CryptoPP AES加密工具命令行传参解密失败问题排查
问题描述
我用C++结合CryptoPP库开发磁盘加密工具,采用AES算法。密码和salt硬编码时程序正常,但通过命令行传入这两个参数时,解密后的明文和原明文不符,解密失败。以下是代码、执行命令及输出结果:
代码
#include "cryptlib.h" #include "rijndael.h" #include "modes.h" #include "files.h" #include "osrng.h" #include "hex.h" #include <iostream> #include <string> #include <Windows.h> #include <typeinfo> using namespace CryptoPP; SecByteBlock generateRandomKey(int size = 32) { AutoSeededRandomPool prng; SecByteBlock key(size); prng.GenerateBlock(key, key.size()); return key; } SecByteBlock generateRandomSalt(int length = 32) { AutoSeededRandomPool prng; SecByteBlock salt(length); prng.GenerateBlock(salt, salt.size()); return salt; } SecByteBlock generateKey(std::string& password, std::string& salt) { SecByteBlock key(32); HKDF<SHA256> hkdf; hkdf.DeriveKey(key, key.size(), (const byte*)password.data(), password.size(), (const byte*)salt.data(), salt.size(), NULL, 0); return key; } SecByteBlock generateSalt(std::string& salt_string) { std::string s1(salt_string); SecByteBlock salt((const byte*)s1.data(), s1.size()); return salt; } std::string encrytPlainText(std::string plainText, SecByteBlock key, SecByteBlock salt) { std::string cipherText; try { CBC_Mode< AES >::Encryption e; e.SetKeyWithIV(key, key.size(), salt); StringSource s(plainText, true, new StreamTransformationFilter(e, new StringSink(cipherText) ) // StreamTransformationFilter ); // StringSource return cipherText; } catch (const Exception& e) { std::cerr << e.what() << std::endl; exit(1); } } std::string decrytCipherText(std::string cipherText, SecByteBlock key, SecByteBlock salt) { std::string plainText; try { CBC_Mode< AES >::Decryption d; d.SetKeyWithIV(key, key.size(), salt); StringSource s(cipherText, true, new StreamTransformationFilter(d, new StringSink(plainText), CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme::ZEROS_PADDING ) // StreamTransformationFilter ); // StringSource return plainText; } catch (const Exception& e) { std::cerr << e.what() << std::endl; std::cerr << "decryption failed" << std::endl; exit(1); } } int main(int argc, char * argv[]) { std::string password = argv[1], salt1 = argv[2]; HexEncoder encoder(new FileSink(std::cout)); SecByteBlock key = generateKey(password, salt1); SecByteBlock salt = generateSalt(salt1); std::string plainText = "An encryption algo", cipherText = encrytPlainText(plainText, key, salt); std::string decryptedCipherText = decrytCipherText(cipherText, key, salt); bool decryptionWasSuccessful = decryptedCipherText == plainText; std::cout << "Key:"; encoder.Put(key, key.size()); std::cout << std::endl; std::cout << "Salt:"; encoder.Put(salt, salt.size()); std::cout << std::endl; std::cout << "Plain Text: " << plainText << ":" << decryptedCipherText << std::endl; std::cout << "Cipher Text: " << cipherText << std::endl; std::cout << "Decryption was successful: " << std::boolalpha << decryptionWasSuccessful << std::endl; int hello; std::cin >> hello; return 0; }
执行命令
DiskEncryptionUtility.exe password salt
输出结果
Key:064A9F5B670FA9D96EF9D2F3E89336ED129F989B75115C0DDE61DBF83EF21760 Salt:73616C74 Plain Text: An encryption algo:An eùbryb♥αÉù;a∞go♫♫♫♫♫♫♫♫♫♫♫♫♫♫ Cipher Text: ╩└o┌Θ╩⌡|♫Σo∙qÆ¢g╬DûO▒îÇ ↑Åσâ)╗-♂ Decryption was successful: false
问题原因及修复方案
1. 加密和解密填充方式不一致
加密时StreamTransformationFilter默认使用PKCS#7填充,但解密时你指定了ZEROS_PADDING,两者不匹配导致解密后数据格式错乱。
修复:移除解密函数中的ZEROS_PADDING参数,和加密保持一致的填充逻辑:
std::string decrytCipherText(std::string cipherText, SecByteBlock key, SecByteBlock salt) { std::string plainText; try { CBC_Mode< AES >::Decryption d; d.SetKeyWithIV(key, key.size(), salt); StringSource s(cipherText, true, new StreamTransformationFilter(d, new StringSink(plainText) ) // StreamTransformationFilter ); // StringSource return plainText; } catch (const Exception& e) { std::cerr << e.what() << std::endl; std::cerr << "decryption failed" << std::endl; exit(1); } }
2. AES-CBC的IV长度不符合算法要求
AES的块大小固定为16字节(128位),但你将命令行传入的salt(仅4字节)直接作为IV使用,长度不足会导致CryptoPP自动补0,且加密和解密的补全逻辑可能出现偏差,引发解密错误。
修复:严格区分密钥派生用的盐和加密模式用的IV,IV必须等于AES块大小。建议加密时生成随机IV,将IV前缀到密文中,解密时先提取IV:
// 修改加密函数,生成随机IV并附加到密文开头 std::string encrytPlainText(std::string plainText, SecByteBlock key) { std::string cipherText; AutoSeededRandomPool prng; SecByteBlock iv(AES::BLOCKSIZE); prng.GenerateBlock(iv, iv.size()); try { CBC_Mode< AES >::Encryption e; e.SetKeyWithIV(key, key.size(), iv); // 先写入IV StringSource s(iv, iv.size(), true, new StringSink(cipherText)); // 再写入加密内容 StringSource s2(plainText, true, new StreamTransformationFilter(e, new StringSink(cipherText)) ); return cipherText; } catch (const Exception& e) { std::cerr << e.what() << std::endl; exit(1); } } // 修改解密函数,先提取IV再解密 std::string decrytCipherText(std::string cipherText, SecByteBlock key) { std::string plainText; try { // 从密文开头提取IV SecByteBlock iv(AES::BLOCKSIZE); memcpy(iv, cipherText.data(), AES::BLOCKSIZE); // 剩余部分为实际密文 std::string realCipher = cipherText.substr(AES::BLOCKSIZE); CBC_Mode< AES >::Decryption d; d.SetKeyWithIV(key, key.size(), iv); StringSource s(realCipher, true, new StreamTransformationFilter(d, new StringSink(plainText)) ); return plainText; } catch (const Exception& e) { std::cerr << e.what() << std::endl; std::cerr << "decryption failed" << std::endl; exit(1); } } // 主函数对应修改 int main(int argc, char * argv[]) { std::string password = argv[1], salt_str = argv[2]; HexEncoder encoder(new FileSink(std::cout)); SecByteBlock key = generateKey(password, salt_str); std::string plainText = "An encryption algo", cipherText = encrytPlainText(plainText, key); std::string decryptedCipherText = decrytCipherText(cipherText, key); bool decryptionWasSuccessful = decryptedCipherText == plainText; std::cout << "Key:"; encoder.Put(key, key.size()); std::cout << std::endl; std::cout << "Salt (from command line):"; encoder.Put((const byte*)salt_str.data(), salt_str.size()); std::cout << std::endl; std::cout << "Plain Text: " << plainText << ":" << decryptedCipherText << std::endl; std::cout << "Decryption was successful: " << std::boolalpha << decryptionWasSuccessful << std::endl; int hello; std::cin >> hello; return 0; }
3. Windows命令行编码问题(可选)
Windows命令行默认使用GBK编码,直接将argv转为std::string可能导致特殊字符/中文参数的字节错误。如果需要支持这类场景,需将命令行参数转换为UTF-8:
#include <locale> #include <codecvt> // GBK转UTF-8 std::string gbkToUtf8(const std::wstring& gbkStr) { std::wstring_convert<std::codecvt_utf8<wchar_t>> conv; return conv.to_bytes(gbkStr); } // 主函数改用宽字符参数 int main(int argc, wchar_t * argv[]) { std::string password = gbkToUtf8(argv[1]); std::string salt_str = gbkToUtf8(argv[2]); // 剩余代码不变 }
内容的提问来源于stack exchange,提问作者jamie_0101
相关产品推荐
相关产品推荐

