我试图在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
发布于 2019-02-20 18:53:30
我无法理解最后16个字节是如何生成的。如果我选择忽略解密中的最后16个字节,Crypto++将引发
InvalidCiphertext
错误。
最后16个字节是填充。填充是由StreamTransformationFilter
过滤器添加的;参见手册中的StreamTransformationFilter类引用。虽然并不明显,但DEFAULT_PADDING
是ECB_Mode
和CBC_Mode
的PKCS_PADDING
。它是NO_PADDING
,用于其他模式,如OFB_Mode
和CTR_Mode
。
您只需要为加密和解密过滤器指定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);
还请参阅模式上的Crypto++ wiki。您也可能对wiki上的认证加密感兴趣。
为此,您还可以:
#ifndef CRYPTOPP_NO_GLOBAL_BYTE
namespace CryptoPP
{
using byte = unsigned char;
}
#endif
CRYPTOPP_NO_GLOBAL_BYTE
是在修复之后定义的。如果未定义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
的DeriveKey
参数。你只需要叫它两次,一次是为了钥匙,一次是为了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。
最后,关于这一点:
您可以通过切换到另一种模式(如CTR_Mode )来避开块大小限制。但是,您必须非常小心密钥或IV的重用,这可能是困难的,因为您正在使用的密码派生方案。
您还可以考虑使用集成加密方案,如椭圆曲线综合加密方案。它是IND-CCA2,它是一个强有力的安全概念。您需要的一切都被打包到加密方案中。
在ECIES下,每个用户都会得到一个公共/私有键盘。然后,使用一个大的随机秘密作为AES密钥、iv密钥和mac密钥的种子。明文经过加密和身份验证。最后,根据用户的公钥对种子进行加密。仍然使用密码,但用于解密私钥。
https://stackoverflow.com/questions/54791882
复制相似问题