Node.js用AES-256-CTR解密WebCrypto AES-256-GCM加密内容结果异常
问题根因
- 核心错误是加密模式不匹配:AES-GCM和AES-CTR是两种完全不同的分组密码工作模式,加密逻辑存在本质差异,用GCM加密得到的密文不可能通过CTR模式解密出正确明文,这是解密结果乱码的核心原因,和密钥、IV是否正确无关。
- 你误以为客户端加密时没有生成authTag是错误认知:WebCrypto的
AES-GCM加密输出的encryptedArrayBuffer本身就已经包含了默认16字节的authTag,直接追加在密文末尾,你之前拼接数据时已经把authTag一起打包进了传输内容,不存在没有authTag的情况。
正确实现方案
可选优化:客户端加密显式指定authTag长度
显式指定authTag长度可以避免前后端对齐问题,默认值为128位(即16字节):
async function encrypt(text: string) { const encodedText = new TextEncoder().encode(text); const aesKey = await generateAesKey(); const iv = window.crypto.getRandomValues( new Uint8Array(SERVER_ENCRYPTION_IV_LENGTH) ); const encrypted = await window.crypto.subtle.encrypt( { name: 'AES-GCM', iv, tagLength: 128 // 显式指定authTag长度 }, aesKey, encodedText ); const concatenatedData = new Uint8Array( iv.byteLength + encrypted.byteLength ); concatenatedData.set(iv); concatenatedData.set(new Uint8Array(encrypted), iv.byteLength); return arrayBufferToBase64(concatenatedData) }
后端改用AES-256-GCM模式解密
从传输的加密数据中拆分出IV、密文、authTag三部分即可正常解密:
import crypto from 'crypto'; export function decrypt(sharedKey: string, message: string) { const messageBuffer = Buffer.from(message, 'base64'); const iv = messageBuffer.subarray(0, 16); // 加密数据末尾16字节为authTag,中间部分为实际密文 const authTag = messageBuffer.subarray(-16); const encryptedData = messageBuffer.subarray(16, -16); const decipher = crypto.createDecipheriv( 'aes-256-gcm', Buffer.from(sharedKey, 'base64'), iv ); // 传入authTag做完整性校验 decipher.setAuthTag(authTag); const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]); // 直接转utf8返回明文即可,不需要多余的编码转换 return decrypted.toString('utf8'); }
额外注意事项
- 如果你的IV长度不是16字节,调整上述代码中iv的截取长度即可
- AES-GCM自带密文完整性校验,篡改密文、IV、authTag任意内容都会直接抛出解密失败错误,安全性远高于CTR模式,不要为了简化步骤改用CTR
- 原后端解密逻辑中多余的
binary转hex再转base64的操作属于无效编码转换,会额外破坏明文内容,不需要保留。
内容的提问来源于stack exchange,提问作者Ali Torki
相关产品推荐
相关产品推荐

