AES-GCM文件加解密程序密钥长度错误的排查与解决
解决CryptoPP AES-GCM的InvalidKeyLength异常及相关问题
首先看你遇到的错误:AES: 12 is not a valid key length,结合代码分析,核心问题出在密钥未正确生成和salt未持久化两个点,下面是具体问题和修复方案:
核心问题分析
- 解密函数中密钥未初始化:
decryptFile函数声明了byte key[32],但完全没有调用deriveKeyAndIV生成密钥,key数组是栈上的垃圾数据。调用SetKeyWithIV时,垃圾数据可能触发CryptoPP错误的密钥长度检查(即使sizeof(key)是32,实际数据的异常字节会导致库误判)。 - salt未写入加密文件:
deriveKeyAndIV中生成的随机salt是密钥派生的关键,加密时必须把salt保存到输出文件,否则解密时无法生成和加密一致的密钥,直接导致密钥不匹配。 - 加密器输出文件路径错误:
encryptFile中AuthenticatedEncryptionFilter的FileSink写的是inputFile.c_str(),这会覆盖原明文文件,完全是逻辑错误。 - 解密器TAG位置标志错误:解密时用了
MAC_AT_BEGIN,但加密时TAG是写在密文末尾的,应该用MAC_AT_END。
分步修复方案
1. 修改密钥派生函数,支持传入salt
把deriveKeyAndIV改成接收外部salt,避免加密和解密时salt不一致:
void deriveKeyAndIV(const std::string &password, byte *key, size_t keySize, byte *iv, size_t ivSize, const byte* salt, size_t saltSize) { CryptoPP::SecByteBlock derived(keySize + ivSize); CryptoPP::PKCS5_PBKDF2_HMAC <CryptoPP::SHA256> kdf; kdf.DeriveKey(derived.data(), derived.size(), 0, reinterpret_cast<const byte*>(password.data()), password.size(), salt, saltSize, 1000000); std::memcpy(key, derived.data(), keySize); std::memcpy(iv, derived.data() + keySize, ivSize); std::cout << "Derived key size: " << keySize << std::endl; std::cout << "Derived iv size: " << ivSize << std::endl; }
2. 修复加密函数,保存salt和IV到输出文件
在encryptFile中生成salt,写入输出文件,同时修正输出路径错误:
void encryptFile(const std::string &password, const std::string &inputFile, const std::string &outputFile) { const size_t keySize = 32; const size_t ivSize = 12; const size_t TAG_SIZE = 16; const size_t SALT_SIZE = 16; byte key[32], iv[12]; CryptoPP::SecByteBlock salt(SALT_SIZE); CryptoPP::AutoSeededRandomPool prng; prng.GenerateBlock(salt, SALT_SIZE); // 派生密钥和IV deriveKeyAndIV(password, key, keySize, iv, ivSize, salt.data(), salt.size()); printHex(key, sizeof(key), "Encryption Key"); printHex(iv, sizeof(iv), "IV"); printHex(salt.data(), salt.size(), "Salt"); // 先写入salt和IV到加密文件头部 CryptoPP::FileSink headerSink(outputFile.c_str(), true); headerSink.Put(salt.data(), salt.size()); headerSink.Put(iv, sizeof(iv)); headerSink.MessageEnd(); // 初始化AES-GCM加密器 CryptoPP::GCM<CryptoPP::AES>::Encryption encryption; encryption.SetKeyWithIV(key, sizeof(key), iv, sizeof(iv)); // 修正输出文件路径:用outputFile而非inputFile CryptoPP::AuthenticatedEncryptionFilter aef(encryption, new CryptoPP::FileSink(outputFile.c_str(), true), false, TAG_SIZE); aef.ChannelPut(CryptoPP::AAD_CHANNEL, iv, sizeof(iv)); // 读取明文并加密 CryptoPP::FileSource fs(inputFile.c_str(), false); byte buffer[4096]; while (true) { size_t bytesRead = fs.Get(buffer, sizeof(buffer)); if (bytesRead == 0) break; aef.ChannelPut(CryptoPP::DEFAULT_CHANNEL, buffer, bytesRead); } aef.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL); aef.ChannelMessageEnd(CryptoPP::AAD_CHANNEL); aef.MessageEnd(); }
3. 修复解密函数,读取salt并生成正确密钥
在decryptFile中先读取salt,再派生密钥,修正TAG位置标志:
void decryptFile(const std::string &password, const std::string &inputFile, const std::string &outputFile) { const size_t keySize = 32; const size_t ivSize = 12; const size_t TAG_SIZE = 16; const size_t SALT_SIZE = 16; byte key[32], iv[12]; byte salt[SALT_SIZE]; CryptoPP::FileSource fs(inputFile.c_str(), false); // 先读取salt fs.Pump(SALT_SIZE); fs.Get(salt, SALT_SIZE); // 再读取IV fs.Pump(ivSize); fs.Get(iv, ivSize); // 派生解密密钥 deriveKeyAndIV(password, key, keySize, iv, ivSize, salt, SALT_SIZE); printHex(key, sizeof(key), "Decryption Key"); printHex(iv, sizeof(iv), "Decryption IV"); // 读取整个加密文件内容,拆分密文和TAG std::string cipherText; CryptoPP::FileSource(inputFile.c_str(), true, new CryptoPP::StringSink(cipherText)); // 跳过salt和IV,剩下的是密文 + TAG size_t headerSize = SALT_SIZE + ivSize; std::string enc = cipherText.substr(headerSize, cipherText.length() - headerSize - TAG_SIZE); std::string mac = cipherText.substr(cipherText.length() - TAG_SIZE); // 初始化AES-GCM解密器 CryptoPP::GCM<CryptoPP::AES>::Decryption decryption; decryption.SetKeyWithIV(key, sizeof(key), iv, sizeof(iv)); // 修正TAG位置标志为MAC_AT_END CryptoPP::AuthenticatedDecryptionFilter adf(decryption, new CryptoPP::FileSink(outputFile.c_str()), CryptoPP::AuthenticatedDecryptionFilter::MAC_AT_END | CryptoPP::AuthenticatedDecryptionFilter::THROW_EXCEPTION, TAG_SIZE); adf.ChannelPut(CryptoPP::AAD_CHANNEL, iv, sizeof(iv)); adf.ChannelPut(CryptoPP::DEFAULT_CHANNEL, reinterpret_cast<const byte*>(enc.data()), enc.size()); adf.ChannelPut(CryptoPP::DEFAULT_CHANNEL, reinterpret_cast<const byte*>(mac.data()), mac.size()); adf.ChannelMessageEnd(CryptoPP::AAD_CHANNEL); adf.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL); bool result = adf.GetLastResult(); if (!result) { throw CryptoPP::Exception(CryptoPP::Exception::OTHER_ERROR, "MAC check failed"); } }
4. 启用main函数的异常捕获
把main中注释掉的try-catch块打开,方便调试错误:
int main() { std::cout << "Starting program...." << std::endl; std::string password; std::cout << "Enter password: "; std::cin >> password; std::string inputFile; std::string outputFile; try{ char choice; std::cout << "Do you want to encrypt or decrypt? (e/d): "; std::cin >> choice; if (choice == 'e') { std::cout << "Enter the input file name for encryption: "; std::cin >> inputFile; outputFile = inputFile + ".enc"; encryptFile(password, inputFile, outputFile); std::cout << inputFile << " was encrypted successfully." << std::endl; } else if (choice == 'd') { std::cout << "Enter the input file name for decryption: "; std::cin >> inputFile; outputFile = inputFile + ".dec"; decryptFile(password, inputFile, outputFile); std::cout << inputFile << " was decrypted successfully." << std::endl; } else { std::cout << "Invalid choice. Please enter 'e' for encryption or 'd' for decryption." << std::endl; } } catch (const std::exception &e) { std::cerr << "An Error occured: " << e.what() << std::endl; return 1; } std::cout << "Program ended" << std::endl; return 0; }
验证修复效果
修复后,加密时会把salt(16字节)、IV(12字节)写入加密文件头部,接着是密文和TAG(16字节)。解密时先读取salt和IV,生成和加密一致的密钥,再完成解密和MAC验证。此时SetKeyWithIV的密钥是合法的32字节AES-256密钥,不会再抛出InvalidKeyLength异常。
内容的提问来源于stack exchange,提问作者Ethan
相关产品推荐
相关产品推荐

