C++ 加密++;AES-128 CBC模式下加密流末尾的额外块

C++ 加密++;AES-128 CBC模式下加密流末尾的额外块,c++,crypto++,C++,Crypto++,我试图在CBC模式下使用AES-128加密320字节的二进制数据,并将密码存储到一个文件中。输出文件应该是320字节,但我得到了336字节。这是我的密码: #include <iostream> #include <fstream> #include <crypto++/aes.h> #include <crypto++/modes.h> #include <crypto++/base64.h> #include <crypto+

我试图在CBC模式下使用AES-128加密320字节的二进制数据,并将密码存储到一个文件中。输出文件应该是320字节,但我得到了336字节。这是我的密码:

#include <iostream>
#include <fstream>
#include <crypto++/aes.h>
#include <crypto++/modes.h>
#include <crypto++/base64.h>
#include <crypto++/sha.h>
#include <cryptopp/osrng.h>
#include <crypto++/filters.h>
#include <crypto++/files.h>

namespace CryptoPP
{
    using byte = unsigned char;
}

void myAESTest()
{
    std::string password = "testPassWord";

    // hash the password string
    // -------------------------------
    CryptoPP::byte key[CryptoPP::AES::DEFAULT_KEYLENGTH], iv[CryptoPP::AES::BLOCKSIZE];
    CryptoPP::byte passHash[CryptoPP::SHA256::DIGESTSIZE];
    CryptoPP::SHA256().CalculateDigest(passHash, (CryptoPP::byte*) password.data(), password.size());
    std::memcpy(key, passHash, CryptoPP::AES::DEFAULT_KEYLENGTH);
    std::memcpy(iv, passHash+CryptoPP::AES::DEFAULT_KEYLENGTH, CryptoPP::AES::BLOCKSIZE);

    // encrypt
    // ---------------------------------
    int chunkSize = 20*CryptoPP::AES::BLOCKSIZE;

    CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption encryptor;
    encryptor.SetKeyWithIV(key, sizeof(key), iv);

    std::ofstream testOut("./test.enc", std::ios::binary);
    CryptoPP::FileSink outSink(testOut);

    CryptoPP::byte message[chunkSize];

    CryptoPP::StreamTransformationFilter stfenc(encryptor, new CryptoPP::Redirector(outSink));

    for(int i = 0; i < chunkSize; i ++)
    {
        message[i] = (CryptoPP::byte)i;
    }

    stfenc.Put(message, chunkSize);
    stfenc.MessageEnd();
    testOut.close();

    // decrypt
    // ------------------------------------
    // Because of some unknown reason increase chuksize by 1 block
    // chunkSize+=16;

    CryptoPP::byte cipher[chunkSize], decrypted[chunkSize];

    CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decryptor;
    decryptor.SetKeyWithIV(key, sizeof(key), iv);

    std::ifstream inFile("./test.enc", std::ios::binary);
    inFile.read((char *)cipher, chunkSize);

    CryptoPP::ArraySink decSink(decrypted, chunkSize);
    CryptoPP::StreamTransformationFilter stfdec(decryptor, new CryptoPP::Redirector(decSink));

    stfdec.Put(cipher, chunkSize);
    stfdec.MessageEnd();
    inFile.close();

    for(int i = 0; i < chunkSize; i++)
    {
        std::cout << (int)decrypted[i] << ' ';
    }
    std::cout << std::endl;
}

int main(int argc, char* argv[]) 
{
    myAESTest();
    return 0;
}
我无法理解最后16个字节是如何生成的。如果我选择忽略解密中的最后16个字节,Crypto++将抛出
InvalidCiphertext
错误

最后16个字节是填充。填充由
StreamTransformationFilter
过滤器添加;请参阅手册中的。虽然不明显,
ECB\u模式和
CBC\u模式的默认填充
PKCS\u填充
。对于其他模式,如B_模式
和CTR_模式,它是
无填充

您只需为加密和解密筛选器指定
无填充
。但是,您必须确保明文和密文是块大小的倍数,AES为16

通过切换到另一种模式,如
CTR\u模式
,可以避开块大小限制。但是,您必须非常小心密钥或IV重用,这可能很困难,因为您使用的是密码派生方案

因此,不是:

CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink));
CRYPTOPP\u NO\u GLOBAL\u字节
在后定义。如果未定义
CRYPTOPP\u NO\u GLOBAL\u BYTE
,则
BYTE
位于全局命名空间中(Crypto++5.6.5及更早版本)。如果定义了
CRYPTOPP\u NO\u GLOBAL\u BYTE
,则
BYTE
位于
CRYPTOPP
命名空间(Crypto++6.0及更高版本)中


为此:

std::ofstream testOut("./test.enc", std::ios::binary);
FileSink outSink(testOut);
SHA256().CalculateDigest(passHash, (byte*) password.data(), password.size());
std::memcpy(key, passHash, AES::DEFAULT_KEYLENGTH);
std::memcpy(iv, passHash+AES::DEFAULT_KEYLENGTH, AES::BLOCKSIZE);
您还可以执行以下操作:

FileSink outSink("./test.enc");

为此:

std::ofstream testOut("./test.enc", std::ios::binary);
FileSink outSink(testOut);
SHA256().CalculateDigest(passHash, (byte*) password.data(), password.size());
std::memcpy(key, passHash, AES::DEFAULT_KEYLENGTH);
std::memcpy(iv, passHash+AES::DEFAULT_KEYLENGTH, AES::BLOCKSIZE);

您可以考虑使用<代码> HKDF 作为派生函数。使用一个密码,但使用两个不同的标签,以确保独立的派生。一个标签可能是字符串

“AES密钥派生版本1”
,另一个标签可能是
“AES iv派生版本1”

标签将用作
DeriveKey
info
参数。你只需要打两次电话,一次是钥匙,一次是静脉注射

unsigned int DeriveKey (byte *derived, size_t derivedLen,
    const byte *secret, size_t secretLen,
    const byte *salt, size_t saltLen,
    const byte *info, size_t infoLen) const
secret
是密码。如果您有
,请使用它。否则HKDF使用默认的salt

另请参见Crypto++wiki上的


最后,关于这一点:

您可以通过切换到其他模式(如CTR_模式)来避开块大小限制。但是,您必须非常小心密钥或IV重用,这可能很困难,因为您使用的是密码派生方案

您也可以考虑一个集成加密方案,比如。是的,这是一种强烈的安全观念。您需要的一切都打包到加密方案中

在ECIES下,每个用户获得一个公钥/私钥对。然后,使用一个大的随机密钥作为AES密钥、iv密钥和mac密钥的种子。明文是加密和认证的。最后,在用户的公钥下对种子进行加密。密码仍在使用,但用于解密私钥

unsigned int DeriveKey (byte *derived, size_t derivedLen,
    const byte *secret, size_t secretLen,
    const byte *salt, size_t saltLen,
    const byte *info, size_t infoLen) const