之前常用Python Flask框架写Web应用,涉及到用户密码这一点,一般是用werkzeug的generate_password_hash函数。
现在写一些新应用开始用Go了,但有时候两者间可能要进行用户密码统一认证,要求双方都能验证 对方生成的密码。
参照了网上的几个案例,虽然自身可以生成、验证,但换到对方反而不行,所以参考 pbkdf2 算法和 werkzeug 的实现函数,重新写了下。
需要注意的是,限定使用了 sha256 哈希算法。
package util
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"strconv"
"strings"
"golang.org/x/crypto/pbkdf2"
)
const SALT_LENGTH = 8
const SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const DEFAULT_PBKDF2_ITERATIONS = 150000
const DEFAULT_PBKDF2_METHOD = "pbkdf2:sha256"
func genSalt() string {
var bytes = make([]byte, SALT_LENGTH)
rand.Read(bytes)
for k, v := range bytes {
bytes[k] = SALT_CHARS[v%byte(len(SALT_CHARS))]
}
return string(bytes)
}
func rendeMethod(iter int) string {
return fmt.Sprintf("%s:%d", DEFAULT_PBKDF2_METHOD, iter)
}
func hashInternal(password, salt string, iter int) string {
t := pbkdf2.Key([]byte(password), []byte(salt), iter, 32, sha256.New)
pwh := hex.EncodeToString(t)
return fmt.Sprintf("%s$%s$%s", rendeMethod(iter), salt, pwh)
}
func GeneratePasswordHash(password string) string {
salt := genSalt()
return hashInternal(password, salt, DEFAULT_PBKDF2_ITERATIONS)
}
func CheckPasswordHash(pwhash string, password string) bool {
if strings.Count(pwhash, "$") < 2 {
return false
}
pws := strings.Split(pwhash, "$")
method := pws[0]
salt := pws[1]
if !strings.HasPrefix(method, DEFAULT_PBKDF2_METHOD) {
return false
}
if strings.Count(method, ":") < 2 {
return false
}
mds := strings.Split(method, ":")
iter, err := strconv.Atoi(mds[2])
if err != nil {
return false
}
return strings.EqualFold(pwhash, hashInternal(password, salt, iter))
}
测试用例(go):
package util
import (
"strings"
"testing"
)
func TestPasswd(t *testing.T) {
pwd := "Abc123@$-*&"
// pwhash 是下面python测试用例中的密码
pwhash := GeneratePasswordHash(pwd)
if !strings.HasPrefix(pwhash, "pbkdf2") {
t.Fatal("hash password format error")
}
if !CheckPasswordHash(pwhash, pwd) {
t.Fatal("CheckPasswordHash error (true)")
}
if CheckPasswordHash(pwhash, "ejaA5W4866") {
t.Fatal("CheckPasswordHash error (false)")
}
// 验证 werkzeug generate_password_hash 生成的密码
pypwhash := "pbkdf2:sha256:150000$XqnEnB37$cf2bffd0ccf0ae5d5c7c9486a6c3dc8f4add525f6faf9c4d85c91e98bb360b86"
if !CheckPasswordHash(pypwhash, "123456") {
t.Fatal("CheckPasswordHash error (werkzeug)")
}
}
测试用例(python):
from werkzeug.security import check_password_hash
check_password_hash("pbkdf2:sha256:150000$ufehHgNr$48915f0de52612c5df20946d30c2b7e2619896348ac7f5aaf07d59cbea1cfb18","Abc123@$-*&")