如何从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
相关产品推荐
相关产品推荐

