Files
NetworkAuth/utils/crypto.go

347 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"sync"
"github.com/spf13/viper"
"golang.org/x/crypto/bcrypt"
)
// ============================================================================
// 结构体定义
// ============================================================================
// CryptoManager 加密管理器,提供高性能的加密解密服务
type CryptoManager struct {
key []byte
gcm cipher.AEAD
mutex sync.RWMutex
inited bool
}
// ============================================================================
// 全局变量
// ============================================================================
// 全局加密管理器实例
var cryptoManager = &CryptoManager{}
// ============================================================================
// 私有函数
// ============================================================================
// initCrypto 初始化加密管理器
// 缓存密钥和GCM实例避免重复创建
func (cm *CryptoManager) initCrypto() error {
cm.mutex.Lock()
defer cm.mutex.Unlock()
if cm.inited {
return nil
}
// 从配置中获取密钥
secret := viper.GetString("encryption_key")
if secret == "" {
secret = "default-secret"
}
// 生成AES密钥
sum := sha256.Sum256([]byte(secret))
cm.key = sum[:]
// 创建AES cipher
block, err := aes.NewCipher(cm.key)
if err != nil {
return err
}
// 创建GCM
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
cm.gcm = gcm
cm.inited = true
return nil
}
// ============================================================================
// 加密解密函数
// ============================================================================
// EncryptString 字符串加密AES-256-GCM
// 使用缓存的密钥和GCM实例提高性能
func EncryptString(plain string) (string, error) {
if err := cryptoManager.initCrypto(); err != nil {
return "", err
}
cryptoManager.mutex.RLock()
gcm := cryptoManager.gcm
cryptoManager.mutex.RUnlock()
// 生成随机nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// 加密
ciphertext := gcm.Seal(nil, nonce, []byte(plain), nil)
buf := append(nonce, ciphertext...)
return base64.StdEncoding.EncodeToString(buf), nil
}
// DecryptString 字符串解密AES-256-GCM
// 使用缓存的密钥和GCM实例提高性能
func DecryptString(enc string) (string, error) {
if err := cryptoManager.initCrypto(); err != nil {
return "", err
}
cryptoManager.mutex.RLock()
gcm := cryptoManager.gcm
cryptoManager.mutex.RUnlock()
// 解码base64
data, err := base64.StdEncoding.DecodeString(enc)
if err != nil {
return "", err
}
// 检查数据长度
if len(data) < gcm.NonceSize() {
return "", errors.New("ciphertext too short")
}
// 分离nonce和密文
nonce := data[:gcm.NonceSize()]
ciphertext := data[gcm.NonceSize():]
// 解密
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plain), nil
}
// EncryptStringBatch 批量加密字符串
// 减少锁竞争,提高批量处理性能
func EncryptStringBatch(plains []string) ([]string, error) {
if err := cryptoManager.initCrypto(); err != nil {
return nil, err
}
cryptoManager.mutex.RLock()
gcm := cryptoManager.gcm
cryptoManager.mutex.RUnlock()
results := make([]string, len(plains))
for i, plain := range plains {
// 生成随机nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// 加密
ciphertext := gcm.Seal(nil, nonce, []byte(plain), nil)
buf := append(nonce, ciphertext...)
results[i] = base64.StdEncoding.EncodeToString(buf)
}
return results, nil
}
// DecryptStringBatch 批量解密字符串
// 减少锁竞争,提高批量处理性能
func DecryptStringBatch(encs []string) ([]string, error) {
if err := cryptoManager.initCrypto(); err != nil {
return nil, err
}
cryptoManager.mutex.RLock()
gcm := cryptoManager.gcm
cryptoManager.mutex.RUnlock()
results := make([]string, len(encs))
for i, enc := range encs {
// 解码base64
data, err := base64.StdEncoding.DecodeString(enc)
if err != nil {
return nil, err
}
// 检查数据长度
if len(data) < gcm.NonceSize() {
return nil, errors.New("ciphertext too short")
}
// 分离nonce和密文
nonce := data[:gcm.NonceSize()]
ciphertext := data[gcm.NonceSize():]
// 解密
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
results[i] = string(plain)
}
return results, nil
}
// GenerateRandomSalt 生成随机密码盐值
// 生成32字节64个十六进制字符的随机盐值用于加密
// 返回: 十六进制格式的盐值字符串和错误信息
func GenerateRandomSalt() (string, error) {
length := 32 // 固定32字节
// 生成随机字节
bytes := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
return "", err
}
// 转换为十六进制字符串
return fmt.Sprintf("%x", bytes), nil
}
// EncryptStringWithSalt 使用盐值进行字符串加密AES-256-GCM
// 将明文和盐值组合后进行加密,增强安全性
// plain: 待加密的明文字符串
// salt: 加密盐值
// 返回: base64编码的密文字符串和错误信息
func EncryptStringWithSalt(plain, salt string) (string, error) {
if err := cryptoManager.initCrypto(); err != nil {
return "", err
}
cryptoManager.mutex.RLock()
gcm := cryptoManager.gcm
cryptoManager.mutex.RUnlock()
// 将明文和盐值组合
combined := plain + salt
// 生成随机nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// 加密
ciphertext := gcm.Seal(nil, nonce, []byte(combined), nil)
buf := append(nonce, ciphertext...)
return base64.StdEncoding.EncodeToString(buf), nil
}
// DecryptStringWithSalt 使用盐值进行字符串解密AES-256-GCM
// 解密密文并移除盐值,返回原始明文
// enc: base64编码的密文字符串
// salt: 解密盐值
// 返回: 解密后的明文字符串和错误信息
func DecryptStringWithSalt(enc, salt string) (string, error) {
if err := cryptoManager.initCrypto(); err != nil {
return "", err
}
cryptoManager.mutex.RLock()
gcm := cryptoManager.gcm
cryptoManager.mutex.RUnlock()
// 解码base64
data, err := base64.StdEncoding.DecodeString(enc)
if err != nil {
return "", err
}
// 检查数据长度
if len(data) < gcm.NonceSize() {
return "", errors.New("ciphertext too short")
}
// 分离nonce和密文
nonce := data[:gcm.NonceSize()]
ciphertext := data[gcm.NonceSize():]
// 解密
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
// 移除盐值,返回原始明文
combined := string(plain)
if len(combined) < len(salt) {
return "", errors.New("decrypted data too short")
}
// 验证盐值是否匹配
if combined[len(combined)-len(salt):] != salt {
return "", errors.New("salt mismatch")
}
return combined[:len(combined)-len(salt)], nil
}
// HashPasswordWithSalt 使用盐值对密码进行哈希处理
// 将密码和盐值组合后先用SHA256处理再使用bcrypt进行哈希
// 这样可以避免bcrypt的72字节限制问题
// password: 原始密码
// salt: 密码盐值
// 返回: bcrypt哈希值和错误信息
func HashPasswordWithSalt(password, salt string) (string, error) {
// 将密码和盐值组合
combined := password + salt
// 先使用SHA256处理组合后的字符串确保长度固定且不超过bcrypt限制
hash := sha256.Sum256([]byte(combined))
sha256Hash := fmt.Sprintf("%x", hash) // 64字节的十六进制字符串
// 使用bcrypt进行哈希成本因子10平衡安全性和性能
hashed, err := bcrypt.GenerateFromPassword([]byte(sha256Hash), 10)
if err != nil {
return "", err
}
return string(hashed), nil
}
// VerifyPasswordWithSalt 验证密码和盐值的组合是否匹配哈希值
// password: 原始密码
// salt: 密码盐值
// hashedPassword: 存储的哈希密码
// 返回: 验证结果true表示匹配
func VerifyPasswordWithSalt(password, salt, hashedPassword string) bool {
// 将密码和盐值组合
combined := password + salt
// 先使用SHA256处理组合后的字符串与哈希生成逻辑保持一致
hash := sha256.Sum256([]byte(combined))
sha256Hash := fmt.Sprintf("%x", hash) // 64字节的十六进制字符串
// 使用bcrypt验证
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(sha256Hash))
return err == nil
}
// GenerateSHA256Hash 生成字符串的SHA256哈希值
// 用于生成密码哈希摘要用于JWT验证
func GenerateSHA256Hash(input string) string {
hash := sha256.Sum256([]byte(input))
return fmt.Sprintf("%x", hash)
}