我正在尝试使用AES-128在CBC模式下加密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
我无法理解最后16个字节是如何生成的。如果我选择忽略解密中的最后16个字节,Crypto ++会抛出
InvalidCiphertext
错误
最后16个字节是填充。填充由qazxswpoi过滤器添加;请参阅手册中的StreamTransformationFilter
。虽然不是很明显,StreamTransformationFilter Class Reference是DEFAULT_PADDING
和PKCS_PADDING
的ECB_Mode
。对于像CBC_Mode
和NO_PADDING
这样的其他模式,它是OFB_Mode
。
您只需要为加密和解密过滤器指定CTR_Mode
。但是,您必须确保明文和密文是块大小的倍数,AES为16。
您可以通过切换到NO_PADDING
等其他模式来回避blocksize限制。但是,您必须非常小心密钥或IV重用,由于您使用的密码派生方案,这可能很难。
所以代替:
CTR_Mode
使用:
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink));
另请参阅Crypto ++ wiki上的CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink), NO_PADDING);
。您可能也对wiki上的CBC_Mode感兴趣。
为此,您还可以:
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
位于全局命名空间(Crypto ++ 5.6.5及更早版本)。如果定义了byte
,则CRYPTOPP_NO_GLOBAL_BYTE
位于byte
命名空间(Crypto ++ 6.0及更高版本)。
为了这:
CryptoPP
你也可以这样做:
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"
的info
参数。你只需要调用它两次,一次用于键,一次用于iv。
DeriveKey
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使用默认盐。
另请参阅Crypto ++ wiki上的salt
。
最后,关于这个:
您可以通过切换到CTR_Mode等其他模式来回避块大小限制。但是,您必须非常小心密钥或IV重用,由于您使用的密码派生方案,这可能很难。
您可能还会考虑使用HKDF等集成加密方案。它是Elliptic Curve Integrated Encryption Scheme,这是一个强大的安全概念。您需要的一切都打包到加密方案中。
在ECIES下,每个用户都获得一个公共/私人密钥对。然后,使用大的随机密钥作为AES密钥,iv和mac密钥的种子。明文经过加密和验证。最后,种子在用户的公钥下加密。密码仍在使用,但它用于解密私钥。