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

JavaScript与C++/Qt对称加密密钥匹配问题及方案探讨

跨语言对称加密(JavaScript + C++/Qt)完整实现及非对称示例

一、核心问题解决思路

要实现双向兼容,必须保证所有加密参数完全一致:

  • 相同的密钥派生函数(KDF)
  • 相同的对称加密算法(如AES-256-CBC)
  • 相同的填充方式(PKCS#7)
  • 相同的编码格式(如Base64)
  • 加密时随机生成IV,解密时传入对应IV

二、对称加密完整实现(AES-256-CBC)

1. JavaScript 端(依赖Node.js crypto 模块)

const crypto = require('crypto');

// 密钥派生:PBKDF2-HMAC-SHA256
function deriveKey(password, salt, iterations = 10000, keyLength = 32) {
    return crypto.pbkdf2Sync(password, salt, iterations, keyLength, 'sha256');
}

// AES-256-CBC 加密
function encryptAES(plaintext, password, salt) {
    const key = deriveKey(password, salt);
    const iv = crypto.randomBytes(16); // CBC模式IV固定16字节
    const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
    
    let encrypted = cipher.update(plaintext, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    
    // 返回IV+密文的组合(用Base64拼接,方便传输)
    return `${iv.toString('base64')}:${encrypted}`;
}

// AES-256-CBC 解密
function decryptAES(encryptedStr, password, salt) {
    const [ivBase64, encryptedBase64] = encryptedStr.split(':');
    const key = deriveKey(password, salt);
    const iv = Buffer.from(ivBase64, 'base64');
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    
    let decrypted = decipher.update(encryptedBase64, 'base64', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
}

// 示例使用
const password = 'mySecurePassword';
const salt = crypto.randomBytes(16).toString('base64'); // 盐值需两端共享,可预定义或首次生成后存储
const plaintext = 'Hello Cross-Language Encryption!';

const encrypted = encryptAES(plaintext, password, salt);
console.log('加密结果:', encrypted);

const decrypted = decryptAES(encrypted, password, salt);
console.log('解密结果:', decrypted);

2. C++/Qt 端(依赖Qt + OpenSSL,无需额外AES库)

Qt通过OpenSSL支持AES和PBKDF2,直接用原生API实现,确保填充与JS一致:

#include <QCoreApplication>
#include <QCryptographicHash>
#include <QRandomGenerator>
#include <QByteArray>
#include <QString>
#include <openssl/evp.h>
#include <openssl/kdf.h>

// 密钥派生:PBKDF2-HMAC-SHA256
QByteArray deriveKey(const QString& password, const QByteArray& salt, int iterations = 10000, int keyLength = 32) {
    QByteArray key(keyLength, 0);
    PKCS5_PBKDF2_HMAC(password.toUtf8().constData(), password.length(),
                      reinterpret_cast<const unsigned char*>(salt.constData()), salt.length(),
                      iterations, EVP_sha256(), keyLength, reinterpret_cast<unsigned char*>(key.data()));
    return key;
}

// AES-256-CBC 加密(PKCS#7填充)
QString encryptAES(const QString& plaintext, const QString& password, const QByteArray& salt) {
    QByteArray key = deriveKey(password, salt);
    QByteArray iv(16, 0);
    QRandomGenerator::global()->fillRange(reinterpret_cast<quint32*>(iv.data()), iv.size() / 4); // 生成随机IV

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<const unsigned char*>(key.constData()), reinterpret_cast<const unsigned char*>(iv.constData()));

    QByteArray plainData = plaintext.toUtf8();
    int cipherLen = plainData.size() + EVP_CIPHER_block_size(EVP_aes_256_cbc());
    QByteArray cipherData(cipherLen, 0);
    
    int len;
    EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(cipherData.data()), &len, reinterpret_cast<const unsigned char*>(plainData.constData()), plainData.size());
    int finalLen;
    EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(cipherData.data()) + len, &finalLen);
    cipherData.truncate(len + finalLen);
    
    EVP_CIPHER_CTX_free(ctx);

    // 返回IV+密文的Base64拼接
    return QString("%1:%2").arg(QString(iv.toBase64())).arg(QString(cipherData.toBase64()));
}

// AES-256-CBC 解密
QString decryptAES(const QString& encryptedStr, const QString& password, const QByteArray& salt) {
    QStringList parts = encryptedStr.split(':');
    if (parts.size() != 2) return "";
    
    QByteArray iv = QByteArray::fromBase64(parts[0].toUtf8());
    QByteArray cipherData = QByteArray::fromBase64(parts[1].toUtf8());
    QByteArray key = deriveKey(password, salt);

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<const unsigned char*>(key.constData()), reinterpret_cast<const unsigned char*>(iv.constData()));

    int plainLen = cipherData.size();
    QByteArray plainData(plainLen, 0);
    
    int len;
    EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(plainData.data()), &len, reinterpret_cast<const unsigned char*>(cipherData.constData()), cipherData.size());
    int finalLen;
    EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(plainData.data()) + len, &finalLen);
    plainData.truncate(len + finalLen);
    
    EVP_CIPHER_CTX_free(ctx);

    return QString::fromUtf8(plainData);
}

