feat: password.

This commit is contained in:
Bobo
2025-06-14 23:28:15 +08:00
parent 3ca4745bac
commit be5aa063df
19 changed files with 1109 additions and 0 deletions

14
password/README.md Normal file
View 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
View 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, &parallelism)
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
View 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
View 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
View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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("验证结果不匹配")
}
}

View File

@@ -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