-
Notifications
You must be signed in to change notification settings - Fork 0
/
argon2.go
154 lines (126 loc) · 4.81 KB
/
argon2.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package cryptography
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"strings"
"github.com/irdaislakhuafa/go-sdk/codes"
"github.com/irdaislakhuafa/go-sdk/errors"
"golang.org/x/crypto/argon2"
)
type Argon2 interface {
// Hash text with argon2 algorithm with format from `hashFormat` or you can determine your specific format
Hash(text []byte) (string, error)
// Compare plain text with hash argon2, return `true`` and error `nil` if equal
Compare(text, hashedText []byte) (bool, error)
// Set memory for argon2 parameter
SetMemory(memory uint32) Argon2
// Set iterations for argon2 parameter
SetIterations(iterations uint32) Argon2
// Set parallelism for argon2 parameter
SetParallelism(parallelism uint8) Argon2
}
type argon2impl struct {
delimiter string
lengthValue uint64
hashFormat string
salt []byte
iterations uint32
parallelism uint8
keyLen uint32
memory uint32
version int
b64enc base64.Encoding
}
// Create argon2 hash with default parameter. Compatible with nodejs library `https://github.com/ranisalt/node-argon2.git` if you set `memory = 65536` and `parallelism = 4` because default parameter of my lib is `memory = 4096` and `parallelism = 1`
func NewArgon2() Argon2 {
result := &argon2impl{
delimiter: "$",
lengthValue: 6,
hashFormat: "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
salt: make([]byte, 16),
iterations: 3,
parallelism: 1,
keyLen: 32,
memory: (4 * 1024),
version: argon2.Version,
b64enc: *base64.RawStdEncoding.Strict(),
}
return result
}
func (a *argon2impl) Hash(text []byte) (string, error) {
if _, err := rand.Read(a.salt); err != nil {
return "", errors.NewWithCode(codes.CodeArgon2, "failed to make salt, %v", err)
}
key := argon2.IDKey(text, a.salt, a.iterations, a.memory, a.parallelism, a.keyLen)
encodedKey, err := a.encodeKey(key)
if err != nil {
return "", errors.NewWithCode(codes.CodeArgon2, "failed to encode key with argon2, %v", err)
}
return encodedKey, nil
}
func (a *argon2impl) Compare(text []byte, hashedText []byte) (bool, error) {
// extract all parameter include salt and key length from encoded argon2 hash
arg, key, err := a.decodeKey(hashedText)
if err != nil {
return false, err
}
// generate other hash with same parameters and compare it with existsing hash
otherKey := argon2.IDKey(text, arg.salt, arg.iterations, arg.memory, arg.parallelism, arg.keyLen)
if subtle.ConstantTimeCompare(key, otherKey) != 1 {
return false, errors.NewWithCode(codes.CodeArgon2NotMatch, "argon2 for plain text with hashed text is not match")
}
return true, nil
}
func (a *argon2impl) encodeKey(key []byte) (string, error) {
base64Salt := a.b64enc.EncodeToString(a.salt)
base64Key := a.b64enc.EncodeToString(key)
encodedKey := fmt.Sprintf(a.hashFormat, a.version, a.memory, a.iterations, a.parallelism, base64Salt, base64Key)
return encodedKey, nil
}
func (a *argon2impl) decodeKey(key []byte) (*argon2impl, []byte, error) {
// split enncoded argon2 hash with delimiter
values := strings.Split(string(key), a.delimiter)
// compare length values with standard length
if lengthValue := len(values); lengthValue != int(a.lengthValue) {
return nil, nil, errors.NewWithCode(codes.CodeArgon2, "invalid length of encoded hash, expected %v but got %v", a.lengthValue, lengthValue)
}
// check argon2 version compatibility
version := 0
if _, err := fmt.Sscanf(values[2], "v=%v", &version); err != nil {
return nil, nil, errors.NewWithCode(codes.CodeArgon2, "failed to get argon2 version from hash text, %v", err)
}
if a.version != version {
return nil, nil, errors.NewWithCode(codes.CodeArgon2IncompatibleVersion, "current argon2 version is %v but encoded hash using version %v", a.version, version)
}
// mapping values for for memory, iterations and parallelism
arg := &argon2impl{}
if _, err := fmt.Sscanf(values[3], "m=%d,t=%d,p=%d", &arg.memory, &arg.iterations, &arg.parallelism); err != nil {
return nil, nil, errors.NewWithCode(codes.CodeArgon2, "failed to get memory, iterations and parallelism information from hash, %v", err)
}
// decode base64 salt
var err error
if arg.salt, err = a.b64enc.DecodeString(values[4]); err != nil {
return nil, nil, errors.NewWithCode(codes.CodeArgon2, "failed to decode salt information from hash, %v", err)
}
// decode base64 key
key, err = a.b64enc.DecodeString(values[5])
if err != nil {
return nil, nil, errors.NewWithCode(codes.CodeArgon2, "failed to decode key information from hash, %v", err)
}
arg.keyLen = uint32(len(key))
return arg, key, nil
}
func (a *argon2impl) SetMemory(memory uint32) Argon2 {
a.memory = memory
return a
}
func (a *argon2impl) SetIterations(iterations uint32) Argon2 {
a.iterations = iterations
return a
}
func (a *argon2impl) SetParallelism(parallelism uint8) Argon2 {
a.parallelism = parallelism
return a
}