Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Password hashing and verification with Argon2id
package main
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"log"
"strings"
"golang.org/x/crypto/argon2"
)
var (
ErrInvalidHash = errors.New("the encoded hash is not in the correct format")
ErrIncompatibleVersion = errors.New("incompatible version of argon2")
)
type params struct {
memory uint32
iterations uint32
parallelism uint8
saltLength uint32
keyLength uint32
}
func main() {
p := &params{
memory: 64 * 1024,
iterations: 3,
parallelism: 2,
saltLength: 16,
keyLength: 32,
}
encodedHash, err := generateFromPassword("password123", p)
if err != nil {
log.Fatal(err)
}
match, err := comparePasswordAndHash("pa$$word", encodedHash)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Match: %v\n", match)
}
func generateFromPassword(password string, p *params) (encodedHash string, err error) {
// Generate a cryptographically secure random salt.
salt, err := generateRandomBytes(p.saltLength)
if err != nil {
return "", err
}
// Pass the plaintext password, salt and parameters to the argon2.IDKey
// function. This will generate a hash of the password using the Argon2id
// variant.
hash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
// Base64 encode the salt and hashed password.
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
// Return a string using the standard encoded hash representation.
encodedHash = fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.memory, p.iterations, p.parallelism, b64Salt, b64Hash)
return encodedHash, nil
}
func generateRandomBytes(n uint32) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}
func comparePasswordAndHash(password, encodedHash string) (match bool, err error) {
// Extract the parameters, salt and derived key from the encoded password
// hash.
p, salt, hash, err := decodeHash(encodedHash)
if err != nil {
return false, err
}
// Derive the key from the other password using the same parameters.
otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
// Check that the contents of the hashed passwords are identical. Note
// that we are using the subtle.ConstantTimeCompare() function for this
// to help prevent timing attacks.
if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
return true, nil
}
return false, nil
}
func decodeHash(encodedHash string) (p *params, salt, hash []byte, err error) {
vals := strings.Split(encodedHash, "$")
if len(vals) != 6 {
return nil, nil, nil, ErrInvalidHash
}
var version int
_, err = fmt.Sscanf(vals[2], "v=%d", &version)
if err != nil {
return nil, nil, nil, err
}
if version != argon2.Version {
return nil, nil, nil, ErrIncompatibleVersion
}
p = &params{}
_, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism)
if err != nil {
return nil, nil, nil, err
}
salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4])
if err != nil {
return nil, nil, nil, err
}
p.saltLength = uint32(len(salt))
hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5])
if err != nil {
return nil, nil, nil, err
}
p.keyLength = uint32(len(hash))
return p, salt, hash, nil
}
@phifty

This comment has been minimized.

Copy link

@phifty phifty commented Dec 11, 2018

Hi,
Great code and blog post! I've implemented a similar thing in my Go version of crypt. Feel very welcome to do a code review or comment it in another constructive way.
Best regards

@ciehanski

This comment has been minimized.

Copy link

@ciehanski ciehanski commented Dec 16, 2018

Just a minor refactor but you could replace lines 96-99 with:

return subtle.ConstantTimeCompare(hash, otherHash) == 1, nil
@AleksandrMakarenkov

This comment has been minimized.

Copy link

@AleksandrMakarenkov AleksandrMakarenkov commented Oct 19, 2020

Hi!
How about to use argon2.Key() instead of argon2.IDKey()?
Seems like argon2i more suitable for server side password hashing and argon2id is more relevant to "proof-of-work" checks.

@alexedwards

This comment has been minimized.

Copy link
Owner Author

@alexedwards alexedwards commented Oct 24, 2020

@AleksandrMakarenkov my understanding is that 2i should be preferred to 2d for password hashing --- not necessarily preferred to 2id. Using 2id is arguably better than just 2i in most cases as it provides some resistance to TMTO attacks.

@AleksandrMakarenkov

This comment has been minimized.

Copy link

@AleksandrMakarenkov AleksandrMakarenkov commented Nov 1, 2020

@alexedwards Hey!
You were right :D
After investigating some posts i realized that argon2id is recommended as "primary algo" for most cases.
Here the link, https://crypto.stackexchange.com/questions/48935/why-use-argon2i-or-argon2d-if-argon2id-exists,
i hope it helps someone else!

@cbluth

This comment has been minimized.

Copy link

@cbluth cbluth commented Apr 23, 2021

great blog post, thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment