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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:35:36