将C++ OpenSSL AES-CBC解密转Node.js后解密异常,原因何在?
问题:C++ AES-CBC解密转Node.js后结果不符
我将一段C++/Qt的AES-CBC解密代码转为Node.js版本,但解密结果不正确。已确认publicKey、iv、密文内容均正确,推测问题出在Node.js的密钥导入或解密逻辑中。C++代码输出的paddingLength为16,Node.js版本输出为4。
原C++代码(简化版)
AES_KEY *aesKey = new AES_KEY; AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(publicKey.data()), 256, aesKey); QByteArray iv = encryptedFile.read(AES_BLOCK_SIZE); int blocksPerLoop = 10000; QByteArray cipher = encryptedFile.read(AES_BLOCK_SIZE * (blocksPerLoop + 1)); int sizeOfInput = cipher.size(); unsigned char *content; content = new unsigned char[sizeOfInput]; memset(content, 0, sizeof(sizeOfInput)); AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(cipher.data()), content, size_t(sizeOfInput), aesKey, reinterpret_cast<unsigned char*>(iv.data()), AES_DECRYPT); int paddingLength = content[sizeOfInput - 1]; qDebug() << "paddingLength" << paddingLength; // prints: 16
转换后的Node.js代码(存在问题)
const aesKey = await crypto.subtle.importKey('raw', Buffer.from(publicKey, 'hex'), { name: 'AES-CBC', length: 256 }, false, [ 'encrypt', 'decrypt' ]) let iv = Buffer.allocUnsafe(AES_BLOCK_SIZE) await encryptedFile.read(iv, 0, AES_BLOCK_SIZE) const blocksPerLoop = 10000 const cipher = Buffer.allocUnsafe(AES_BLOCK_SIZE * (blocksPerLoop + 1)) await encryptedFile.read(cipher, 0, AES_BLOCK_SIZE * (blocksPerLoop + 1)) const sizeOfInput = cipher.length var decipher = crypto.createDecipheriv('AES-256-CBC', aesKey, iv) decipher.setAutoPadding(false) const content = Buffer.concat([decipher.update(cipher), decipher.final()]) const paddingLength = content[sizeOfInput - 1] console.log('paddingLength', paddingLength) // prints: 4
问题排查与修正方案
核心问题
- 密钥导入方式不兼容:Node.js内置
crypto模块和Web Crypto API(crypto.subtle)的密钥对象不能混用。crypto.createDecipheriv需要直接传入Buffer格式的密钥,而非CryptoKey对象。 - 文件读取未校验完整性:
encryptedFile.read返回实际读取的字节数,原代码未校验是否读取到完整的IV和密文,可能导致数据截断或包含缓冲区垃圾数据。 - 密文长度处理错误:直接使用缓冲区总长度而非实际读取的字节数,会导致解密时处理无效数据。
修正后的Node.js代码
const crypto = require('crypto'); const AES_BLOCK_SIZE = 16; // 直接将密钥转为Buffer,无需通过crypto.subtle导入 const aesKeyBuffer = Buffer.from(publicKey, 'hex'); // 读取IV并校验完整性 let iv = Buffer.alloc(AES_BLOCK_SIZE); const ivBytesRead = await encryptedFile.read(iv, 0, AES_BLOCK_SIZE); if (ivBytesRead !== AES_BLOCK_SIZE) { throw new Error('无法读取完整的IV'); } const blocksPerLoop = 10000; const cipherBufferSize = AES_BLOCK_SIZE * (blocksPerLoop + 1); const cipher = Buffer.alloc(cipherBufferSize); // 读取密文并校验完整性 const cipherBytesRead = await encryptedFile.read(cipher, 0, cipherBufferSize); if (cipherBytesRead === 0) { throw new Error('未读取到密文数据'); } // 使用Buffer格式密钥初始化解密器 const decipher = crypto.createDecipheriv('AES-256-CBC', aesKeyBuffer, iv); decipher.setAutoPadding(false); // 与C++逻辑一致,手动处理填充 // 仅解密实际读取到的密文部分 const content = Buffer.concat([ decipher.update(cipher.slice(0, cipherBytesRead)), decipher.final() ]); const paddingLength = content[content.length - 1]; console.log('paddingLength', paddingLength); // 应输出16
关键修正点
- 移除
crypto.subtle.importKey,直接使用Buffer.from生成密钥缓冲区,适配Node.jscrypto模块的要求。 - 校验文件读取的字节数,确保IV和密文读取完整,避免数据不完整导致解密错误。
- 解密时仅处理实际读取到的密文数据,排除缓冲区中未被覆盖的垃圾数据。
内容的提问来源于stack exchange,提问作者André Fiedler
相关产品推荐
相关产品推荐

