You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js中用C++/OpenSSL通过密码/盐解密加密字符串

问题:C++/OpenSSL与Node.js跨端加解密一致性验证与优化

我尝试用C++/OpenSSL通过密码和盐解密一段加密字符串,最初测试时发现Node.js生成的Key和C++端匹配,但IV不匹配,而且执行+= decipher.final('utf8');时抛出错误:Uncaught Error Error: error:1C800064:Provider routines::bad decrypt。

---编辑---
现在我已经修改代码让它能运行,但不确定当前实现是否正确,也想知道有没有可以优化的点。


Node.js 解密代码

function decrypt(encrypted, passphrase, salt) 
{
   const keySize = 16;
   const ivSize = 16;
   const iterations = 1000;  // 和C++代码保持一致
   const digest = 'sha1';   // 和C++代码保持一致
   
   const keyAndIv = crypto.pbkdf2Sync(passphrase, salt, iterations, keySize + ivSize, digest);
   const key = keyAndIv.slice(0, keySize);
   const iv = keyAndIv.slice(keySize, keySize + ivSize);
   console.log('Key (hex):', key.toString('hex'));
   console.log('IV (hex):', iv.toString('hex'));

   const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
   let decrypted = decipher.update(encrypted, 'base64', 'utf8');
   decrypted += decipher.final('utf8');

   return decrypted;
}
 
const encryptedStr = 'YZ/vV9aj+ublWdLrOw44EQ=='
const passphrase = 'password123';
const salt = 'salt123'; 

const decryptedStr = decrypt(encryptedStr, passphrase, salt);
console.log(decryptedStr);

C++/OpenSSL 加密代码

#include <openssl/rand.h>
#include <openssl/evp.h>
#include <openssl/bio.h>

std::string stringToBase64(const std::string& input) 
{
   BIO* bio = BIO_new(BIO_s_mem());

   BIO* base64 = BIO_new(BIO_f_base64());
   BIO_set_flags(base64, BIO_FLAGS_BASE64_NO_NL);  // 不添加换行符

   BIO* bio_chain = BIO_push(base64, bio);    
   BIO_write(bio_chain, input.c_str(), input.length());
   BIO_flush(bio_chain);

   char* encoded_data;
   long encoded_length = BIO_get_mem_data(bio, &encoded_data);

   std::string base64_string(encoded_data, static_cast<size_t>(encoded_length));

   // 释放BIO资源
   BIO_free_all(bio_chain);

   return base64_string;
}


std::string encrypt(std::string str, const char* password, const char* salt)
{
   EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new();

   unsigned char derived[32]; // 32字节,用于存储密钥和IV

   if(!PKCS5_PBKDF2_HMAC(password, strlen(password), 
      reinterpret_cast<const unsigned char *>(salt), strlen(salt), 
      1000, EVP_sha1(), 32, derived)) // 派生32字节数据
   {
       EVP_CIPHER_CTX_free(ctx);
       return std::string();
   }

   unsigned char key[EVP_MAX_KEY_LENGTH];
   unsigned char iv[EVP_MAX_IV_LENGTH];

   // 前16字节作为密钥
   memcpy(key, derived, 16);
   // 后16字节作为IV
   memcpy(iv, derived + 16, 16);

   if (EVP_CipherInit(ctx, EVP_aes_128_cbc(), key, iv, true) != 1) 
   {
       EVP_CIPHER_CTX_free(ctx);
       return std::string();
   }
       
   const int MAX_BUFFER_SIZE = str.size();
   int len = 0;
   int outLen;
   
   int blocksize = EVP_CIPHER_CTX_block_size(ctx);
   std::string cipherBuf;
   cipherBuf.resize(MAX_BUFFER_SIZE + blocksize);
       
   if (EVP_CipherUpdate(ctx, reinterpret_cast<unsigned char *>(
       cipherBuf.data()), &outLen, reinterpret_cast<const unsigned char *>
       (str.data()), str.size()) != 1)
   {
       EVP_CIPHER_CTX_free(ctx);
       return std::string();
   }
   
   len += outLen;
 
   if (EVP_CipherFinal(ctx, reinterpret_cast<unsigned char *>
       (cipherBuf.data() + len), &outLen) != 1)
   {
       EVP_CIPHER_CTX_free(ctx);
       return std::string();     
   }
   
   len += outLen;
    
   cipherBuf.resize(len);
   // 清理资源
   EVP_CIPHER_CTX_free(ctx);

   str = stringToBase64(cipherBuf); 
   // 清空缓冲区(注:局部变量无需手动清零)
   memset(cipherBuf.data(), 0, MAX_BUFFER_SIZE + blocksize);

   return str;
}

int main(int argc, char* argv[])
{
   const char* password = "password123";
   const char* salt = "salt123";

   std::string encrypted = encrypt("test123", password, salt);
}

正确性验证与优化建议

正确性确认

当前代码已经对齐了跨端加解密的核心参数:PBKDF2迭代次数1000、摘要算法SHA1、密钥+IV总长度32字节(16字节密钥+16字节IV),加解密流程完全匹配,Node.js可以正确解密C++加密的内容,说明当前实现是正确的。

优化点

  • 内存安全优化:C++端的memset(cipherBuf.data(), 0, MAX_BUFFER_SIZE + blocksize);属于冗余操作,cipherBuf是函数局部变量,函数执行完毕后会自动销毁,且resize后的缓冲区仅保留实际密文长度,无需手动清零。
  • 类型转换规范:C++中str.data()本身是const char*,可以直接转换为const unsigned char*,无需多余的reinterpret_cast。
  • 错误处理增强:当前错误仅返回空字符串,可通过OpenSSL的ERR_get_error()函数获取具体错误码和信息,便于问题定位。
  • IV安全性提升:当前通过PBKDF2派生IV的方式可行,但更安全的做法是每次加密生成随机IV,将IV与密文一起存储(例如IV前置在密文头部),固定IV会降低AES-CBC的加密安全性。
  • 算法扩展性:当前使用AES-128-CBC,密钥长度16字节符合要求;若后续升级到AES-256,需将密钥长度调整为32字节,同时PBKDF2派生的总长度也要对应修改为32+16=48字节(若IV仍用16字节)。
  • Base64编码优化:C端的Base64实现可替换为OpenSSL的EVP_EncodeInit_ex/EVP_EncodeUpdate/EVP_EncodeFinal_ex系列函数,或使用C17标准库的<base64>模块(若编译器支持),代码会更简洁规范。

内容的提问来源于stack exchange,提问作者Jabu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 12:28:09