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

AES-GCM文件加解密程序密钥长度错误的排查与解决

解决CryptoPP AES-GCM的InvalidKeyLength异常及相关问题

首先看你遇到的错误:AES: 12 is not a valid key length,结合代码分析,核心问题出在密钥未正确生成和salt未持久化两个点,下面是具体问题和修复方案:

核心问题分析

  1. 解密函数中密钥未初始化:decryptFile函数声明了byte key[32],但完全没有调用deriveKeyAndIV生成密钥,key数组是栈上的垃圾数据。调用SetKeyWithIV时,垃圾数据可能触发CryptoPP错误的密钥长度检查(即使sizeof(key)是32,实际数据的异常字节会导致库误判)。
  2. salt未写入加密文件:deriveKeyAndIV中生成的随机salt是密钥派生的关键,加密时必须把salt保存到输出文件,否则解密时无法生成和加密一致的密钥,直接导致密钥不匹配。
  3. 加密器输出文件路径错误:encryptFile中AuthenticatedEncryptionFilter的FileSink写的是inputFile.c_str(),这会覆盖原明文文件,完全是逻辑错误。
  4. 解密器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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 21:43:10