Skip to content

Commit eca5ca9

Browse files
committed
added argon2 algorithm
1 parent 40a5cf3 commit eca5ca9

10 files changed

+206
-33
lines changed

Gopkg.lock

+7-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

argon2i.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package crypt
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
9+
"golang.org/x/crypto/argon2"
10+
)
11+
12+
// Argon2iPrefix defines the settings prefix for argon2i hashes.
13+
const Argon2iPrefix = "$argon2i$"
14+
15+
func init() {
16+
RegisterAlgorithm(Argon2iPrefix, argon2iAlgorithm)
17+
}
18+
19+
const (
20+
argon2iDefaultMemory = 32 * 1024
21+
argon2iDefaultTime = 4
22+
argon2iDefaultThreads = 4
23+
argon2iKeySize = 32
24+
argon2iMaxSaltSize = 0xFFFFFFFF
25+
)
26+
27+
func argon2iAlgorithm(password, settings string) (string, error) {
28+
passwordBytes := []byte(password)
29+
_, parameter, salt, _, err := DecodeSettings(settings)
30+
if err != nil {
31+
return "", err
32+
}
33+
saltBytes, err := Base64Encoding.DecodeString(salt)
34+
if err != nil {
35+
return "", fmt.Errorf("base64 decode [%s]: %v", salt, err)
36+
}
37+
if len(saltBytes) > argon2iMaxSaltSize {
38+
saltBytes = saltBytes[:argon2iMaxSaltSize]
39+
}
40+
memory := parameter.GetInt("m", argon2iDefaultMemory)
41+
time := parameter.GetInt("t", argon2iDefaultTime)
42+
threads := parameter.GetInt("p", argon2iDefaultThreads)
43+
44+
hash := argon2.Key(passwordBytes, saltBytes, uint32(time), uint32(memory), uint8(threads), argon2iKeySize)
45+
46+
p := []string{}
47+
if memory != argon2iDefaultMemory {
48+
p = append(p, "m="+strconv.Itoa(memory))
49+
}
50+
if time != argon2iDefaultTime {
51+
p = append(p, "t="+strconv.Itoa(time))
52+
}
53+
if threads != argon2iDefaultThreads {
54+
p = append(p, "p="+strconv.Itoa(threads))
55+
}
56+
57+
buf := bytes.Buffer{}
58+
buf.Write([]byte(Argon2iPrefix))
59+
buf.WriteString("v=19$")
60+
if len(p) > 0 {
61+
buf.WriteString(strings.Join(p, ","))
62+
buf.WriteString("$")
63+
}
64+
buf.WriteString(Base64Encoding.EncodeToString(saltBytes))
65+
buf.WriteString("$")
66+
buf.WriteString(Base64Encoding.EncodeToString(hash))
67+
return buf.String(), nil
68+
}

argon2i_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package crypt_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/simia-tech/crypt"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestArgon2i(t *testing.T) {
12+
tcs := []struct {
13+
password string
14+
settings string
15+
expectedResult string
16+
expectedErr error
17+
}{
18+
{"password", "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ", "$argon2i$v=19$m=65536,t=2$c29tZXNhbHQ$IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4", nil},
19+
{"another password", "$argon2i$v=19$m=65536,t=2,p=4$YW5vdGhlcnNhbHQ", "$argon2i$v=19$m=65536,t=2$YW5vdGhlcnNhbHQ$BCRltpeTFX0QYrELiOXWGZniID9nOUsBPy8Bu0SE7bM", nil},
20+
{"password", "$argon2i$v=19$m=65536,t=2,p=4$bG9uZ3NhbHRsb25nc2FsdGxvbmc", "$argon2i$v=19$m=65536,t=2$bG9uZ3NhbHRsb25nc2FsdGxvbmc$rDmQABiNkSO3bGHbBUkShgb7wIlBP8HHfq6nDH+Sqss", nil},
21+
}
22+
23+
for _, tc := range tcs {
24+
t.Run(tc.settings, func(t *testing.T) {
25+
result, err := crypt.Crypt(tc.password, tc.settings)
26+
require.Equal(t, tc.expectedErr, err)
27+
assert.Equal(t, tc.expectedResult, result)
28+
})
29+
}
30+
}

