-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpass.go
131 lines (111 loc) · 3.11 KB
/
pass.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"golang.org/x/crypto/argon2"
"golang.org/x/sync/semaphore"
"strings"
)
var oldAlgo = errors.New("Password was hased with an algorithm that has now been replaced.")
var hashSemaphore *semaphore.Weighted
func init() {
hashSemaphore = semaphore.NewWeighted(15)
}
type HashPair struct {
HashAlgo int32
PassHash string
}
type algoEntry struct {
hash func(pass string, arg uint) string
compare func(pass, hash string, arg uint) bool
arg uint
}
var passAlgos []algoEntry = []algoEntry{
{hash: argon2Hash, compare: argon2Compare, arg: 0},
}
func init() {
if len(passAlgos) > 1 {
panic("make sure passwords are hashed on the server when changing them online.")
}
}
// Used for timing obfuscation
var fakePassHash []userEntry
var fakePassEntropy []byte = make([]byte, 128)
func init() {
fakePassHash = make([]userEntry, 0, len(passAlgos))
for algo, entry := range passAlgos {
hash := entry.hash("hunter2D#EXdx4&%$JmP68", entry.arg) // maybe change to randomly generated.
fakePassHash = append(fakePassHash, userEntry{Hash: HashPair{int32(algo), hash}})
}
n, err := rand.Read(fakePassEntropy)
if err != nil {
panic(err)
}
if n != 128 {
panic("bad entropy")
}
}
func argon2Core(pass string, salt []byte, arg uint) string {
var time, memory, keyLen uint32
var threads uint8
switch arg {
case 0:
// low memory reccomendation from OWASP+key length recommended from the Argon2 spec
memory, time, threads, keyLen = 15*1024, 2, 1, 16
default:
panic("invalid argument number")
}
hash := argon2.IDKey([]byte(pass), salt, time, memory, threads, keyLen)
return fmt.Sprintf("%s$%s",
base64.URLEncoding.EncodeToString(salt),
base64.URLEncoding.EncodeToString(hash))
}
func argon2Hash(pass string, arg uint) string {
var salt []byte
switch arg {
case 0:
// low memory reccomendation from OWASP+key length recommended from the Argon2 spec
salt = make([]byte, 16)
_, err := rand.Read(salt)
if err != nil {
panic(err)
}
default:
panic("invalid argument number")
}
return argon2Core(pass, salt, arg)
}
func argon2Compare(pass, hash1 string, arg uint) bool {
// Parse errors can only be the result of programmer error, so we panic.
var parseErr error = errors.New("Fatal error, unparsable password hash")
sh := strings.Split(hash1, "$")
if len(sh) != 2 {
panic(parseErr)
}
salt, err := base64.URLEncoding.DecodeString(sh[0])
if err != nil {
panic(parseErr)
}
hash2 := argon2Core(pass, salt, arg)
return subtle.ConstantTimeCompare([]byte(hash1), []byte(hash2)) == 1 // allocation :/
}
func passHash(pass string) HashPair {
hashAlgo := int32(len(passAlgos) - 1)
entry := passAlgos[hashAlgo]
passHash := entry.hash(pass, entry.arg)
return HashPair{hashAlgo, passHash}
}
func passCompare(pass string, hash HashPair) (bool, error) {
if hash.HashAlgo < 0 || int(hash.HashAlgo) >= len(passAlgos) {
panic("Fatal error, incorrect algo number")
}
entry := passAlgos[hash.HashAlgo]
var err error
if int(hash.HashAlgo) < len(passAlgos)-1 {
err = oldAlgo
}
return entry.compare(pass, hash.PassHash, entry.arg), err
}