AES-128 CBC 模式下加密流末尾的 Crypto++ 额外块
Crypto++ extra block at the end of encrypted stream in AES-128 CBC mode
我正在尝试在 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 个字节,CryptoPP 会抛出 CryptoPP::InvalidCiphertext
错误:
terminate called after throwing an instance of 'CryptoPP::InvalidCiphertext'
what(): StreamTransformationFilter: invalid PKCS #7 block padding found
I'm not able to understand how the last 16 bytes are generated. If I choose to ignore the last 16 bytes in the decryption, Crypto++ throws InvalidCiphertext
error
最后16个字节是填充。填充由 StreamTransformationFilter
过滤器添加;请参阅手册中的 StreamTransformationFilter Class Reference。虽然不是很明显,DEFAULT_PADDING
是 PKCS_PADDING
对于 ECB_Mode
和 CBC_Mode
。对于其他模式如 OFB_Mode
和 CTR_Mode
,它是 NO_PADDING
。
您只需为加密和解密过滤器指定NO_PADDING
。但是,您必须确保明文和密文是块大小的倍数,对于 AES 是 16。
您可以通过切换到另一种模式来避开块大小限制,例如 CTR_Mode
。但是你必须非常小心密钥或 IV 重用,由于你使用的密码派生方案,这可能很困难。
所以代替:
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink));
使用:
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink), NO_PADDING);
另请参阅维基上的 CBC_Mode on the Crypto++ wiki. You might also be interested in Authenticated Encryption。
为此,您还可以:
#ifndef CRYPTOPP_NO_GLOBAL_BYTE
namespace CryptoPP
{
using byte = unsigned char;
}
#endif
CRYPTOPP_NO_GLOBAL_BYTE
定义在C++17 std::byte
fixes之后。如果未定义 CRYPTOPP_NO_GLOBAL_BYTE
,则 byte
位于全局命名空间中(Crypto++ 5.6.5 及更早版本)。如果定义了 CRYPTOPP_NO_GLOBAL_BYTE
,则 byte
在 CryptoPP
命名空间中(Crypto++ 6.0 及更高版本)。
为此:
std::ofstream testOut("./test.enc", std::ios::binary);
FileSink outSink(testOut);
您还可以这样做:
FileSink outSink("./test.enc");
为此:
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 key derivation version 1"
,另一个标签可能是 "AES iv derivation version 1"
.
该标签将用作 DeriveKey
的 info
参数。你只需要调用它两次,一次调用 key,一次调用 iv.
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
是密码。如果您有 salt
则使用它。否则 HKDF 使用默认盐。
另请参阅 Crypto++ wiki 上的 HKDF。
最后,关于这个:
You can sidestep the blocksize restriction by switching to another mode like CTR_Mode. But then you have to be very careful of key or IV reuse, which may be difficult due to the password derivation scheme you are using.
您也可以考虑使用集成加密方案,例如 Elliptic Curve Integrated Encryption Scheme. It is IND-CCA2,这是一个强大的安全概念。您需要的一切都被打包到加密方案中。
在 ECIES 下,每个用户都会得到一个 public/private 密钥对。然后,使用一个大的随机秘密作为 AES 密钥、iv 和 mac 密钥的种子。明文经过加密和验证。最后,种子在用户的 public 密钥下加密。密码还是用的,不过是用来解密私钥的
我正在尝试在 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 个字节,CryptoPP 会抛出 CryptoPP::InvalidCiphertext
错误:
terminate called after throwing an instance of 'CryptoPP::InvalidCiphertext'
what(): StreamTransformationFilter: invalid PKCS #7 block padding found
I'm not able to understand how the last 16 bytes are generated. If I choose to ignore the last 16 bytes in the decryption, Crypto++ throws
InvalidCiphertext
error
最后16个字节是填充。填充由 StreamTransformationFilter
过滤器添加;请参阅手册中的 StreamTransformationFilter Class Reference。虽然不是很明显,DEFAULT_PADDING
是 PKCS_PADDING
对于 ECB_Mode
和 CBC_Mode
。对于其他模式如 OFB_Mode
和 CTR_Mode
,它是 NO_PADDING
。
您只需为加密和解密过滤器指定NO_PADDING
。但是,您必须确保明文和密文是块大小的倍数,对于 AES 是 16。
您可以通过切换到另一种模式来避开块大小限制,例如 CTR_Mode
。但是你必须非常小心密钥或 IV 重用,由于你使用的密码派生方案,这可能很困难。
所以代替:
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink));
使用:
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink), NO_PADDING);
另请参阅维基上的 CBC_Mode on the Crypto++ wiki. You might also be interested in Authenticated Encryption。
为此,您还可以:
#ifndef CRYPTOPP_NO_GLOBAL_BYTE
namespace CryptoPP
{
using byte = unsigned char;
}
#endif
CRYPTOPP_NO_GLOBAL_BYTE
定义在C++17 std::byte
fixes之后。如果未定义 CRYPTOPP_NO_GLOBAL_BYTE
,则 byte
位于全局命名空间中(Crypto++ 5.6.5 及更早版本)。如果定义了 CRYPTOPP_NO_GLOBAL_BYTE
,则 byte
在 CryptoPP
命名空间中(Crypto++ 6.0 及更高版本)。
为此:
std::ofstream testOut("./test.enc", std::ios::binary);
FileSink outSink(testOut);
您还可以这样做:
FileSink outSink("./test.enc");
为此:
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 key derivation version 1"
,另一个标签可能是 "AES iv derivation version 1"
.
该标签将用作 DeriveKey
的 info
参数。你只需要调用它两次,一次调用 key,一次调用 iv.
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
是密码。如果您有 salt
则使用它。否则 HKDF 使用默认盐。
另请参阅 Crypto++ wiki 上的 HKDF。
最后,关于这个:
You can sidestep the blocksize restriction by switching to another mode like CTR_Mode. But then you have to be very careful of key or IV reuse, which may be difficult due to the password derivation scheme you are using.
您也可以考虑使用集成加密方案,例如 Elliptic Curve Integrated Encryption Scheme. It is IND-CCA2,这是一个强大的安全概念。您需要的一切都被打包到加密方案中。
在 ECIES 下,每个用户都会得到一个 public/private 密钥对。然后,使用一个大的随机秘密作为 AES 密钥、iv 和 mac 密钥的种子。明文经过加密和验证。最后,种子在用户的 public 密钥下加密。密码还是用的,不过是用来解密私钥的