使用Go解密SubtleCrypto AES-GCM加密字符串遇认证失败问题
解决Go解密浏览器SubtleCrypto AES-GCM时的"cipher: message authentication failed"问题
最可能的问题出在编码转换、数据拼接的细节差异上,以下是逐点排查和修正方案:
1. 密钥生成一致性检查
前端用SHA-256哈希密码生成AES密钥,需确保Go端密钥字节与前端完全一致:
- 前端正确实现:
const password = "your-pass"; const encoder = new TextEncoder(); const hash = await crypto.subtle.digest("SHA-256", encoder.encode(password)); // 直接将SHA-256哈希结果作为raw格式导入AES密钥 const key = await crypto.subtle.importKey("raw", hash, {name: "AES-GCM"}, false, ["encrypt"]); - Go端对应实现:
password := "your-pass" hash := sha256.Sum256([]byte(password)) key := hash[:] // 直接用SHA-256哈希结果作为AES密钥 - 验证方式:把两端的哈希结果转十六进制字符串对比,确保完全相同。
2. IV与密文的编码/拼接错误(核心坑点)
前端不能直接将Uint8Array(IV/密文)转UTF-8字符串——无效UTF-8字节会被替换为�,导致数据损坏。正确做法是直接对字节数组做Base64编码,再用分隔符拼接:
- 前端正确编码逻辑:
// 工具函数:Uint8Array转标准Base64 function uint8ToBase64(arr) { return btoa(String.fromCharCode(...new Uint8Array(arr))); } // 生成12字节IV const iv = crypto.getRandomValues(new Uint8Array(12)); // 加密得到密文+标签(SubtleCrypto自动附加16字节GCM标签) const ciphertextWithTag = await crypto.subtle.encrypt( {name: "AES-GCM", iv: iv}, key, encoder.encode("plaintext") ); // 用分隔符拼接IV和密文的Base64 const encryptedStr = `${uint8ToBase64(iv)}:${uint8ToBase64(ciphertextWithTag)}`; - 错误示例(要避免):
// 错误:Uint8Array转UTF-8字符串会损坏数据 const ivStr = new TextDecoder().decode(iv); const encryptedStr = btoa(ivStr + new TextDecoder().decode(ciphertextWithTag));
3. Go端解密的对应处理
Go端需要拆分前端传来的拼接字符串,分别解码IV和密文+标签,再做GCM解密:
import ( "crypto/aes" "crypto/cipher" "crypto/sha256" "encoding/base64" "strings" "fmt" ) func decrypt(password, encrypted string) (string, error) { // 拆分IV和密文的Base64 parts := strings.Split(encrypted, ":") if len(parts) != 2 { return "", fmt.Errorf("invalid format") } // 解码IV(必须是12字节) iv, err := base64.StdEncoding.DecodeString(parts[0]) if err != nil || len(iv) != 12 { return "", fmt.Errorf("invalid IV: %v", err) } // 解码密文+标签 ciphertextWithTag, err := base64.StdEncoding.DecodeString(parts[1]) if err != nil { return "", fmt.Errorf("invalid ciphertext: %v", err) } // 生成密钥 hash := sha256.Sum256([]byte(password)) block, err := aes.NewCipher(hash[:]) if err != nil { return "", err } // GCM解密(自动验证标签) gcm, err := cipher.NewGCM(block) if err != nil { return "", err } plaintext, err := gcm.Open(nil, iv, ciphertextWithTag, nil) if err != nil { return "", fmt.Errorf("auth failed: %v", err) } return string(plaintext), nil }
4. 其他排查点
- 附加数据(AAD):如果前端加密时传入了
additionalData参数,Go解密时必须传入完全相同的字节数据。 - Base64编码类型:前端用
btoa对应Go的base64.StdEncoding;如果前端用Base64URL编码,Go要改用base64.URLEncoding。 - 标签长度:SubtleCrypto默认用16字节GCM标签,Go的GCM默认也是16字节,无需修改;若前端自定义标签长度,Go需用
cipher.NewGCMWithTagSize对应设置。
内容的提问来源于stack exchange,提问作者elw
相关产品推荐
相关产品推荐

