请求:用OpenSSL实现与Node.js CryptoJS兼容的AES加密C++代码
实现与Node.js CryptoJS AES加密逻辑一致的C++ OpenSSL代码
原Node.js CryptoJS加密逻辑
原代码逻辑为:将10位数字消息与指定的SecuritySalt拼接后,使用32位字符串secret_key进行AES加密,输出Base64格式密文。代码如下:
var CryptoJS = require("crypto-js"); const SecuritySalt = "Amidst the bustling city streets, a solitary figure found solace in the melody of raindrops dancing upon the cobblestones.sdwe2q"; const secret_key = "Whispersofthewindechoedthroughth"; const encryptData = (message) => { return CryptoJS.AES.encrypt( SecuritySalt + message, secret_key ).toString(); }; let message = "1111111111"; console.log("encrypt message :" + encryptData(message));
对应的解密代码:
const secret_key = "Whispersofthewindechoedthroughth"; const decryptData = (cipherText) => { const bytes = CryptoJS.AES.decrypt(cipherText, secret_key); let message = bytes.toString(CryptoJS.enc.Utf8); return message; }; // 假设cipherText是加密后的Base64字符串 console.log("decrypt: " + decryptData(cipherText));
原C++实现的问题
你之前的实现错误在于偏离了CryptoJS的默认行为:
- CryptoJS使用EVP_BytesToKey算法(MD5哈希,迭代1次)从字符串密钥生成AES密钥和IV,而非PKCS5_PBKDF2_HMAC
- CryptoJS会生成随机8字节盐,并用
Salted__<盐><密文>的格式打包后再做Base64编码,而非使用全0IV和直接编码密文
正确的C++ OpenSSL实现代码
以下代码严格遵循CryptoJS的默认AES加密逻辑:
#include <iostream> #include <string> #include <openssl/evp.h> #include <openssl/rand.h> #include <openssl/bio.h> #include <openssl/buffer.h> // Base64编码函数(无换行符,匹配CryptoJS输出格式) std::string base64_encode(const unsigned char* data, size_t 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, data, length); BIO_flush(bio); BIO_get_mem_ptr(bio, &bufferPtr); BIO_set_close(bio, BIO_NOCLOSE); std::string encoded(bufferPtr->data, bufferPtr->length); BIO_free_all(bio); return encoded; } std::string encrypt_aes_cryptojs_compatible(const std::string& plaintext, const std::string& password) { // 生成随机8字节盐(CryptoJS默认行为) unsigned char salt[8]; if (!RAND_bytes(salt, sizeof(salt))) { throw std::runtime_error("Failed to generate random salt"); } // 使用EVP_BytesToKey生成AES-256-CBC密钥和IV unsigned char key[32], iv[16]; int key_len = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_md5(), salt, reinterpret_cast<const unsigned char*>(password.c_str()), password.length(), 1, key, iv); if (key_len != 32) { throw std::runtime_error("Failed to generate key via EVP_BytesToKey"); } // 初始化加密上下文 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) { throw std::runtime_error("Failed to create EVP_CIPHER_CTX"); } if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key, iv) != 1) { EVP_CIPHER_CTX_free(ctx); throw std::runtime_error("Failed to initialize encryption"); } // 计算密文缓冲区大小 int ciphertext_len = plaintext.length() + EVP_CIPHER_block_size(EVP_aes_256_cbc()); unsigned char* ciphertext = new unsigned char[ciphertext_len]; int len; // 执行加密 if (EVP_EncryptUpdate(ctx, ciphertext, &len, reinterpret_cast<const unsigned char*>(plaintext.c_str()), plaintext.length()) != 1) { EVP_CIPHER_CTX_free(ctx); delete[] ciphertext; throw std::runtime_error("Failed to perform encryption update"); } int ciphertext_total_len = len; if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) { EVP_CIPHER_CTX_free(ctx); delete[] ciphertext; throw std::runtime_error("Failed to finalize encryption"); } ciphertext_total_len += len; EVP_CIPHER_CTX_free(ctx); // 按CryptoJS格式打包:Salted__前缀 + 8字节盐 + 密文 unsigned char* packed_data = new unsigned char[8 + 8 + ciphertext_total_len]; memcpy(packed_data, "Salted__", 8); memcpy(packed_data + 8, salt, 8); memcpy(packed_data + 16, ciphertext, ciphertext_total_len); size_t packed_len = 16 + ciphertext_total_len; // Base64编码 std::string encoded = base64_encode(packed_data, packed_len); // 清理内存 delete[] ciphertext; delete[] packed_data; return encoded; } int main() { const std::string SecuritySalt = "Amidst the bustling city streets, a solitary figure found solace in the melody of raindrops dancing upon the cobblestones.sdwe2q"; const std::string secret_key = "Whispersofthewindechoedthroughth"; const std::string message = "1111111111"; // 拼接盐与消息 std::string plaintext = SecuritySalt + message; try { std::string encrypted = encrypt_aes_cryptojs_compatible(plaintext, secret_key); std::cout << "encrypt message: " << encrypted << std::endl; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 1; } return 0; }
关键说明
- EVP_BytesToKey算法:CryptoJS默认使用该算法从字符串密码生成32位AES密钥和16位IV,参数为MD5哈希、迭代1次、随机8字节盐
- Salted__格式:CryptoJS输出的密文必须是
Salted__前缀 + 8字节盐 + 实际密文的组合,再进行Base64编码,否则无法被Node.js端解密 - 随机盐:每次加密生成不同盐,因此相同明文的加密结果不同,但均能被正确解密
编译时需链接OpenSSL库,示例命令:
g++ -o aes_encrypt aes_encrypt.cpp -lcrypto
内容的提问来源于stack exchange,提问作者Mihir Aryan
相关产品推荐
相关产品推荐

