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(); }
四、关键注意事项
- 盐值共享:对称加密的盐值必须在JS和C++端完全一致,可预定义或首次生成后存储在两端。
- IV传递:CBC模式的IV必须随密文一起传输,不能硬编码,否则会降低安全性。
- 填充一致性:必须统一使用PKCS#7填充,JS的
crypto模块默认使用该填充,C++端OpenSSL的API也默认支持。 - 编码统一:密文、IV、密钥建议用Base64编码,避免二进制传输的乱码问题。
内容的提问来源于stack exchange,提问作者Dariusz
相关产品推荐
相关产品推荐

