Skip to content

Instantly share code, notes, and snippets.

@JRaspass
Last active October 4, 2019 16:15
Show Gist options
  • Select an option

  • Save JRaspass/2df6920c29fff913d5523d79afb896d1 to your computer and use it in GitHub Desktop.

Select an option

Save JRaspass/2df6920c29fff913d5523d79afb896d1 to your computer and use it in GitHub Desktop.
package password
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
"golang.org/x/text/unicode/norm"
)
const (
_ = 1 << (10 * iota)
kibibyte
mebibyte
// https://argon2-cffi.readthedocs.io/en/stable/parameters.html
keyLen = 16
mem = 64 * mebibyte / kibibyte
para = 2
saltLen = 16
time = 1
format = "$argon2id$v=%d$m=%d,t=%d,p=%d$%s"
)
var (
errInvalidFormat = errors.New("invalid format")
errInvalidVersion = errors.New("invalid version")
)
// Hash returns the hash of the password.
func Hash(password string) (string, error) {
salt := make([]byte, saltLen)
if _, err := rand.Read(salt); err != nil {
return "", err
}
b64 := base64.RawStdEncoding.EncodeToString(salt) + "$" +
base64.RawStdEncoding.EncodeToString(argon(password, salt, time, mem, para, keyLen))
hash := fmt.Sprintf(format, argon2.Version, mem, time, para, b64)
return hash, nil
}
// Match returns true if the password matches the hash.
func Match(password, hash string) (bool, error) {
if key, salt, t, m, p, err := decode(hash); err != nil {
return false, err
} else {
otherKey := argon(password, salt, t, m, p, uint32(len(key)))
return subtle.ConstantTimeCompare(key, otherKey) == 1, nil
}
}
// Upgrade returns am upgraded hash if applicable.
func Upgrade(password, hash string) (string, error) {
if key, salt, t, m, p, err := decode(hash); err != nil {
return "", err
} else if keyLen != len(key) || mem != m || para != p || saltLen != len(salt) || time != t {
return Hash(password)
} else {
return "", nil
}
}
func argon(password string, salt []byte, t, m uint32, p uint8, l uint32) []byte {
// NFKD normalise passwords https://stackoverflow.com/questions/16173328
return argon2.IDKey(norm.NFKD.Bytes([]byte(password)), salt, t, m, p, l)
}
func decode(hash string) (key, salt []byte, t, m uint32, p uint8, err error) {
var b64 string
var v int
if _, err = fmt.Sscanf(hash, format, &v, &m, &t, &p, &b64); err != nil {
err = errInvalidFormat
return
}
if v != argon2.Version {
err = errInvalidVersion
return
}
b64s := strings.Split(b64, "$")
if len(b64s) != 2 {
err = errInvalidFormat
return
}
if key, err = base64.RawStdEncoding.DecodeString(b64s[1]); err != nil {
err = errInvalidFormat
return
}
if salt, err = base64.RawStdEncoding.DecodeString(b64s[0]); err != nil {
err = errInvalidFormat
return
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment