feat: password.
This commit is contained in:
14
password/README.md
Normal file
14
password/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 密码加解密
|
||||||
|
|
||||||
|
算法列表
|
||||||
|
|
||||||
|
| 算法名 | 特点 | 用途 |
|
||||||
|
|-----------------------------------------------|-------------------------------|------------------------|
|
||||||
|
| Bcrypt | 基于 Blowfish 算法,内置盐值,支持调整计算成本。 | 密码存储,防止暴力破解。 |
|
||||||
|
| Argon2 | 内存硬性哈希算法,支持多种参数调整(内存、时间、并行度)。 | 码存储,适合高安全性需求。 |
|
||||||
|
| PBKDF2 | 基于 HMAC 的密钥派生函数,支持多种哈希算法。 | 密码存储,兼容性好。 |
|
||||||
|
| SHA-256/SHA-512 | 快速单向哈希算法,无内置盐值。 | 数据完整性校验,需手动添加盐值用于密码存储。 |
|
||||||
|
| AES (Advanced Encryption Standard) | 对称加密算法,支持 128/192/256 位密钥。 | 数据加密传输或存储。 |
|
||||||
|
| RSA | 非对称加密算法,基于大整数分解难题。 | 数据加密、数字签名。 |
|
||||||
|
| ECDSA/ECDH (椭圆曲线算法) | 基于椭圆曲线的非对称加密,密钥更短但安全性高。 | 数据完整性和认证。 |
|
||||||
|
| HMAC (Hash-based Message Authentication Code) | 基于哈希算法的消息认证码。 | 数据完整性和认证。 |
|
||||||
111
password/argon2.go
Normal file
111
password/argon2.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Argon2Crypto 实现 Argon2id 密码哈希算法
|
||||||
|
type Argon2Crypto struct {
|
||||||
|
// 参数可配置,默认使用推荐值
|
||||||
|
Memory uint32
|
||||||
|
Iterations uint32
|
||||||
|
Parallelism uint8
|
||||||
|
SaltLength uint32
|
||||||
|
KeyLength uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArgon2Crypto 创建带默认参数的 Argon2 加密器
|
||||||
|
func NewArgon2Crypto() *Argon2Crypto {
|
||||||
|
return &Argon2Crypto{
|
||||||
|
Memory: 64 * 1024, // 64MB
|
||||||
|
Iterations: 3,
|
||||||
|
Parallelism: 2,
|
||||||
|
SaltLength: 16,
|
||||||
|
KeyLength: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密
|
||||||
|
func (a *Argon2Crypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值
|
||||||
|
salt := make([]byte, a.SaltLength)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成哈希
|
||||||
|
hash := argon2.IDKey(
|
||||||
|
[]byte(password),
|
||||||
|
salt,
|
||||||
|
a.Iterations,
|
||||||
|
a.Memory,
|
||||||
|
a.Parallelism,
|
||||||
|
a.KeyLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 格式化输出(兼容标准格式)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
||||||
|
argon2.Version,
|
||||||
|
a.Memory,
|
||||||
|
a.Iterations,
|
||||||
|
a.Parallelism,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
base64.RawStdEncoding.EncodeToString(hash),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (a *Argon2Crypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, "$")
|
||||||
|
if len(parts) != 6 {
|
||||||
|
return false, errors.New("无效的 Argon2 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
var version int
|
||||||
|
var memory, iterations uint32
|
||||||
|
var parallelism uint8
|
||||||
|
_, err := fmt.Sscanf(parts[2], "v=%d", &version)
|
||||||
|
if err != nil || version != argon2.Version {
|
||||||
|
return false, errors.New("不支持的 Argon2 版本")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.New("无效的 Argon2 参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码盐值和哈希
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
keyLength := uint32(len(decodedHash))
|
||||||
|
|
||||||
|
// 使用相同参数生成新哈希
|
||||||
|
newHash := argon2.IDKey(
|
||||||
|
[]byte(password),
|
||||||
|
salt,
|
||||||
|
iterations,
|
||||||
|
memory,
|
||||||
|
parallelism,
|
||||||
|
keyLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return subtle.ConstantTimeCompare(newHash, decodedHash) == 1, nil
|
||||||
|
}
|
||||||
41
password/argon2_test.go
Normal file
41
password/argon2_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArgon2Crypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewArgon2Crypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
34
password/bcrypt.go
Normal file
34
password/bcrypt.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BCryptCrypto struct{}
|
||||||
|
|
||||||
|
func NewBCryptCrypto() *BCryptCrypto {
|
||||||
|
return &BCryptCrypto{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 bcrypt 加密密码,返回加密后的字符串和空盐值
|
||||||
|
func (b *BCryptCrypto) Encrypt(password string) (encrypted string, err error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码是否匹配加密后的字符串
|
||||||
|
func (b *BCryptCrypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(encrypted), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
41
password/bcrypt_test.go
Normal file
41
password/bcrypt_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBCryptCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewBCryptCrypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
142
password/ecdsa_ecdh.go
Normal file
142
password/ecdsa_ecdh.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ECDSACrypto 实现基于 ECDSA 的加密和验证
|
||||||
|
type ECDSACrypto struct {
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDSACrypto 创建一个新的 ECDSACrypto 实例
|
||||||
|
func NewECDSACrypto() (*ECDSACrypto, error) {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ECDSACrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 ECDSA 对消息进行签名
|
||||||
|
func (e *ECDSACrypto) Encrypt(plainPassword string) (string, error) {
|
||||||
|
if plainPassword == "" {
|
||||||
|
return "", errors.New("密码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, e.privateKey, hash[:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := r.String() + "$" + s.String()
|
||||||
|
return "ecdsa$" + signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证消息的签名是否有效
|
||||||
|
func (e *ECDSACrypto) Verify(plainPassword, encrypted string) (bool, error) {
|
||||||
|
if plainPassword == "" || encrypted == "" {
|
||||||
|
return false, errors.New("密码或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(encrypted, "$", 3)
|
||||||
|
if len(parts) != 3 || parts[0] != "ecdsa" {
|
||||||
|
return false, errors.New("加密字符串格式无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(big.Int)
|
||||||
|
s := new(big.Int)
|
||||||
|
r.SetString(parts[1], 10)
|
||||||
|
s.SetString(parts[2], 10)
|
||||||
|
|
||||||
|
hash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
return ecdsa.Verify(e.publicKey, hash[:], r, s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDHCrypto 实现基于 ECDH 的密钥交换
|
||||||
|
type ECDHCrypto struct {
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDHCrypto 创建一个新的 ECDHCrypto 实例
|
||||||
|
func NewECDHCrypto() (*ECDHCrypto, error) {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ECDHCrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 返回公钥作为加密结果
|
||||||
|
func (e *ECDHCrypto) Encrypt(plainPassword string) (string, error) {
|
||||||
|
if plainPassword == "" {
|
||||||
|
return "", errors.New("密码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes := elliptic.Marshal(e.privateKey.Curve, e.publicKey.X, e.publicKey.Y)
|
||||||
|
return "ecdh$" + base64.StdEncoding.EncodeToString(publicKeyBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证共享密钥是否一致
|
||||||
|
func (e *ECDHCrypto) Verify(plainPassword, encrypted string) (bool, error) {
|
||||||
|
if plainPassword == "" || encrypted == "" {
|
||||||
|
return false, errors.New("密码或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(encrypted, "$", 2)
|
||||||
|
if len(parts) != 2 || parts[0] != "ecdh" {
|
||||||
|
return false, errors.New("加密字符串格式无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return false, errors.New("无效的公钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
|
||||||
|
expectedHash := sha256.Sum256(sharedX.Bytes())
|
||||||
|
actualHash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
|
||||||
|
return expectedHash == actualHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ECDHCrypto) DeriveSharedSecret(publicKey string) ([]byte, error) {
|
||||||
|
// 解码对方的公钥
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化公钥
|
||||||
|
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return nil, errors.New("无效的公钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算共享密钥
|
||||||
|
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
|
||||||
|
|
||||||
|
// 返回共享密钥的字节表示
|
||||||
|
return sharedX.Bytes(), nil
|
||||||
|
}
|
||||||
60
password/ecdsa_ecdh_test.go
Normal file
60
password/ecdsa_ecdh_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestECDSACrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto, err := NewECDSACrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "test message"
|
||||||
|
|
||||||
|
// 签名消息
|
||||||
|
encrypted, err := crypto.Encrypt(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("签名失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
isValid, err := crypto.Verify(message, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("签名验证未通过")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDHCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto1, err := NewECDHCrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDHCrypto 实例1失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto2, err := NewECDHCrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDHCrypto 实例2失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "test message"
|
||||||
|
|
||||||
|
// 获取公钥
|
||||||
|
encrypted, err := crypto1.Encrypt(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证共享密钥
|
||||||
|
isValid, err := crypto2.Verify(message, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("共享密钥验证未通过")
|
||||||
|
}
|
||||||
|
}
|
||||||
11
password/go.mod
Normal file
11
password/go.mod
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module github.com/tx7do/go-utils/password
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
|
require golang.org/x/crypto v0.39.0
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.33.0 // indirect
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
6
password/go.sum
Normal file
6
password/go.sum
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
50
password/hmac.go
Normal file
50
password/hmac.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HMACCrypto 实现基于 HMAC 的加密和验证
|
||||||
|
type HMACCrypto struct {
|
||||||
|
secretKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHMACCrypto 创建一个新的 HMACCrypto 实例
|
||||||
|
func NewHMACCrypto(secretKey string) *HMACCrypto {
|
||||||
|
return &HMACCrypto{
|
||||||
|
secretKey: []byte(secretKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 HMAC-SHA256 对数据进行加密
|
||||||
|
func (h *HMACCrypto) Encrypt(data string) (string, error) {
|
||||||
|
if data == "" {
|
||||||
|
return "", errors.New("数据不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, h.secretKey)
|
||||||
|
_, err := mac.Write([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := mac.Sum(nil)
|
||||||
|
return hex.EncodeToString(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证数据的 HMAC 值是否匹配
|
||||||
|
func (h *HMACCrypto) Verify(data, encrypted string) (bool, error) {
|
||||||
|
if data == "" || encrypted == "" {
|
||||||
|
return false, errors.New("数据或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHash, err := h.Encrypt(data)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hmac.Equal([]byte(expectedHash), []byte(encrypted)), nil
|
||||||
|
}
|
||||||
43
password/hmac_test.go
Normal file
43
password/hmac_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHMACCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
secretKey := "mysecretkey"
|
||||||
|
crypto := NewHMACCrypto(secretKey)
|
||||||
|
|
||||||
|
data := "testdata"
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
encrypted, err := crypto.Encrypt(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证
|
||||||
|
isValid, err := crypto.Verify(data, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败的情况
|
||||||
|
isValid, err = crypto.Verify("wrongdata", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证结果错误,预期验证失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
29
password/interface.go
Normal file
29
password/interface.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Crypto 密码加解密接口
|
||||||
|
type Crypto interface {
|
||||||
|
// Encrypt 加密密码,返回加密后的字符串(包含算法标识和盐值)
|
||||||
|
Encrypt(plainPassword string) (encrypted string, err error)
|
||||||
|
|
||||||
|
// Verify 验证密码是否匹配
|
||||||
|
Verify(plainPassword, encrypted string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateCrypto(algorithm string) (Crypto, error) {
|
||||||
|
algorithm = strings.ToLower(algorithm)
|
||||||
|
switch algorithm {
|
||||||
|
case "bcrypt":
|
||||||
|
return NewBCryptCrypto(), nil
|
||||||
|
case "pbkdf2":
|
||||||
|
return NewPBKDF2Crypto(), nil
|
||||||
|
case "argon2":
|
||||||
|
return NewArgon2Crypto(), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("不支持的加密算法")
|
||||||
|
}
|
||||||
|
}
|
||||||
150
password/pbkdf2.go
Normal file
150
password/pbkdf2.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PBKDF2Crypto 实现 PBKDF2-HMAC 密码哈希算法
|
||||||
|
type PBKDF2Crypto struct {
|
||||||
|
// 可配置参数,默认使用推荐值
|
||||||
|
Iterations int
|
||||||
|
KeyLength int
|
||||||
|
Hash func() hash.Hash
|
||||||
|
HashName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPBKDF2Crypto 创建带默认参数的 PBKDF2 加密器 (SHA256)
|
||||||
|
func NewPBKDF2Crypto() *PBKDF2Crypto {
|
||||||
|
return &PBKDF2Crypto{
|
||||||
|
Iterations: 310000, // NIST 推荐最小值
|
||||||
|
KeyLength: 32, // 256-bit
|
||||||
|
Hash: sha256.New,
|
||||||
|
HashName: "sha256",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPBKDF2WithSHA512 创建使用 SHA512 的 PBKDF2 加密器
|
||||||
|
func NewPBKDF2WithSHA512() *PBKDF2Crypto {
|
||||||
|
return &PBKDF2Crypto{
|
||||||
|
Iterations: 600000, // SHA512 需要更多迭代
|
||||||
|
KeyLength: 64, // 512-bit
|
||||||
|
Hash: sha512.New,
|
||||||
|
HashName: "sha512",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密
|
||||||
|
func (p *PBKDF2Crypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值 (16 bytes 推荐最小值)
|
||||||
|
salt := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成密钥
|
||||||
|
key := pbkdf2Key([]byte(password), salt, p.Iterations, p.KeyLength, p.Hash)
|
||||||
|
|
||||||
|
// 格式: pbkdf2:<hash>:<iterations>:<base64-salt>:<base64-key>
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"pbkdf2:%s:%d:%s:%s",
|
||||||
|
p.HashName,
|
||||||
|
p.Iterations,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
base64.RawStdEncoding.EncodeToString(key),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (p *PBKDF2Crypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, ":")
|
||||||
|
if len(parts) != 5 || parts[0] != "pbkdf2" {
|
||||||
|
return false, errors.New("无效的 PBKDF2 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
hashName := parts[1]
|
||||||
|
iterations, err := strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.New("无效的迭代次数")
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedKey, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据哈希名称选择哈希函数
|
||||||
|
hashFunc, ok := getHashFunction(hashName)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("不支持的哈希算法: %s", hashName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新密钥
|
||||||
|
keyLength := len(expectedKey)
|
||||||
|
newKey := pbkdf2Key([]byte(password), salt, iterations, keyLength, hashFunc)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return hmac.Equal(newKey, expectedKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pbkdf2Key 实现 PBKDF2 核心算法
|
||||||
|
func pbkdf2Key(password, salt []byte, iterations, keyLength int, hashFunc func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(hashFunc, password)
|
||||||
|
hashLength := prf.Size()
|
||||||
|
blockCount := (keyLength + hashLength - 1) / hashLength
|
||||||
|
|
||||||
|
output := make([]byte, 0, blockCount*hashLength)
|
||||||
|
for i := 1; i <= blockCount; i++ {
|
||||||
|
// U1 = PRF(password, salt || INT(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
binary.BigEndian.PutUint32(make([]byte, 4), uint32(i))
|
||||||
|
prf.Write([]byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)})
|
||||||
|
u := prf.Sum(nil)
|
||||||
|
|
||||||
|
// F = U1 ⊕ U2 ⊕ ... ⊕ U_iterations
|
||||||
|
f := make([]byte, len(u))
|
||||||
|
copy(f, u)
|
||||||
|
|
||||||
|
for j := 1; j < iterations; j++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(u)
|
||||||
|
u = prf.Sum(nil)
|
||||||
|
for k := 0; k < len(f); k++ {
|
||||||
|
f[k] ^= u[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output = append(output, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output[:keyLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHashFunction 根据名称获取哈希函数
|
||||||
|
func getHashFunction(name string) (func() hash.Hash, bool) {
|
||||||
|
switch name {
|
||||||
|
case "sha256":
|
||||||
|
return sha256.New, true
|
||||||
|
case "sha512":
|
||||||
|
return sha512.New, true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
77
password/pbkdf2_test.go
Normal file
77
password/pbkdf2_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPBKDF2Crypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewPBKDF2Crypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPBKDF2WithSHA512_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewPBKDF2WithSHA512()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
73
password/rsa.go
Normal file
73
password/rsa.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RSACrypto 实现 RSA 加密和解密
|
||||||
|
type RSACrypto struct {
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSACrypto 创建一个新的 RSACrypto 实例
|
||||||
|
func NewRSACrypto(keySize int) (*RSACrypto, error) {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &RSACrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用公钥加密数据
|
||||||
|
func (r *RSACrypto) Encrypt(data string) (string, error) {
|
||||||
|
encryptedBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, r.publicKey, []byte(data), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(encryptedBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt 使用私钥解密数据
|
||||||
|
func (r *RSACrypto) Decrypt(encryptedData string) (string, error) {
|
||||||
|
decodedData, err := base64.StdEncoding.DecodeString(encryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
decryptedBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, r.privateKey, decodedData, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(decryptedBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPrivateKey 导出私钥为 PEM 格式
|
||||||
|
func (r *RSACrypto) ExportPrivateKey() (string, error) {
|
||||||
|
privateKeyBytes := x509.MarshalPKCS1PrivateKey(r.privateKey)
|
||||||
|
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: privateKeyBytes,
|
||||||
|
})
|
||||||
|
return string(privateKeyPEM), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPublicKey 导出公钥为 PEM 格式
|
||||||
|
func (r *RSACrypto) ExportPublicKey() (string, error) {
|
||||||
|
publicKeyBytes, err := x509.MarshalPKIXPublicKey(r.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PUBLIC KEY",
|
||||||
|
Bytes: publicKeyBytes,
|
||||||
|
})
|
||||||
|
return string(publicKeyPEM), nil
|
||||||
|
}
|
||||||
63
password/rsa_test.go
Normal file
63
password/rsa_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRSACrypto_EncryptAndDecrypt(t *testing.T) {
|
||||||
|
// 创建 RSACrypto 实例
|
||||||
|
crypto, err := NewRSACrypto(2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
originalData := "securedata"
|
||||||
|
encryptedData, err := crypto.Encrypt(originalData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encryptedData == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encryptedData)
|
||||||
|
|
||||||
|
// 测试解密
|
||||||
|
decryptedData, err := crypto.Decrypt(encryptedData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("解密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if decryptedData != originalData {
|
||||||
|
t.Fatalf("解密结果不匹配,期望: %s,实际: %s", originalData, decryptedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRSACrypto_ExportKeys(t *testing.T) {
|
||||||
|
// 创建 RSACrypto 实例
|
||||||
|
crypto, err := NewRSACrypto(2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试导出私钥
|
||||||
|
privateKey, err := crypto.ExportPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("导出私钥失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey == "" {
|
||||||
|
t.Fatal("导出的私钥为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试导出公钥
|
||||||
|
publicKey, err := crypto.ExportPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("导出公钥失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicKey == "" {
|
||||||
|
t.Fatal("导出的公钥为空")
|
||||||
|
}
|
||||||
|
}
|
||||||
110
password/sha.go
Normal file
110
password/sha.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SHACrypto 实现 SHA-256/SHA-512 密码哈希算法(带盐值)
|
||||||
|
type SHACrypto struct {
|
||||||
|
Hash func() hash.Hash
|
||||||
|
HashName string
|
||||||
|
SaltLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA256Crypto 创建 SHA-256 加密器
|
||||||
|
func NewSHA256Crypto() *SHACrypto {
|
||||||
|
return &SHACrypto{
|
||||||
|
Hash: sha256.New,
|
||||||
|
HashName: "sha256",
|
||||||
|
SaltLength: 16, // 16 字节盐值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA512Crypto 创建 SHA-512 加密器
|
||||||
|
func NewSHA512Crypto() *SHACrypto {
|
||||||
|
return &SHACrypto{
|
||||||
|
Hash: sha512.New,
|
||||||
|
HashName: "sha512",
|
||||||
|
SaltLength: 16, // 16 字节盐值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密(带盐值)
|
||||||
|
func (s *SHACrypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值
|
||||||
|
salt := make([]byte, s.SaltLength)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算哈希
|
||||||
|
hashValue := s.Hash()
|
||||||
|
hashValue.Write(salt)
|
||||||
|
hashValue.Write([]byte(password))
|
||||||
|
hashBytes := hashValue.Sum(nil)
|
||||||
|
|
||||||
|
// 格式: sha256:$salt$hash 或 sha512:$salt$hash
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s$%s$%s",
|
||||||
|
s.HashName,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
hex.EncodeToString(hashBytes),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (s *SHACrypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, "$")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return false, errors.New("无效的 SHA 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashName := parts[0]
|
||||||
|
if hashName != s.HashName {
|
||||||
|
return false, fmt.Errorf("哈希算法不匹配: 期望 %s, 实际 %s", s.HashName, hashName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码盐值
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码原始哈希值
|
||||||
|
originalHash, err := hex.DecodeString(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算新哈希
|
||||||
|
hashValue := s.Hash()
|
||||||
|
hashValue.Write(salt)
|
||||||
|
hashValue.Write([]byte(password))
|
||||||
|
newHash := hashValue.Sum(nil)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return compareHash(newHash, originalHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareHash 安全比较两个哈希值
|
||||||
|
func compareHash(h1, h2 []byte) bool {
|
||||||
|
if len(h1) != len(h2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
result := 0
|
||||||
|
for i := 0; i < len(h1); i++ {
|
||||||
|
result |= int(h1[i] ^ h2[i])
|
||||||
|
}
|
||||||
|
return result == 0
|
||||||
|
}
|
||||||
53
password/sha_test.go
Normal file
53
password/sha_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSHACrypto_EncryptAndVerify_SHA256(t *testing.T) {
|
||||||
|
crypto := NewSHA256Crypto()
|
||||||
|
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSHACrypto_EncryptAndVerify_SHA512(t *testing.T) {
|
||||||
|
crypto := NewSHA512Crypto()
|
||||||
|
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
1
tag.bat
1
tag.bat
@@ -9,6 +9,7 @@ git tag id/v0.0.2
|
|||||||
git tag slug/v0.0.1
|
git tag slug/v0.0.1
|
||||||
git tag name_generator/v0.0.1
|
git tag name_generator/v0.0.1
|
||||||
git tag mapper/v0.0.1
|
git tag mapper/v0.0.1
|
||||||
|
git tag password/v0.0.1
|
||||||
|
|
||||||
git tag entgo/v1.1.31
|
git tag entgo/v1.1.31
|
||||||
git tag gorm/v1.1.6
|
git tag gorm/v1.1.6
|
||||||
|
|||||||
Reference in New Issue
Block a user