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

如何从Node.js解密C++ OpenSSL ChaCha20加密的数据?

Node.js解密C++ OpenSSL ChaCha20加密数据失败问题

我需要在Node.js中解密由C++ OpenSSL ChaCha20加密的数据,但Node.js标准crypto模块不直接支持不带Poly1305的ChaCha20。尝试使用JsChaCha20库后程序未报错,但解密结果不正确,decrypt函数返回乱码:'bAۄ���@��'。目前正在排查问题到底出在代码、JsChaCha20库,还是OpenSSL与该库的实现不匹配上。

Node.js解密代码

import crypto     from 'crypto'
import JSChaCha20 from './jschacha20.js'

function decrypt(data, password)
{
    if (!data.length)
        return false

    // 从密码生成盐
    const pwhash = crypto.createHash('sha256').update(password).digest()
    const salt   = Buffer.from(pwhash)

    const NONCE_SIZE = 12
    const KEY_SIZE   = 32

    // 从密码派生密钥
    const key = crypto.createHash('sha256').update(password).digest()

    // 派生nonce
    const context = Buffer.concat([Buffer.from(password), salt])
    const hash    = crypto.createHash('sha256').update(context).digest()
    const nonce   = hash.slice(0, NONCE_SIZE)

    // 输出值用于对比
    console.log('\nkey\n', key.toString('base64'))
    console.log('\nnonce\n', nonce.toString('base64'))
    console.log('\nsalt\n', salt.toString('base64'))
    console.log('\ncontext\n', context.toString('base64'))

    try 
    {
        const decipher = new JSChaCha20(key, nonce)
        let decrypted  = decipher.decrypt(Buffer.from(data))
        decrypted      = Buffer.from(decrypted).toString('utf8')
        return decrypted
    } 
    catch (error)
    {
        console.error(error.message)
        return false
    }
}

let encrypted   = "b1OnS61xyC/D0Dc="
encrypted       = Buffer.from(encrypted, 'base64')
const decrypted = decrypt(encrypted, "password")

C++ OpenSSL ChaCha20加密实现

#include <openssl/sha.h>
#include <openssl/buffer.h>
#include <openssl/rand.h>
#include <openssl/evp.h>

bool encrypt(std::string& data, std::string& password)
{
    if (data.empty()) 
        return false;
    
    // 从密码生成盐
    unsigned char pwhash[SHA256_DIGEST_LENGTH];
    SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), pwhash);
    std::vector<unsigned char> salt(pwhash, pwhash + 32);

    constexpr size_t NONCE_SIZE = 12;
    constexpr size_t KEY_SIZE = 32;

    // 从密码派生密钥
    unsigned char key[KEY_SIZE];
    SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), key);

    // 派生nonce
    std::vector<unsigned char> context;
    context.reserve(password.size() + salt.size());
    context.insert(context.end(), password.begin(), password.end());
    context.insert(context.end(), salt.begin(), salt.end());
    
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256(context.data(), context.size(), hash);
    std::vector<unsigned char> nonce(hash, hash + NONCE_SIZE);
    
    // 使用派生的nonce加密
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) return false;
    if (EVP_EncryptInit_ex(ctx, EVP_chacha20(), nullptr, key, nonce.data()) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }

    std::vector<char> ciphertext(data.size(), 0);
    int ciphertextLen;
    if (EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(ciphertext.data()), &ciphertextLen, 
                          reinterpret_cast<const unsigned char*>(data.data()), data.size()) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    EVP_CIPHER_CTX_free(ctx);
    ciphertext.resize(ciphertextLen);

    // 输出key、nonce、salt、context的base64格式
    auto toBase64 = [](const unsigned char* data, size_t len) -> std::string {
        BIO* b64 = BIO_new(BIO_f_base64());
        BIO* bio = BIO_new(BIO_s_mem());
        bio = BIO_push(b64, bio);
        BIO_write(bio, data, len);
        BIO_flush(bio);
        BUF_MEM* bufferPtr;
        BIO_get_mem_ptr(bio, &bufferPtr);
        std::string result(bufferPtr->data, bufferPtr->length - 1); // 排除空终止符
        BIO_free_all(bio);
        return result;
    };

    std::cout << "\nkey\n"     << toBase64(key, sizeof(key));
    std::cout << "\nnonce\n"   << toBase64(nonce.data(), nonce.size());
    std::cout << "\nsalt\n"    << toBase64(salt.data(), salt.size());
    std::cout << "\ncontext\n" << toBase64(context.data(), context.size());

    int encDataSize = 4 * ((data.length() + 2) / 3);
    std::vector<unsigned char> b64(encDataSize + 1); // +1 用于容纳EVP_EncodeBlock添加的终止空字符
    EVP_EncodeBlock(b64.data(), reinterpret_cast<const unsigned char*>(ciphertext.data()), ciphertext.size());

    data = std::string(b64.begin(), b64.end());

    return true;
}


int main()
{
    std::string data     = "Hello World";
    std::string password = "password";
    encrypt(data, password);
}

排查情况

  • 已确认Node.js和C++代码中key、nonce、salt、context的base64值完全匹配。
  • 为了不增加密文大小,选择通过密码派生nonce(明知这种做法不安全),同时也是选择不带Poly1305的ChaCha20的原因,因为Poly1305会附加标签增加数据体积。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 16:41:17