base64.go

+16-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package crypt
22

3+
import "encoding/base64"
4+
5+
// Base64Encoding implements the crypt-specific base63 encoding.
6+
var Base64Encoding = base64.StdEncoding.WithPadding(base64.NoPadding)
7+
38
const (
4-
alphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
9+
encode24Bit = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
510
)
611

7-
func base64_24Bit(src []byte) []byte {
12+
// Encode24BitBase64 implements a special version of base64 that is used
13+
// with md5, sha256 and sha512.
14+
func Encode24BitBase64(src []byte) []byte {
815
if len(src) == 0 {
916
return []byte{} // TODO: return nil
1017
}
@@ -16,10 +23,10 @@ func base64_24Bit(src []byte) []byte {
1623
n := len(src) / 3 * 3
1724
for si < n {
1825
val := uint(src[si+2])<<16 | uint(src[si+1])<<8 | uint(src[si])
19-
dst[di+0] = alphabet[val&0x3f]
20-
dst[di+1] = alphabet[val>>6&0x3f]
21-
dst[di+2] = alphabet[val>>12&0x3f]
22-
dst[di+3] = alphabet[val>>18]
26+
dst[di+0] = encode24Bit[val&0x3f]
27+
dst[di+1] = encode24Bit[val>>6&0x3f]
28+
dst[di+2] = encode24Bit[val>>12&0x3f]
29+
dst[di+3] = encode24Bit[val>>18]
2330
di += 4
2431
si += 3
2532
}
@@ -34,10 +41,10 @@ func base64_24Bit(src []byte) []byte {
3441
val |= uint(src[si+1]) << 8
3542
}
3643

37-
dst[di+0] = alphabet[val&0x3f]
38-
dst[di+1] = alphabet[val>>6&0x3f]
44+
dst[di+0] = encode24Bit[val&0x3f]
45+
dst[di+1] = encode24Bit[val>>6&0x3f]
3946
if rem == 2 {
40-
dst[di+2] = alphabet[val>>12]
47+
dst[di+2] = encode24Bit[val>>12]
4148
}
4249
return dst
4350
}

base64_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package crypt_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/simia-tech/crypt"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestBase64(t *testing.T) {
12+
tcs := []struct {
13+
decoded string
14+
encoded string
15+
}{
16+
{"somesalt", "c29tZXNhbHQ"},
17+
{"test", "dGVzdA"},
18+
{"test123test123", "dGVzdDEyM3Rlc3QxMjM"},
19+
}
20+
21+
for _, tc := range tcs {
22+
t.Run(tc.decoded, func(t *testing.T) {
23+
encodeResult := crypt.Base64Encoding.EncodeToString([]byte(tc.decoded))
24+
assert.Equal(t, tc.encoded, encodeResult)
25+
26+
decodeResult, err := crypt.Base64Encoding.DecodeString(string(encodeResult))
27+
require.NoError(t, err)
28+
assert.Equal(t, tc.decoded, string(decodeResult))
29+
})
30+
}
31+
}
32+
33+
func TestEncode24BitBase64(t *testing.T) {
34+
tcs := []struct {
35+
decoded string
36+
expectEncoded string
37+
}{
38+
{"somesalt", "nxKPZBLMgF5"},
39+
{"test", "oJqQo/"},
40+
{"test123test123", "oJqQo3XAnELNnFLAmA1"},
41+
}
42+
43+
for _, tc := range tcs {
44+
t.Run(tc.decoded, func(t *testing.T) {
45+
encodeResult := crypt.Encode24BitBase64([]byte(tc.decoded))
46+
assert.Equal(t, tc.expectEncoded, string(encodeResult))
47+
})
48+
}
49+
}

md5.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func md5Algorithm(password, settings string) (string, error) {
9292
buf.Write([]byte(MD5Prefix))
9393
buf.Write(saltBytes)
9494
buf.WriteByte('$')
95-
buf.Write(base64_24Bit([]byte{
95+
buf.Write(Encode24BitBase64([]byte{
9696
sumA[12], sumA[6], sumA[0],
9797
sumA[13], sumA[7], sumA[1],
9898
sumA[14], sumA[8], sumA[2],

settings.go

+31-20
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,39 @@ func (p Parameter) GetInt(key string, defaultValue int) int {
2525
}
2626

2727
// DecodeSettings decodes the provided settings string into it's parts.
28-
func DecodeSettings(settings string) (string, Parameter, string, string, error) {
29-
parts := strings.Split(settings, "$")
30-
switch {
31-
case len(parts) == 3:
32-
return parts[1], nil, parts[2], "", nil
33-
case len(parts) >= 4:
34-
if strings.Contains(parts[2], "=") {
35-
pairs := strings.Split(parts[2], ",")
36-
p := Parameter{}
37-
for _, pair := range pairs {
38-
pairParts := strings.SplitN(pair, "=", 2)
39-
if len(pairParts) < 2 {
40-
continue
28+
func DecodeSettings(settings string) (code string, parameter Parameter, salt string, hash string, err error) {
29+
step := 0
30+
for _, part := range strings.Split(settings, "$") {
31+
switch step {
32+
case 0, 1:
33+
code = part
34+
step++
35+
case 2:
36+
if strings.Contains(part, "=") {
37+
if parameter == nil {
38+
parameter = Parameter{}
4139
}
42-
p[pairParts[0]] = pairParts[1]
40+
parameter = decodeParameters(parameter, part)
41+
} else {
42+
salt = part
43+
step++
4344
}
44-
if len(parts) >= 5 {
45-
return parts[1], p, parts[3], parts[4], nil
46-
}
47-
return parts[1], p, parts[3], "", nil
45+
case 3:
46+
hash = part
47+
step++
48+
}
49+
}
50+
return
51+
}
52+
53+
func decodeParameters(p Parameter, text string) Parameter {
54+
pairs := strings.Split(text, ",")
55+
for _, pair := range pairs {
56+
parts := strings.SplitN(pair, "=", 2)
57+
if len(parts) < 2 {
58+
continue
4859
}
49-
return parts[1], nil, parts[2], parts[3], nil
60+
p[parts[0]] = parts[1]
5061
}
51-
return "", nil, "", "", ErrInvalidSettings
62+
return p
5263
}

settings_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ func TestDecodeSettings(t *testing.T) {
2323
{"$1$rounds=100$salt", "1", crypt.Parameter{"rounds": "100"}, "salt", "", nil},
2424
{"$1$rounds=200$salt$hash", "1", crypt.Parameter{"rounds": "200"}, "salt", "hash", nil},
2525
{"$1$rounds=300$salt$hash$", "1", crypt.Parameter{"rounds": "300"}, "salt", "hash", nil},
26+
{"$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4",
27+
"argon2i", crypt.Parameter{"v": "19", "m": "65536", "t": "2", "p": "4"}, "c29tZXNhbHQ", "IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4", nil},
2628
}
2729

2830
for _, tc := range tcs {

sha256.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func sha256Algorithm(password, settings string) (string, error) {
110110
}
111111
buf.Write(saltBytes)
112112
buf.WriteByte('$')
113-
buf.Write(base64_24Bit([]byte{
113+
buf.Write(Encode24BitBase64([]byte{
114114
sumA[20], sumA[10], sumA[0],
115115
sumA[11], sumA[1], sumA[21],
116116
sumA[2], sumA[22], sumA[12],

sha512.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func sha512Algorithm(password, settings string) (string, error) {
114114
}
115115
buf.Write(saltBytes)
116116
buf.WriteByte('$')
117-
buf.Write(base64_24Bit([]byte{
117+
buf.Write(Encode24BitBase64([]byte{
118118
sumA[42], sumA[21], sumA[0],
119119
sumA[1], sumA[43], sumA[22],
120120
sumA[23], sumA[2], sumA[44],

0 commit comments

Comments
 (0)