// Package pw 提供口令散列(PBKDF2-HMAC-SHA256),仅用标准库。 package pw import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" "fmt" "strconv" "strings" ) const iterations = 120000 // Hash 生成 `pbkdf2-sha256$迭代次数$盐$摘要` 形式的字符串。 func Hash(password string) (string, error) { salt := make([]byte, 16) if _, err := rand.Read(salt); err != nil { return "", err } dk := pbkdf2Key([]byte(password), salt, iterations, 32) return fmt.Sprintf("pbkdf2-sha256$%d$%s$%s", iterations, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(dk)), nil } // Verify 校验口令,常量时间比较。 func Verify(password, encoded string) bool { parts := strings.Split(encoded, "$") if len(parts) != 4 || parts[0] != "pbkdf2-sha256" { return false } iter, err := strconv.Atoi(parts[1]) if err != nil || iter <= 0 { return false } salt, err := base64.RawStdEncoding.DecodeString(parts[2]) if err != nil { return false } want, err := base64.RawStdEncoding.DecodeString(parts[3]) if err != nil || len(want) == 0 { return false } got := pbkdf2Key([]byte(password), salt, iter, len(want)) return subtle.ConstantTimeCompare(got, want) == 1 } func pbkdf2Key(password, salt []byte, iter, keyLen int) []byte { prf := hmac.New(sha256.New, password) hashLen := prf.Size() numBlocks := (keyLen + hashLen - 1) / hashLen var buf [4]byte dk := make([]byte, 0, numBlocks*hashLen) u := make([]byte, hashLen) for block := 1; block <= numBlocks; block++ { prf.Reset() prf.Write(salt) buf[0] = byte(block >> 24) buf[1] = byte(block >> 16) buf[2] = byte(block >> 8) buf[3] = byte(block) prf.Write(buf[:4]) dk = prf.Sum(dk) t := dk[len(dk)-hashLen:] copy(u, t) for n := 2; n <= iter; n++ { prf.Reset() prf.Write(u) u = u[:0] u = prf.Sum(u) for x := range u { t[x] ^= u[x] } } } return dk[:keyLen] }