// 示例使用
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    
    QString password = "mySecurePassword";
    QByteArray salt = QRandomGenerator::global()->generate(16); // 盐值需与JS端共享
    QString plaintext = "Hello Cross-Language Encryption!";
    
    QString encrypted = encryptAES(plaintext, password, salt);
    qDebug() << "加密结果:" << encrypted;
    
    QString decrypted = decryptAES(encrypted, password, salt);
    qDebug() << "解密结果:" << decrypted;
    
    return a.exec();
}

注意:C++端需要在.pro文件中添加QT += core和LIBS += -lcrypto,确保OpenSSL链接正常。


三、非对称加密示例(RSA 2048)

1. JavaScript 端

const crypto = require('crypto');

// 生成RSA密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048,
    publicKeyEncoding: { type: 'spki', format: 'pem' },
    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

// 公钥加密
function encryptRSA(plaintext, pubKey) {
    return crypto.publicEncrypt(pubKey, Buffer.from(plaintext, 'utf8')).toString('base64');
}

// 私钥解密
function decryptRSA(encryptedBase64, privKey) {
    return crypto.privateDecrypt(privKey, Buffer.from(encryptedBase64, 'base64')).toString('utf8');
}

// 示例
const plaintext = "RSA Test Message";
const encrypted = encryptRSA(plaintext, publicKey);
console.log('RSA加密结果:', encrypted);
const decrypted = decryptRSA(encrypted, privateKey);
console.log('RSA解密结果:', decrypted);

2. C++/Qt 端

#include <QCoreApplication>
#include <QSslKey>
#include <QByteArray>
#include <QString>
#include <openssl/rsa.h>
#include <openssl/pem.h>

// 生成RSA密钥对
void generateRSAKeyPair(QByteArray& pubKeyPem, QByteArray& privKeyPem) {
    RSA* rsa = RSA_new();
    BIGNUM* e = BN_new();
    BN_set_word(e, RSA_F4);
    RSA_generate_key_ex(rsa, 2048, e, nullptr);
    
    // 导出公钥PEM
    BIO* pubBio = BIO_new(BIO_s_mem());
    PEM_write_bio_RSA_PUBKEY(pubBio, rsa);
    char* pubData;
    long pubLen = BIO_get_mem_data(pubBio, &pubData);
    pubKeyPem = QByteArray(pubData, pubLen);
    
    // 导出私钥PEM
    BIO* privBio = BIO_new(BIO_s_mem());
    PEM_write_bio_RSAPrivateKey(privBio, rsa, nullptr, nullptr, 0, nullptr, nullptr);
    char* privData;
    long privLen = BIO_get_mem_data(privBio, &privData);
    privKeyPem = QByteArray(privData, privLen);
    
    BIO_free(pubBio);
    BIO_free(privBio);
    RSA_free(rsa);
    BN_free(e);
}

// 公钥加密
QString encryptRSA(const QString& plaintext, const QByteArray& pubKeyPem) {
    RSA* rsa = PEM_read_bio_RSA_PUBKEY(BIO_new_mem_buf(pubKeyPem.constData(), pubKeyPem.size()), nullptr, nullptr, nullptr);
    if (!rsa) return "";
    
    QByteArray plainData = plaintext.toUtf8();
    int encryptLen = RSA_size(rsa);
    QByteArray encryptData(encryptLen, 0);
    
    int result = RSA_public_encrypt(plainData.size(), reinterpret_cast<const unsigned char*>(plainData.constData()),
                                    reinterpret_cast<unsigned char*>(encryptData.data()), rsa, RSA_PKCS1_PADDING);
    RSA_free(rsa);
    
    if (result == -1) return "";
    encryptData.truncate(result);
    return QString(encryptData.toBase64());
}

// 私钥解密
QString decryptRSA(const QString& encryptedBase64, const QByteArray& privKeyPem) {
    RSA* rsa = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(privKeyPem.constData(), privKeyPem.size()), nullptr, nullptr, nullptr);
    if (!rsa) return "";
    
    QByteArray encryptData = QByteArray::fromBase64(encryptedBase64.toUtf8());
    int decryptLen = RSA_size(rsa);
    QByteArray decryptData(decryptLen, 0);
    
    int result = RSA_private_decrypt(encryptData.size(), reinterpret_cast<const unsigned char*>(encryptData.constData()),
                                     reinterpret_cast<unsigned char*>(decryptData.data()), rsa, RSA_PKCS1_PADDING);
    RSA_free(rsa);
    
    if (result == -1) return "";
    decryptData.truncate(result);
    return QString::fromUtf8(decryptData);
}

// 示例使用
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    
    QByteArray pubKey, privKey;
    generateRSAKeyPair(pubKey, privKey);
    
    QString plaintext = "RSA Test Message";
    QString encrypted = encryptRSA(plaintext, pubKey);
    qDebug() << "RSA加密结果:" << encrypted;
    
    QString decrypted = decryptRSA(encrypted, privKey);
    qDebug() << "RSA解密结果:" << decrypted;
    
    return a.exec();
}

四、关键注意事项

  1. 盐值共享:对称加密的盐值必须在JS和C++端完全一致,可预定义或首次生成后存储在两端。
  2. IV传递:CBC模式的IV必须随密文一起传输,不能硬编码,否则会降低安全性。
  3. 填充一致性:必须统一使用PKCS#7填充,JS的crypto模块默认使用该填充,C++端OpenSSL的API也默认支持。
  4. 编码统一:密文、IV、密钥建议用Base64编码,避免二进制传输的乱码问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 16:45:47