snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
227
oryx/hasherx/hash_comparator.go
Normal file
227
oryx/hasherx/hash_comparator.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package hasherx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
var ErrUnknownHashAlgorithm = errors.New("unknown hash algorithm")
|
||||
|
||||
// Compare the given password with the given hash.
|
||||
func Compare(ctx context.Context, password []byte, hash []byte) error {
|
||||
switch {
|
||||
case IsBcryptHash(hash):
|
||||
return CompareBcrypt(ctx, password, hash)
|
||||
case IsArgon2idHash(hash):
|
||||
return CompareArgon2id(ctx, password, hash)
|
||||
case IsArgon2iHash(hash):
|
||||
return CompareArgon2i(ctx, password, hash)
|
||||
case IsPbkdf2Hash(hash):
|
||||
return ComparePbkdf2(ctx, password, hash)
|
||||
default:
|
||||
return errors.WithStack(ErrUnknownHashAlgorithm)
|
||||
}
|
||||
}
|
||||
|
||||
func CompareBcrypt(_ context.Context, password []byte, hash []byte) error {
|
||||
if err := validateBcryptPasswordLength(password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := bcrypt.CompareHashAndPassword(hash, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CompareArgon2id(_ context.Context, password []byte, hash []byte) error {
|
||||
// Extract the parameters, salt and derived key from the encoded password
|
||||
// hash.
|
||||
p, salt, hash, err := decodeArgon2idHash(string(hash))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mem := uint64(p.Memory)
|
||||
if mem > math.MaxUint32 {
|
||||
return errors.WithStack(ErrInvalidHash)
|
||||
}
|
||||
|
||||
// Derive the key from the other password using the same parameters.
|
||||
otherHash := argon2.IDKey(password, salt, p.Iterations, uint32(mem), 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 nil
|
||||
}
|
||||
return errors.WithStack(ErrMismatchedHashAndPassword)
|
||||
}
|
||||
|
||||
func CompareArgon2i(_ context.Context, password []byte, hash []byte) error {
|
||||
// Extract the parameters, salt and derived key from the encoded password
|
||||
// hash.
|
||||
p, salt, hash, err := decodeArgon2idHash(string(hash))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mem := uint64(p.Memory)
|
||||
if mem > math.MaxUint32 {
|
||||
return errors.WithStack(ErrInvalidHash)
|
||||
}
|
||||
|
||||
// Derive the key from the other password using the same parameters.
|
||||
otherHash := argon2.Key(password, salt, p.Iterations, uint32(mem), 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 nil
|
||||
}
|
||||
return errors.WithStack(ErrMismatchedHashAndPassword)
|
||||
}
|
||||
|
||||
func ComparePbkdf2(_ context.Context, password []byte, hash []byte) error {
|
||||
// Extract the parameters, salt and derived key from the encoded password
|
||||
// hash.
|
||||
p, salt, hash, err := decodePbkdf2Hash(string(hash))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Derive the key from the other password using the same parameters.
|
||||
otherHash := pbkdf2.Key(password, salt, int(p.Iterations), int(p.KeyLength), getPseudorandomFunctionForPbkdf2(p.Algorithm))
|
||||
|
||||
// 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 nil
|
||||
}
|
||||
return errors.WithStack(ErrMismatchedHashAndPassword)
|
||||
}
|
||||
|
||||
var (
|
||||
isBcryptHash = regexp.MustCompile(`^\$2[abzy]?\$`)
|
||||
isArgon2idHash = regexp.MustCompile(`^\$argon2id\$`)
|
||||
isArgon2iHash = regexp.MustCompile(`^\$argon2i\$`)
|
||||
isPbkdf2Hash = regexp.MustCompile(`^\$pbkdf2-sha[0-9]{1,3}\$`)
|
||||
)
|
||||
|
||||
func IsBcryptHash(hash []byte) bool {
|
||||
return isBcryptHash.Match(hash)
|
||||
}
|
||||
|
||||
func IsArgon2idHash(hash []byte) bool {
|
||||
return isArgon2idHash.Match(hash)
|
||||
}
|
||||
|
||||
func IsArgon2iHash(hash []byte) bool {
|
||||
return isArgon2iHash.Match(hash)
|
||||
}
|
||||
|
||||
func IsPbkdf2Hash(hash []byte) bool {
|
||||
return isPbkdf2Hash.Match(hash)
|
||||
}
|
||||
|
||||
func decodeArgon2idHash(encodedHash string) (p *Argon2Config, salt, hash []byte, err error) {
|
||||
parts := strings.Split(encodedHash, "$")
|
||||
if len(parts) != 6 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
|
||||
var version int
|
||||
_, err = fmt.Sscanf(parts[2], "v=%d", &version)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if version != argon2.Version {
|
||||
return nil, nil, nil, ErrIncompatibleVersion
|
||||
}
|
||||
|
||||
p = new(Argon2Config)
|
||||
_, err = fmt.Sscanf(parts[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(parts[4])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
saltLength := uint(len(salt))
|
||||
if saltLength > math.MaxUint32 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
p.SaltLength = uint32(saltLength)
|
||||
|
||||
hash, err = base64.RawStdEncoding.Strict().DecodeString(parts[5])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keyLength := uint(len(hash))
|
||||
if keyLength > math.MaxUint32 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
p.KeyLength = uint32(keyLength)
|
||||
|
||||
return p, salt, hash, nil
|
||||
}
|
||||
|
||||
// decodePbkdf2Hash decodes PBKDF2 encoded password hash.
|
||||
// format: $pbkdf2-<digest>$i=<iterations>,l=<length>$<salt>$<hash>
|
||||
func decodePbkdf2Hash(encodedHash string) (p *PBKDF2Config, salt, hash []byte, err error) {
|
||||
parts := strings.Split(encodedHash, "$")
|
||||
if len(parts) != 5 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
|
||||
p = new(PBKDF2Config)
|
||||
digestParts := strings.SplitN(parts[1], "-", 2)
|
||||
if len(digestParts) != 2 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
p.Algorithm = digestParts[1]
|
||||
|
||||
_, err = fmt.Sscanf(parts[2], "i=%d,l=%d", &p.Iterations, &p.KeyLength)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
salt, err = base64.RawStdEncoding.Strict().DecodeString(parts[3])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
saltLength := uint(len(salt))
|
||||
if saltLength > math.MaxUint32 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
p.SaltLength = uint32(saltLength)
|
||||
|
||||
hash, err = base64.RawStdEncoding.Strict().DecodeString(parts[4])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keyLength := uint(len(hash))
|
||||
if keyLength > math.MaxUint32 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
p.KeyLength = uint32(keyLength)
|
||||
|
||||
return p, salt, hash, nil
|
||||
}
|
||||
Reference in New Issue
Block a user