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
|
package kdf
import (
"crypto/subtle"
"strconv"
"github.com/pkg/errors"
"github.com/smallstep/cli/crypto/randutil"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/scrypt"
)
// KDF is the type that all the key derivation functions implements. The
// current methods uses safe default values, but future improvements can add
// functional options to be able to use custom settings.
type KDF func(password []byte) (string, error)
// Scrypt uses scrypt-32768 to derive the given password. Returns the hash
// using the PHC string format.
func Scrypt(password []byte) (string, error) {
salt, err := randutil.Salt(16)
if err != nil {
return "", err
}
// use scrypt-32768 by default
p := scryptParams[scryptHash32768]
hash, err := scrypt.Key(password, salt, p.N, p.r, p.p, p.kl)
if err != nil {
return "", errors.Wrap(err, "error deriving password")
}
return phcEncode("scrypt", p.getParams(), salt, hash), nil
}
// Bcrypt uses bcrypt to derive the given password. Returns the hash
// using the Modular Crypt Format standard for bcrypt implementations.
func Bcrypt(password []byte) (string, error) {
hash, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
if err != nil {
return "", errors.Wrap(err, "error deriving password")
}
return string(hash), nil
}
// Argon2i uses Argon2i variant to derive the given password. Returns the hash
// using the PHC string format.
//
// Argon2i is optimized to resist side-channel attacks.
func Argon2i(password []byte) (string, error) {
salt, err := randutil.Salt(16)
if err != nil {
return "", err
}
p := argon2Params[argon2iHash]
hash := argon2.Key(password, salt, p.t, p.m, p.p, p.kl)
identifier := "argon2i$v=" + strconv.Itoa(argon2.Version)
return phcEncode(identifier, p.getParams(), salt, hash), nil
}
// Argon2id uses Argon2id variant to derive the given password. Returns the
// hash using the PHC string format.
//
// Argon2id is an hybrid version of Argon2d, that maximizes resistance to GPU
// attacks and Argon2i that is optimized to resist side-channel attacks. The
// Internet draft (https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03)
// recommends using Argon2id.
func Argon2id(password []byte) (string, error) {
salt, err := randutil.Salt(16)
if err != nil {
return "", err
}
p := argon2Params[argon2idHash]
hash := argon2.IDKey(password, salt, p.t, p.m, p.p, p.kl)
identifier := "argon2id$v=" + strconv.Itoa(argon2.Version)
return phcEncode(identifier, p.getParams(), salt, hash), nil
}
// Compare compares the password with the given PHC encoded hash, returns true
// if they match. The time taken is a function of the length of the slices and
// is independent of the contents.
func Compare(password, phc []byte) (bool, error) {
id, version, params, salt, hash, err := phcDecode(string(phc))
if err != nil {
return false, errors.Wrap(err, "error decoding hash")
}
var hashedPass []byte
switch id {
case bcryptHash:
return (bcrypt.CompareHashAndPassword(hash, password) == nil), nil
case scryptHash:
p, err := newScryptParams(params)
if err != nil {
return false, err
}
hashedPass, err = scrypt.Key(password, salt, p.N, p.r, p.p, len(hash))
if err != nil {
return false, errors.Wrap(err, "error deriving input")
}
case argon2iHash:
p, err := newArgon2Params(params)
if err != nil {
return false, err
}
if version != 0 && version != argon2.Version {
return false, errors.Errorf("unsupported argon2 version '%d'", version)
}
hashedPass = argon2.Key(password, salt, p.t, p.m, p.p, uint32(len(hash)))
case argon2idHash:
p, err := newArgon2Params(params)
if err != nil {
return false, err
}
if version != 0 && version != argon2.Version {
return false, errors.Errorf("unsupported argon2 version '%d'", version)
}
hashedPass = argon2.IDKey(password, salt, p.t, p.m, p.p, uint32(len(hash)))
default:
return false, errors.Errorf("invalid or unsupported hash method with id '%s'", id)
}
return (subtle.ConstantTimeCompare(hash, hashedPass) == 1), nil
}
// CompareString compares the given password with the given PHC encoded hash,
// returns true if they match. The time taken is a function of the length of
// the slices and is independent of the contents.
func CompareString(password, phc string) (bool, error) {
return Compare([]byte(password), []byte(phc))
}
|