前端使用CryptoJS默認加密方法:
var pass = CryptoJS.AES.encrypt(formData.password, key.value).toString()
使用 CryptoJS.AES.encrypt() 時不指定加密模式和參數時,CryptoJS 默認會執行以下操作
var encrypted = CryptoJS.AES.encrypt("明文", "密鑰", {format: CryptoJS.format.OpenSSL // 關鍵!默認使用 OpenSSL 兼容格式}
).toString()
默認生成的加密數據包含:
?頭部標識?:Salted__(8字節)
?隨機鹽值?:(8字節)
?實際加密數據?:密文數據(長度根據明文變化)
封裝解密函數,go語言環境當中自帶以下依賴,可以直接使用
package handlersimport ("crypto/aes""crypto/cipher""crypto/md5""encoding/base64""errors"
)// AesDecryptCBCOpenSSL 解密經過CryptoJS(AES-CBC模式)加密的字符串
// 參數:
// - cipherText: Base64編碼的加密字符串(包含OpenSSL格式的salt頭)
// - key: 用戶提供的原始密鑰
// 返回值:
// - 解密后的原始字符串
// - 錯誤信息(解密失敗時)
func AesDecryptCBCOpenSSL(cipherText, key string) (string, error) {// 步驟1:Base64解碼cipherBytes, err := base64.StdEncoding.DecodeString(cipherText)if err != nil {return "", err}// 步驟2:驗證OpenSSL頭部格式(CryptoJS默認生成的格式)// 前8字節應為 "Salted__" 后跟8字節的隨機鹽值if len(cipherBytes) < 16 || string(cipherBytes[:8]) != "Salted__" {return "", errors.New("invalid OpenSSL salted encryption format")}// 步驟3:提取鹽值(第9-16字節)salt := cipherBytes[8:16]// 步驟4:通過EVP KDF生成實際的加密密鑰和IVkeyAndIV := evpKDF([]byte(key), salt, 32, 16) // 生成32字節密鑰+16字節IV// 分割密鑰和初始化向量keyBytes := keyAndIV[:32]ivBytes := keyAndIV[32:]// 步驟5:獲取真正的加密數據(移除頭部和鹽值)data := cipherBytes[16:]// 步驟6:創建AES-CBC解密器block, err := aes.NewCipher(keyBytes)if err != nil {return "", err}// 步驟7:執行解密(會原地修改data的內容)mode := cipher.NewCBCDecrypter(block, ivBytes)mode.CryptBlocks(data, data)// 步驟8:移除PKCS#7填充data = pkcs7Unpad(data, block.BlockSize())return string(data), nil
}// evpKDF 實現OpenSSL的EVP_BytesToKey密鑰派生函數
// 參數:
// - password: 用戶提供的原始密鑰
// - salt: 隨機鹽值
// - keyLen: 需要的密鑰長度(單位字節)
// - ivLen: 需要的IV長度(單位字節)
// 返回值:
// - 派生后的密鑰和IV的字節組合
func evpKDF(password, salt []byte, keyLen, ivLen int) []byte {digest := md5.New()var prev []bytevar result []byte// 循環直到生成足夠的密鑰材料for len(result) < keyLen+ivLen {digest.Reset()digest.Write(prev) // 寫入前次結果digest.Write(password) // 寫入密碼digest.Write(salt) // 寫入鹽值prev = digest.Sum(nil) // 計算MD5值result = append(result, prev...) // 累積結果}// 返回組合后的密鑰和IVreturn result[:keyLen+ivLen]
}// pkcs7Unpad 移除PKCS#7填充
// 參數:
// - data: 解密后的字節數組(含填充)
// - blockSize: 塊大小(AES為16字節)
// 返回值:
// - 移除填充后的原始數據
func pkcs7Unpad(data []byte, blockSize int) []byte {if len(data) == 0 {return nil}// 獲取最后一個字節的填充值padLen := int(data[len(data)-1])// 驗證填充有效性if padLen > len(data) || padLen > blockSize {// 無效填充直接返回原數據(可能需要處理錯誤)return data}// 移除填充return data[:len(data)-padLen]
}
go語言實現加密和解密:
package mainimport ("crypto/aes""crypto/cipher""crypto/rand""encoding/hex""errors""fmt""io"
)// AES-GCM 加密
func AesEncryptGCM(plaintext []byte, key []byte) ([]byte, error) {block, err := aes.NewCipher(key)if err != nil {return nil, err}gcm, err := cipher.NewGCM(block)if err != nil {return nil, err}nonce := make([]byte, gcm.NonceSize())if _, err := io.ReadFull(rand.Reader, nonce); err != nil {return nil, err}return gcm.Seal(nonce, nonce, plaintext, nil), nil
}// AES-GCM 解密
func AesDecryptGCM(ciphertext []byte, key []byte) ([]byte, error) {block, err := aes.NewCipher(key)if err != nil {return nil, err}gcm, err := cipher.NewGCM(block)if err != nil {return nil, err}nonceSize := gcm.NonceSize()if len(ciphertext) < nonceSize {return nil, errors.New("invalid ciphertext")}nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]return gcm.Open(nil, nonce, ciphertext, nil)
}func main() {key := []byte("0123456789abcdef0123456789abcdef") // 32 字節 = AES-256plaintext := []byte("SensitiveData需要加密的內容")// 加密ciphertext, _ := AesEncryptGCM(plaintext, key)fmt.Printf("加密后的 HEX: %x\n", ciphertext)//如果需要把加密后的字符串保存到數據庫當中需要轉為Base64 編碼encoded := base64.StdEncoding.EncodeToString(ciphertext)user.Password = encoded // 存儲安全編碼后的字符串// 解密decode, _ := base64.StdEncoding.DecodeString(user.Password)decrypted, _ := AesDecryptGCM(decode, key)fmt.Printf("解密結果: %s\n", decrypted)
}