Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
G
go-common
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
ai3
go-common
Commits
3d4cd358
Commit
3d4cd358
authored
4 months ago
by
ale
Browse files
Options
Downloads
Patches
Plain Diff
Add Argon2id support
parent
811dad51
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
pwhash/argon2.go
+90
-22
90 additions, 22 deletions
pwhash/argon2.go
pwhash/password.go
+11
-10
11 additions, 10 deletions
pwhash/password.go
pwhash/password_test.go
+10
-1
10 additions, 1 deletion
pwhash/password_test.go
with
111 additions
and
33 deletions
pwhash/argon2.go
+
90
−
22
View file @
3d4cd358
...
...
@@ -12,8 +12,9 @@ import (
"golang.org/x/crypto/argon2"
)
var
(
argonKeyLen
uint32
=
32
const
(
argonLegacyKeySize
=
32
argonDefaultKeySize
=
16
argonSaltLen
=
16
)
...
...
@@ -28,9 +29,11 @@ type argon2PasswordHash struct {
// newArgon2PasswordHash returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func
newArgon2PasswordHash
(
time
,
mem
uint32
,
threads
uint8
,
codec
argon2Codec
)
PasswordHash
{
func
newArgon2PasswordHash
(
kind
string
,
keySize
int
,
time
,
mem
uint32
,
threads
uint8
,
codec
argon2Codec
)
PasswordHash
{
return
&
argon2PasswordHash
{
params
:
argon2Params
{
KeySize
:
keySize
,
Kind
:
kind
,
Time
:
time
,
Memory
:
mem
,
Threads
:
threads
,
...
...
@@ -51,7 +54,7 @@ func NewArgon2() PasswordHash {
// NewArgon2WithParams returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func
NewArgon2WithParams
(
time
,
mem
uint32
,
threads
uint8
)
PasswordHash
{
return
newArgon2PasswordHash
(
time
,
mem
,
threads
,
&
a2Codec
{})
return
newArgon2PasswordHash
(
kindArgon2I
,
argonLegacyKeySize
,
time
,
mem
,
threads
,
&
a2
Legacy
Codec
{})
}
// NewArgon2Std returns an Argon2i-based PasswordHash that conforms
...
...
@@ -64,12 +67,12 @@ func NewArgon2Std() PasswordHash {
)
}
// NewArgon2StdWithParams returns an Argon2i-based PasswordHash using
// NewArgon2StdWithParams returns an Argon2i
d
-based PasswordHash using
// the specified parameters for time, memory, and number of
// threads. This will use the string encoding ("$argon2$") documented
// threads. This will use the string encoding ("$argon2
id
$") documented
// in the argon2 reference implementation.
func
NewArgon2StdWithParams
(
time
,
mem
uint32
,
threads
uint8
)
PasswordHash
{
return
newArgon2PasswordHash
(
time
,
mem
,
threads
,
&
argon2StdCodec
{})
return
newArgon2PasswordHash
(
kindArgon2ID
,
argonDefaultKeySize
,
time
,
mem
,
threads
,
&
argon2StdCodec
{})
}
// ComparePassword returns true if the given password matches the
...
...
@@ -79,28 +82,53 @@ func (s *argon2PasswordHash) ComparePassword(encrypted, password string) bool {
if
err
!=
nil
{
return
false
}
dk2
:=
argon2
.
Key
([]
byte
(
password
),
salt
,
params
.
Time
,
params
.
Memory
,
params
.
Threads
,
argonKeyLen
)
dk2
:=
params
.
hash
(
password
,
salt
)
return
subtle
.
ConstantTimeCompare
(
dk
,
dk2
)
==
1
}
// Encrypt the given password with the Argon2 algorithm.
func
(
s
*
argon2PasswordHash
)
Encrypt
(
password
string
)
string
{
salt
:=
getRandomBytes
(
argonSaltLen
)
dk
:=
argon2
.
Key
([]
byte
(
password
),
salt
,
s
.
params
.
Time
,
s
.
params
.
Memory
,
s
.
params
.
Threads
,
argonKeyLen
)
dk
:=
s
.
params
.
hash
(
password
,
salt
)
return
s
.
codec
.
encodeArgon2Hash
(
s
.
params
,
salt
,
dk
)
}
const
(
kindArgon2I
=
"argon2i"
kindArgon2ID
=
"argon2id"
)
type
argon2Params
struct
{
Kind
string
KeySize
int
Time
uint32
Memory
uint32
Threads
uint8
}
func
(
p
argon2Params
)
hash
(
password
string
,
salt
[]
byte
)
[]
byte
{
if
p
.
KeySize
==
0
{
panic
(
"key size is 0"
)
}
switch
p
.
Kind
{
case
kindArgon2I
:
return
argon2
.
Key
([]
byte
(
password
),
salt
,
p
.
Time
,
p
.
Memory
,
p
.
Threads
,
uint32
(
p
.
KeySize
))
case
kindArgon2ID
:
return
argon2
.
IDKey
([]
byte
(
password
),
salt
,
p
.
Time
,
p
.
Memory
,
p
.
Threads
,
uint32
(
p
.
KeySize
))
default
:
panic
(
"unknown argon2 hash kind"
)
}
}
// Default Argon2 parameters are tuned for a high-traffic
// authentication service (<1ms per operation).
var
defaultArgon2Params
=
argon2Params
{
Kind
:
kindArgon2ID
,
KeySize
:
16
,
Time
:
1
,
Memory
:
4
*
1024
,
Memory
:
6
4
*
1024
,
Threads
:
4
,
}
...
...
@@ -109,13 +137,14 @@ type argon2Codec interface {
decodeArgon2Hash
(
string
)
(
argon2Params
,
[]
byte
,
[]
byte
,
error
)
}
type
a2Codec
struct
{}
// Argon2i legacy encoding, do not use.
type
a2LegacyCodec
struct
{}
func
(
*
a2Codec
)
encodeArgon2Hash
(
params
argon2Params
,
salt
,
dk
[]
byte
)
string
{
func
(
*
a2
Legacy
Codec
)
encodeArgon2Hash
(
params
argon2Params
,
salt
,
dk
[]
byte
)
string
{
return
fmt
.
Sprintf
(
"$a2$%d$%d$%d$%x$%x"
,
params
.
Time
,
params
.
Memory
,
params
.
Threads
,
salt
,
dk
)
}
func
(
*
a2Codec
)
decodeArgon2Hash
(
s
string
)
(
params
argon2Params
,
salt
[]
byte
,
dk
[]
byte
,
err
error
)
{
func
(
*
a2
Legacy
Codec
)
decodeArgon2Hash
(
s
string
)
(
params
argon2Params
,
salt
[]
byte
,
dk
[]
byte
,
err
error
)
{
if
!
strings
.
HasPrefix
(
s
,
"$a2$"
)
{
err
=
errors
.
New
(
"not an Argon2 password hash"
)
return
...
...
@@ -127,6 +156,8 @@ func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk
return
}
params
.
Kind
=
kindArgon2I
var
i
uint64
if
i
,
err
=
strconv
.
ParseUint
(
parts
[
0
],
10
,
32
);
err
!=
nil
{
...
...
@@ -148,16 +179,36 @@ func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk
if
err
!=
nil
{
return
}
dk
,
err
=
hex
.
DecodeString
(
parts
[
4
])
if
err
!=
nil
{
return
}
params
.
KeySize
=
len
(
dk
)
switch
len
(
dk
)
{
case
16
,
24
,
32
:
default
:
err
=
errors
.
New
(
"bad key size"
)
}
return
}
// Standard Argon2 encoding as per the reference implementation in
// https://github.com/P-H-C/phc-winner-argon2/blob/4ac8640c2adc1257677d27d3f833c8d1ee68c7d2/src/encoding.c#L242-L252
type
argon2StdCodec
struct
{}
const
argon2HashVersionStr
=
"v=19"
func
(
*
argon2StdCodec
)
encodeArgon2Hash
(
params
argon2Params
,
salt
,
dk
[]
byte
)
string
{
encSalt
:=
base64
.
RawStdEncoding
.
EncodeToString
(
salt
)
encDK
:=
base64
.
RawStdEncoding
.
EncodeToString
(
dk
)
return
fmt
.
Sprintf
(
"$argon2i$v=19$m=%d,t=%d,p=%d$%s$%s"
,
params
.
Memory
,
params
.
Time
,
params
.
Threads
,
encSalt
,
encDK
)
return
fmt
.
Sprintf
(
"$%s$%s$m=%d,t=%d,p=%d$%s$%s"
,
params
.
Kind
,
argon2HashVersionStr
,
params
.
Memory
,
params
.
Time
,
params
.
Threads
,
encSalt
,
encDK
)
}
func
parseArgon2HashParams
(
s
string
)
(
params
argon2Params
,
err
error
)
{
...
...
@@ -181,7 +232,7 @@ func parseArgon2HashParams(s string) (params argon2Params, err error) {
i
,
err
=
strconv
.
ParseUint
(
kv
[
1
],
10
,
8
)
params
.
Threads
=
uint8
(
i
)
default
:
err
=
errors
.
New
(
"unknown parameter in hash"
)
err
=
fmt
.
Errorf
(
"unknown parameter
'%s'
in hash"
,
kv
[
0
]
)
}
if
err
!=
nil
{
return
...
...
@@ -191,29 +242,46 @@ func parseArgon2HashParams(s string) (params argon2Params, err error) {
}
func
(
*
argon2StdCodec
)
decodeArgon2Hash
(
s
string
)
(
params
argon2Params
,
salt
[]
byte
,
dk
[]
byte
,
err
error
)
{
if
!
strings
.
HasPrefix
(
s
,
"$argon2i$"
)
{
var
kind
string
switch
{
case
strings
.
HasPrefix
(
s
,
"$argon2i$"
)
:
kind
=
kindArgon2I
case
strings
.
HasPrefix
(
s
,
"$argon2id$"
)
:
kind
=
kindArgon2ID
default
:
err
=
errors
.
New
(
"not an Argon2 password hash"
)
return
}
parts
:=
strings
.
SplitN
(
s
[
9
:
]
,
"$"
,
4
)
if
len
(
parts
)
!=
4
{
parts
:=
strings
.
SplitN
(
s
,
"$"
,
6
)
if
len
(
parts
)
!=
6
{
err
=
errors
.
New
(
"bad encoding"
)
return
}
if
parts
[
0
]
!=
"v=19"
{
if
parts
[
2
]
!=
argon2HashVersionStr
{
err
=
errors
.
New
(
"bad argon2 hash version"
)
return
}
params
,
err
=
parseArgon2HashParams
(
parts
[
1
])
params
,
err
=
parseArgon2HashParams
(
parts
[
3
])
if
err
!=
nil
{
return
}
if
salt
,
err
=
base64
.
RawStdEncoding
.
DecodeString
(
parts
[
2
]);
err
!=
nil
{
params
.
Kind
=
kind
if
salt
,
err
=
base64
.
RawStdEncoding
.
DecodeString
(
parts
[
4
]);
err
!=
nil
{
return
}
if
dk
,
err
=
base64
.
RawStdEncoding
.
DecodeString
(
parts
[
5
]);
err
!=
nil
{
return
}
dk
,
err
=
base64
.
RawStdEncoding
.
DecodeString
(
parts
[
3
])
params
.
KeySize
=
len
(
dk
)
switch
len
(
dk
)
{
case
16
,
24
,
32
:
default
:
err
=
errors
.
New
(
"bad key size"
)
}
return
}
This diff is collapsed.
Click to expand it.
pwhash/password.go
+
11
−
10
View file @
3d4cd358
...
...
@@ -18,7 +18,6 @@
//
// go test -bench=Argon2 -run=none . 2>&1 | \
// awk '/^Bench/ {ops=1000000000 / $3; print $1 " " ops " ops/sec"}'
//
package
pwhash
import
(
...
...
@@ -55,6 +54,7 @@ var prefixRegistry = map[string]PasswordHash{
"$s$"
:
NewScrypt
(),
"$a2$"
:
NewArgon2
(),
"$argon2i$"
:
NewArgon2Std
(),
"$argon2id$"
:
NewArgon2Std
(),
}
// ComparePassword returns true if the given password matches the
...
...
@@ -65,6 +65,7 @@ func ComparePassword(encrypted, password string) bool {
return
h
.
ComparePassword
(
encrypted
,
password
)
}
}
return
false
}
...
...
This diff is collapsed.
Click to expand it.
pwhash/password_test.go
+
10
−
1
View file @
3d4cd358
...
...
@@ -65,7 +65,7 @@ func testImpl(t *testing.T, h PasswordHash) {
}
}
func
TestStandardArgon2Password
(
t
*
testing
.
T
)
{
func
TestStandardArgon2
I
Password
(
t
*
testing
.
T
)
{
enc
:=
"$argon2i$v=19$m=32768,t=4,p=1$DG0B56zlrrx+VMVaM6wvsw$8iV+HwTKmofjrb+q9I2zZGQnGXzXtiIXv8VdHdvbbX8"
pw
:=
"idontmindbirds"
if
!
ComparePassword
(
enc
,
pw
)
{
...
...
@@ -73,6 +73,15 @@ func TestStandardArgon2Password(t *testing.T) {
}
}
func
TestStandardArgon2IDPassword
(
t
*
testing
.
T
)
{
// python3 -c 'from argon2 import PasswordHasher ; print(PasswordHasher().hash("idontmindbirds"))'
enc
:=
"$argon2id$v=19$m=102400,t=2,p=8$7hQLBrHoxYxRO0R8km62pA$Dv5+BCctW4nCrxsy5C9JBg"
pw
:=
"idontmindbirds"
if
!
ComparePassword
(
enc
,
pw
)
{
t
.
Fatal
(
"comparison failed"
)
}
}
func
BenchmarkArgon2
(
b
*
testing
.
B
)
{
var
testParams
[]
argon2Params
for
iTime
:=
1
;
iTime
<=
5
;
iTime
++
{
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment