Node Crypto解密文件报错:Unsupported state or unable to authenticate data
Node.js AES-GCM 解密报错 "Unsupported state or unable to authenticate data" 解决方法
问题根源
你的代码存在两个关键问题导致解密失败:
- Buffer拼接方式错误:使用
+拼接Buffer会触发隐式字符串转换,破坏二进制数据结构,导致AuthTag和加密内容无法正确解析。 - 异步执行顺序错误:
file.encrypt()和file.decrypt()连续调用时,加密操作的异步fs.readFile还未完成,解密就已读取原文件内容,导致解密逻辑失效。
修正后的代码
加密解密核心模块
const crypto = require('crypto'); const fs = require('fs'); const app = { encrypt(data, password) { const salt = crypto.randomBytes(16); const key = crypto.scryptSync(password, salt, 32, { N: 16384 }); const cipher = crypto.createCipheriv('aes-256-gcm', key, salt); const encryptedData = Buffer.concat([cipher.update(data), cipher.final()]); const authTag = cipher.getAuthTag(); // 用Buffer.concat正确合并二进制数据 return Buffer.concat([salt, authTag, encryptedData]); }, decrypt(data, password) { const salt = data.slice(0, 16); const authTag = data.slice(16, 32); const encData = data.slice(32); const key = crypto.scryptSync(password, salt, 32, { N: 16384 }); const decipher = crypto.createDecipheriv('aes-256-gcm', key, salt); decipher.setAuthTag(authTag); const plainText = Buffer.concat([decipher.update(encData), decipher.final()]); return plainText; } }
文件操作模块(修正异步顺序)
const fileLoc = './sample.txt'; const password = 'password'; const file = { encrypt() { return new Promise((resolve, reject) => { fs.readFile(fileLoc, (err, data) => { if (err) { console.log('File not found.'); reject(err); return; } const cipherText = app.encrypt(data, password); fs.writeFileSync(fileLoc, cipherText); console.log('加密完成'); resolve(); }); }); }, decrypt() { return new Promise((resolve, reject) => { fs.readFile(fileLoc, (err, data) => { if (err) { console.log('File not found.'); reject(err); return; } try { const plainText = app.decrypt(data, password); fs.writeFileSync(fileLoc, plainText); console.log('解密完成'); resolve(); } catch (decryptErr) { console.log('解密失败:', decryptErr.message); reject(decryptErr); } }); }); } } // 保证加密完成后再执行解密 file.encrypt().then(() => { file.decrypt(); }).catch(err => { console.error(err); });
额外说明
- AES-GCM模式下,AuthTag的长度默认是16字节,你的切片逻辑是正确的,核心问题还是Buffer拼接和异步顺序。
- 用Promise封装异步操作可以更清晰地控制执行顺序,避免回调嵌套。
- 处理二进制文件时,必须始终用Buffer操作,避免任何隐式的字符串转换。
内容的提问来源于stack exchange,提问作者Akshay
相关产品推荐
相关产品推荐

