diff --git a/password/README.md b/password/README.md new file mode 100644 index 0000000..977f593 --- /dev/null +++ b/password/README.md @@ -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) | 基于哈希算法的消息认证码。 | 数据完整性和认证。 | diff --git a/password/argon2.go b/password/argon2.go new file mode 100644 index 0000000..de26a40 --- /dev/null +++ b/password/argon2.go @@ -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 +} diff --git a/password/argon2_test.go b/password/argon2_test.go new file mode 100644 index 0000000..523b0e9 --- /dev/null +++ b/password/argon2_test.go @@ -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("验证通过,但密码不应匹配") + } +} diff --git a/password/bcrypt.go b/password/bcrypt.go new file mode 100644 index 0000000..fc321d4 --- /dev/null +++ b/password/bcrypt.go @@ -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 +} diff --git a/password/bcrypt_test.go b/password/bcrypt_test.go new file mode 100644 index 0000000..b0e1439 --- /dev/null +++ b/password/bcrypt_test.go @@ -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("验证通过,但密码不应匹配") + } +} diff --git a/password/ecdsa_ecdh.go b/password/ecdsa_ecdh.go new file mode 100644 index 0000000..3496457 --- /dev/null +++ b/password/ecdsa_ecdh.go @@ -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 +} diff --git a/password/ecdsa_ecdh_test.go b/password/ecdsa_ecdh_test.go new file mode 100644 index 0000000..bfe6bcb --- /dev/null +++ b/password/ecdsa_ecdh_test.go @@ -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("共享密钥验证未通过") + } +} diff --git a/password/go.mod b/password/go.mod new file mode 100644 index 0000000..9173772 --- /dev/null +++ b/password/go.mod @@ -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 => ../ diff --git a/password/go.sum b/password/go.sum new file mode 100644 index 0000000..c0d8114 --- /dev/null +++ b/password/go.sum @@ -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= diff --git a/password/hmac.go b/password/hmac.go new file mode 100644 index 0000000..6e1cf02 --- /dev/null +++ b/password/hmac.go @@ -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 +} diff --git a/password/hmac_test.go b/password/hmac_test.go new file mode 100644 index 0000000..6e11e3d --- /dev/null +++ b/password/hmac_test.go @@ -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("验证结果错误,预期验证失败") + } +} diff --git a/password/interface.go b/password/interface.go new file mode 100644 index 0000000..6ec831c --- /dev/null +++ b/password/interface.go @@ -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("不支持的加密算法") + } +} diff --git a/password/pbkdf2.go b/password/pbkdf2.go new file mode 100644 index 0000000..4fb8eb3 --- /dev/null +++ b/password/pbkdf2.go @@ -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:::: + 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 + } +} diff --git a/password/pbkdf2_test.go b/password/pbkdf2_test.go new file mode 100644 index 0000000..c9ab112 --- /dev/null +++ b/password/pbkdf2_test.go @@ -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("验证通过,但密码不应匹配") + } +} diff --git a/password/rsa.go b/password/rsa.go new file mode 100644 index 0000000..b6f2b61 --- /dev/null +++ b/password/rsa.go @@ -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 +} diff --git a/password/rsa_test.go b/password/rsa_test.go new file mode 100644 index 0000000..d11a749 --- /dev/null +++ b/password/rsa_test.go @@ -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("导出的公钥为空") + } +} diff --git a/password/sha.go b/password/sha.go new file mode 100644 index 0000000..759f7eb --- /dev/null +++ b/password/sha.go @@ -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 +} diff --git a/password/sha_test.go b/password/sha_test.go new file mode 100644 index 0000000..324d938 --- /dev/null +++ b/password/sha_test.go @@ -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("验证结果不匹配") + } +} diff --git a/tag.bat b/tag.bat index 0707150..a4b5546 100644 --- a/tag.bat +++ b/tag.bat @@ -9,6 +9,7 @@ git tag id/v0.0.2 git tag slug/v0.0.1 git tag name_generator/v0.0.1 git tag mapper/v0.0.1 +git tag password/v0.0.1 git tag entgo/v1.1.31 git tag gorm/v1.1.6