Node.js解密C++/OpenSSL AES-256-GCM加密数据失败排查
Node.js解密C++/OpenSSL AES-256-GCM加密数据失败问题
问题背景
尝试用Node.js解密C++/OpenSSL AES-256-GCM加密的数据,已确认JS端的password、salt、iv、派生密钥、密文均与C++端一致,但调用decipher.final()时抛出错误:
Error: Unsupported state or unable to authenticate data
at Decipheriv.final
C++加密实现代码
#include <string> #include <sstream> #include <iomanip> #include <vector> #include <iostream> #include <openssl/rand.h> #include <openssl/bio.h> #include <openssl/buffer.h> std::string base64Encode(const unsigned char* input, int length) { BIO *bio, *b64; BUF_MEM *bufferPtr; b64 = BIO_new(BIO_f_base64()); bio = BIO_new(BIO_s_mem()); bio = BIO_push(b64, bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); BIO_write(bio, input, length); BIO_flush(bio); BIO_get_mem_ptr(bio, &bufferPtr); std::string result(bufferPtr->data, bufferPtr->length); BIO_free_all(bio); return result; } std::string bytesToHexString(const unsigned char* data, int len) { std::stringstream ss; ss << std::hex << std::setfill('0'); for (int i = 0; i < len; ++i) { ss << std::setw(2) << static_cast<int>(data[i]); } return ss.str(); } bool encrypt(std::string& data, const std::vector<unsigned char>& password) { constexpr int KEY_SIZE = 32; // AES-256 constexpr int IV_SIZE = 16; constexpr int TAG_SIZE = 16; constexpr int ITERATION_COUNT = 10000; std::vector<unsigned char> key(KEY_SIZE); std::vector<unsigned char> iv(IV_SIZE); std::vector<unsigned char> salt(16); RAND_bytes(salt.data(), 16); // PBKDF2 for key derivation if (PKCS5_PBKDF2_HMAC(reinterpret_cast<const char*>(password.data()), password.size(), salt.data(), salt.size(), ITERATION_COUNT, EVP_sha256(), KEY_SIZE, key.data()) != 1) return false; if (RAND_bytes(iv.data(), IV_SIZE) != 1) return false; // encryption context std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); if (!ctx) return false; if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, key.data(), iv.data()) != 1) return false; std::vector<unsigned char> ciphertext(data.size() + EVP_MAX_BLOCK_LENGTH); int outLen1 = 0, outLen2 = 0; if (EVP_EncryptUpdate(ctx.get(), ciphertext.data(), &outLen1, reinterpret_cast<const unsigned char*>(data.data()), data.size()) != 1) return false; if (EVP_EncryptFinal_ex(ctx.get(), ciphertext.data() + outLen1, &outLen2) != 1) return false; //tag std::vector<unsigned char> tag(TAG_SIZE); if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, TAG_SIZE, tag.data()) != 1) return false; // debug: std::cout << "\nSalt:\n" << bytesToHexString(salt.data(), salt.size()) << std::endl; std::cout << "\nPassword (hex):\n" << bytesToHexString(password.data(), password.size()) << std::endl; std::cout << "\nDerived key:\n" << bytesToHexString(key.data(), KEY_SIZE) << std::endl; std::cout << "\nIV:\n" << bytesToHexString(iv.data(), IV_SIZE) << std::endl; std::cout << "\nTAG:\n" << bytesToHexString(tag.data(), TAG_SIZE) << std::endl; std::cout << "\nCiphertext:\n" << bytesToHexString(ciphertext.data(), outLen1 + outLen2) << std::endl; // combined: salt, IV, ciphertext, and tag std::vector<unsigned char> result; result.insert(result.end(), salt.begin(), salt.end()); result.insert(result.end(), iv.begin(), iv.end()); result.insert(result.end(), ciphertext.begin(), ciphertext.begin() + outLen1 + outLen2); result.insert(result.end(), tag.begin(), tag.end()); data = base64Encode(result.data(), result.size()); // erase buffers... return true; } int main() { std::vector<unsigned char> password(9); RAND_bytes(password.data(), 9); std::string base64Password = base64Encode(password.data(), password.size()); std::cout << "Password: " << base64Password << std::endl; std::string data = "Hello, World!"; encrypt(data, password); std::cout << "\nEncrypted data:\n" << data; return 0; }
JavaScript解密实现代码(含修复)
const crypto = require('crypto'); function decrypt(encryptedBase64, passwordBase64, SALT_SIZE) { const KEY_SIZE = 32; // AES-256 const IV_SIZE = 16; const TAG_SIZE = 16; const ITERATION_COUNT = 10000; // Decode the base64 encrypted data const encryptedBuffer = Buffer.from(encryptedBase64, 'base64'); // Decode the base64 password const passwordBuffer = Buffer.from(passwordBase64, 'base64'); // Extract salt, IV, ciphertext, and tag const salt = encryptedBuffer.slice(0, SALT_SIZE); const iv = encryptedBuffer.slice(SALT_SIZE, SALT_SIZE + IV_SIZE); const tag = encryptedBuffer.slice(-TAG_SIZE); const ciphertext = encryptedBuffer.slice(SALT_SIZE + IV_SIZE, -TAG_SIZE); // Derive the key using PBKDF2 const key = crypto.pbkdf2Sync(passwordBuffer, salt, ITERATION_COUNT, KEY_SIZE, 'sha256'); console.log('\nSalt:\n', salt.toString('hex')); console.log('\nPassword (hex):\n', passwordBuffer.toString('hex')); console.log('\nDerived key:\n', key.toString('hex')); console.log('\nIV:\n', iv.toString('hex')); console.log('\nTAG:\n', tag.toString('hex')); console.log('\nCiphertext:\n', ciphertext.toString('hex')); const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(tag); let decrypted = decipher.update(ciphertext); // 合并final()返回的剩余解密数据 decrypted = Buffer.concat([decrypted, decipher.final()]); return decrypted.toString('utf8'); } (async () => { const encryptedBase64 = 'p4lx3wi2bstgBR/puZBYOKcJU4Ua41spBbeR9etV9K7BaRWGsUqKUIymO+vaQ1PAlv/M1e9OxExVykGNzQ==' const password = 'Et5Q/1E6iTg5' try { const decryptedData = decrypt(encryptedBase64, password, 16) console.log('Decrypted data:', decryptedData) } catch (error) { console.error('Decryption failed:', error.message) } })();
错误信息
Error: Unsupported state or unable to authenticate data at Decipheriv.final
问题原因及修复方案
1. 缺少crypto模块引入
原JS代码未引入Node.js核心的crypto模块,这是加密解密的基础依赖,必须在代码开头添加:
const crypto = require('crypto');
2. 未处理decipher.final()的返回值
AES-GCM模式下,decipher.final()可能返回剩余的解密数据,若仅调用而不接收,会导致解密数据不完整,触发标签认证失败。需将update()和final()的结果合并:
let decrypted = decipher.update(ciphertext); decrypted = Buffer.concat([decrypted, decipher.final()]);
3. 自执行函数语法错误
原代码末尾的自执行函数缺少闭合括号和分号,导致代码无法正常执行,修正为:
})();
修复以上三点后,解密即可正常完成,返回原始明文Hello, World!。
内容的提问来源于stack exchange,提问作者Lillo
相关产品推荐
相关产品推荐

