I'm using the Crypto++ library for C++ to encrypt 128-bit messages with their public key and my private key from disk. However, when I call my function the program terminates with the error message:
terminate called after throwing an instance of
'CryptoPP::InvalidArgument' what(): RSA/OAEP-MGF1(SHA-1): message
length of 256 exceeds the maximum of 214 for this public key
I'm not sure why it thinks my messages are 256-bit when they are clearly 128-bit. Below is my code:
std::string encryptWithBothKeys(std::string key_name1, std::string key_name2, std::string plain){
std::string cipher1, cipher2;
AutoSeededRandomPool rng;
RSA::PublicKey pub_key;
RSA::PrivateKey priv_key;
loadPublicKey(key_name1, pub_key);
loadPrivateKey(key_name2, priv_key);
RSAES_OAEP_SHA_Encryptor e_pub(pub_key);
RSAES_OAEP_SHA_Encryptor e_priv(priv_key);
StringSource ss1(plain, true,
new PK_EncryptorFilter(rng, e_pub,
new StringSink(cipher1)
)
);
StringSource ss2(cipher1, true,
new PK_EncryptorFilter(rng, e_priv,
new StringSink(cipher2)
)
);
return cipher2;
}
void load(const std::string& filename, BufferedTransformation& bt){
FileSource file(filename.c_str(), true /*pumpAll*/);
file.TransferTo(bt);
bt.MessageEnd();
}
void loadPrivateKey(const std::string& filename, PrivateKey& key){
ByteQueue queue;
load(filename, queue);
key.Load(queue);
}
void loadPublicKey(const std::string& filename, PublicKey& key){
ByteQueue queue;
load(filename, queue);
key.Load(queue);
}
And in the main function I call it like this:
std::string encrypted_msg = encryptWithBothKeys("their.pub", "my.key", "0123456789ABCDEF");
Any suggestions?
EDIT: Turns out the ciphertext after the first encryption is size 256. I guess I need to figure out why it's increasing the size.
I'm not sure why it thinks my messages are 256-bit when they are clearly 128-bit. Below is my code:
Those are bytes, not bits.
What size RSA modulus are you using? I'm guessing its a 2048-bit key, which is 256 bytes, which leaves ≈214 bytes due to OAEP padding.
So the question becomes, what is the size of plain and cipher1 during this:
StringSource ss1(plain, true,
new PK_EncryptorFilter(rng, e_pub,
new StringSink(cipher1)
)
);
My guess is plain1 is small enough. However, when its padded and encrypted, it expands to 256 bytes as cipher1. So the message cipher1 is too large when this is executed:
StringSource ss2(cipher1, true,
new PK_EncryptorFilter(rng, e_priv,
new StringSink(cipher2)
)
);
Also, should the above be using PK_DecryptorFilter? Are you certain you want to encrypt again? If so, then why are you using e_priv?
Encrypting with the private key is not a valid cryptographic operation. If you want the "encrypt with the private key" thing, it usually means you want a Probabilistic Signature Scheme with Recovery (PSSR). Crypto++ supplies a couple of them.
Related
I am trying to perform an encryption and decryption in Crypto++ library using AES128 cipher with CCM mode of operation. I have a problem when I try to encrypt string longer than 16777215 bytes.
My code:
const int TAG_SIZE = 8;
CCM< AES, TAG_SIZE >::Encryption e;
CCM< AES, TAG_SIZE >::Decryption d;
e.SetKeyWithIV( key, sizeof(key), iv, sizeof(iv) );
e.SpecifyDataLengths( 0, plain.size(), 0 );
//Encryption
StringSource ss1(
plain,
true,
new AuthenticatedEncryptionFilter(
e,
new StringSink(cipher)
)
);
d.SetKeyWithIV( key, sizeof(key), iv, sizeof(iv) );
d.SpecifyDataLengths( 0, plain.size(), 0 );
//Decryption
AuthenticatedDecryptionFilter df( d,
new StringSink(recovered)
);
StringSource ss2(
cipher,
true,
new Redirector(df)
);
When I try to encrypt/decrypt a plaintext which is size of a CD (737280000), I get the following error:
"terminate called after throwing an instance of 'CryptoPP::InvalidArgument'
what(): AES/CCM: message length 737280000 exceeds the maximum of 16777215"
My question is, how do I encrypt/decrypt a plaintext which is longer than 16777215 bytes?
My question is, how do I encrypt/decrypt a plaintext which is longer than 16777215 bytes?
CCM mode is specified in NIST SP800-38c. Section A.1, Length Requirements, discusses maximum plain text under a security context. A security context is the {key, iv} combination (with some hand waiving).
I believe you have three choices. First, you can increase the length of the IV. The larger the iv, the more plain text you can encrypt. The max iv length is 13 so it does not scale up forever.
Second, you have to re-key or change the iv before you hit the maximum plain text under the context. You can find the maximum plain text length using MaxMessageLength(). Crypto++ tracks the number of bytes processed via m_totalMessageLength, but it is not exposed to user programs. You will have to track it yourself.
Third, you can change algorithms. An algorithm like ChaCha20Poly1305 allows you to encrypt 2^38-1 64-byte blocks. That is just under 2^44 bytes or about 256 GB. You should be safe with ChaCha20Poly1305.
Crypto++ tells you the maximum number of bytes via MaxMessageLength(). In the case of CCM it is based on the iv length, and tracked through m_L in the code below.
lword MaxMessageLength() const
{return m_L<8 ? (W64LIT(1)<<(8*m_L))-1 : W64LIT(0)-1;}
MaxMessageLength() is used in authenc.cpp. ProcessData() throws an exception when the limit is hit:
if (m_state >= State_IVSet && length > MaxMessageLength()-m_totalMessageLength)
throw InvalidArgument(AlgorithmName() + ": message length exceeds maximum");
m_totalMessageLength += length;
const int TAG_SIZE = 8;
CCM< AES, TAG_SIZE >::Encryption e;
CCM< AES, TAG_SIZE >::Decryption d;
Your tag size is a tad bit on the small side. You might want to use the maximum size, if your protocol allows it.
I recommend you switch algorithms. CCM is a bastard mode that got standardized in the early 2000's through some Wireless Working Group. Then, NIST adopted it because it was already standardized.
At the time CCM was standardized there were better Authenticated Encryption modes available, like CWC, OCB, EAX and GCM. Unfortunately the damage was done. And now you have algorithms like Bernstein's ChaChaPoly1305.
You might also want to checkout AEAD Comparison on the Crypto++ wiki. The comparison shows CCM is about the worst of the authenticated encryption modes.
I'm trying to encrypt 320 bytes of binary data using AES-128 in CBC mode and store the cipher into a file. The output file should have been of 320 bytes, but I got 336 bytes. Here is my code:
#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;
}
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, CryptoPP throws CryptoPP::InvalidCiphertext error:
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
The last 16 bytes are padding. Padding is added by the StreamTransformationFilter filter; see StreamTransformationFilter Class Reference in the manual. Though not obvious, DEFAULT_PADDING is PKCS_PADDING for ECB_Mode and CBC_Mode. It is NO_PADDING for other modes like OFB_Mode and CTR_Mode.
You only need to specify NO_PADDING for both the encryption and decryption filters. However, you have to ensure the plaintext and ciphertext are a multiple of the blocksize, which is 16 for AES.
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.
So instead of:
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink));
Use:
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink), NO_PADDING);
Also see CBC_Mode on the Crypto++ wiki. You might also be interested in Authenticated Encryption on the wiki.
For this, you can also:
#ifndef CRYPTOPP_NO_GLOBAL_BYTE
namespace CryptoPP
{
using byte = unsigned char;
}
#endif
CRYPTOPP_NO_GLOBAL_BYTE is defined after the C++17 std::byte fixes. If CRYPTOPP_NO_GLOBAL_BYTE is not defined, then byte is in the global namespace (Crypto++ 5.6.5 and earlier). If CRYPTOPP_NO_GLOBAL_BYTE is defined, then byte is in the CryptoPP namespace (Crypto++ 6.0 and later).
For this:
std::ofstream testOut("./test.enc", std::ios::binary);
FileSink outSink(testOut);
You can also do:
FileSink outSink("./test.enc");
For this:
SHA256().CalculateDigest(passHash, (byte*) password.data(), password.size());
std::memcpy(key, passHash, AES::DEFAULT_KEYLENGTH);
std::memcpy(iv, passHash+AES::DEFAULT_KEYLENGTH, AES::BLOCKSIZE);
You might consider using HKDF as a derivation function. Use the one password but two different labels to ensure independent derivation. One label might be the string "AES key derivation version 1" and the other label might be "AES iv derivation version 1".
The label would be used as the info parameter for DeriveKey. You just need to call it twice, once for the key and once for the 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 is the password. If you have salt then use it. Otherwise HKDF uses a default salt.
Also see HKDF on the Crypto++ wiki.
Finally, regarding this:
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.
You might also consider an Integrated Encryption Scheme, like Elliptic Curve Integrated Encryption Scheme. It is IND-CCA2, which is a strong notion of security. Everything you need is packaged into the encryption scheme.
Under ECIES each user gets a public/private keypair. Then, a large random secret is used as a seed for the AES key, iv and mac key. The the plaintext is encrypted and authenticated. Finally, the seed is encrypted under the user's public key. The password is still used, but it is used to decrypt the private key.
I have searched a lot for this issue but did not find any solution. In my current project, I have to work on encrypting images with a sender receiver form. So i have to generate a key in the sender part to encrypt the file, and i have to use the same key (which is passed as an argument to the main) to get the original data, to continue program execution.
I save the key on a text file:
void GetKeyAndIv() {
// Initialize the key and IV
prng.GenerateBlock( key, key.size() );
prng.GenerateBlock(iv, iv.size());
};
/*********************Begin of the Function***********************/
//Function encrypt a file (original file) and store the result in another file (encrypted_file)
void Encrypt(std::string original_file, std::string encrypted_file_hex,string encrypted_file,string binary) {
ofstream out;
out.open("Key.txt");
out.clear();
out<<"key = "<< key<<endl;
out<<"iv = "<< iv<<endl;
string cipher, encoded;
//Getting the encryptor ready
CBC_Mode< CryptoPP::AES >::Encryption e;
e.SetKeyWithIV( key, key.size(), iv );
try
{
ifstream infile(original_file.c_str(), ios::binary);
ifstream::pos_type size = infile.seekg(0, std::ios_base::end).tellg();
infile.seekg(0, std::ios_base::beg);
//read the original file and print it
string temp;
temp.resize(size);
infile.read((char*)temp.data(), temp.size());
infile.close();
// Encryption
CryptoPP::StringSource ss( temp, true,
new CryptoPP::StreamTransformationFilter( e,
new CryptoPP::StringSink( cipher )//,
//CryptoPP::BlockPaddingSchemeDef::NO_PADDING
) // StreamTransformationFilter
); // StringSource
std::ofstream outfile1(encrypted_file.c_str(),ios::out | ios::binary);
outfile1.write(cipher.c_str() , cipher.size());
}
catch( const CryptoPP::Exception& e )
{
cout <<"Encryption Error:\n" <<e.what() << endl;
system("pause");
exit(1);
}
Then i pass it to the client side using the following code:
int main(int argc, char* argv[])
{
.....
string s1=argv[7];
SecByteBlock b1(reinterpret_cast<const byte*>(&s1[0]), s1.size());
string s2=argv[8];
SecByteBlock iv1(reinterpret_cast<const byte*>(&s2[0]), s2.size());
.....
}
I got an error while trying to decrypt the file, using the following code
void Decrypt(std::string encrypted_file,SecByteBlock key,SecByteBlock iv,string decrypted_file) {
string recovered;
try
{
// Read the encrypted file contents to a string as binary data.
std::ifstream infile(encrypted_file.c_str(), std::ios::binary);
const std::string cipher_text((std::istreambuf_iterator<char>(infile)),
std::istreambuf_iterator<char>());
infile.close();
CBC_Mode< CryptoPP::AES >::Decryption d;
d.SetKeyWithIV( key, key.size(), iv );
Decryption Error:
StreamTransformationFilter: invalid PKCS #7 block padding found
Which means i have different key during decryption process. Why this happened, and if anyone can help solving this issue.
It happen if the key used for decryption in not the same as the key that has been used for encryption.
During decryption, in PKCS#7 mode, just after decrypting the last block of 16 bytes, there is a check of padding bytes in order to know the original length of the message (which is not necessary a multiple of 16 bytes) : the last byte should be 0x01, or the last two bytes should be equal to 0x02, or the last three bytes should be equal to 0x03, ... When the decryption key is not the same as the encryption key, the padding bytes are not decrypted correctly and this implies a PKCS#7 block padding error when decrypting.
I change the CBC_Mode to another modes instead, ODB_Mode work for me
What if I want to encrypt data, using the Crypto++ library and having a user-defined password that is shorter than 32 Byte?
Right now I have the following code:
byte passwordBytes[AES::MAX_KEYLENGTH];
byte ivBytes[AES::BLOCKSIZE];
std::string textToEncrypt("encryptMe");
std::string aesKey("passwordFromUser");
std::string ivText("Iv16BytesOfText...");
memset(passwordBytes, 0, sizeof(passwordBytes)); //fill with zeroes first
memcpy(passwordBytes, aesKey.data(), aesKey.size()); //fill with key data
memcpy(ivBytes, ivText.data(), CryptoPP::AES::BLOCKSIZE); //fill iv bytes
CTR_Mode<AES>::Encryption encryption;
encryption.SetKeyWithIV(passwordBytes, sizeof(passwordBytes), ivBytes);
StringSource encryptor(textToEncrypt, true,
new StreamTransformationFilter(encryption,
new StringSink(verschluesselterText)
,StreamTransformationFilter::NO_PADDING
)
);
As you can see, aesKey is shorter than 32 Bytes.
To apply the full 32 Bytes to the encrypting function, I just fill out the unused space with zeroes, but that doesn't seem to be the best solution to me.
Am I missing something regarding creating an AES Key? With a user-defined password?
My second question, what if the user chooses a password that is longer then 32 Byte? In My case, the password would be truncated, which doesn't sound right to me.
Thanks for your help!
What if i want to encrypt data, using the Crypto++ library and having a user defined password that is shorter then 32 Byte?
Use a key derivation function (KDF) to digest the password. The modern one is Krawczyk and Eronen's HKDF using the Extract-then-Expand model. The paper is located at Cryptographic Extraction and Key Derivation: The HKDF Scheme.
You should consider using it for the IV, too. Rather than deriving 32 bytes (AES::MAX_KEYLENGTH), derive 48 bytes (AES::MAX_KEYLENGTH+AES::BLOCKSIZE) instead. The IV in your design can then be used for the salt parameter to the KDF.
Maybe something like:
#include <iostream>
#include <string>
using namespace std;
#include "cryptlib.h"
#include "aes.h"
#include "sha.h"
#include "hkdf.h"
#include "modes.h"
#include "filters.h"
using namespace CryptoPP;
int main(int argc, char* argv[])
{
SecByteBlock key(AES::MAX_KEYLENGTH+AES::BLOCKSIZE);
string password("passwordFromUser"), iv("<random value>"), message("encryptMe");
string encrypted, recovered;
try
{
HKDF<SHA256> hkdf;
hkdf.DeriveKey(key, key.size(), (const byte*)password.data(), password.size(), (const byte*)iv.data(), iv.size(), NULL, 0);
///////////////////////////////////////////////////////////////////////
CTR_Mode<AES>::Encryption encryption;
encryption.SetKeyWithIV(key, AES::MAX_KEYLENGTH, key+AES::MAX_KEYLENGTH);
StringSource encryptor(message, true,
new StreamTransformationFilter(encryption,
new StringSink(encrypted))
);
///////////////////////////////////////////////////////////////////////
CTR_Mode<AES>::Decryption decryption;
decryption.SetKeyWithIV(key, AES::MAX_KEYLENGTH, key+AES::MAX_KEYLENGTH);
StringSource decryptor(encrypted, true,
new StreamTransformationFilter(decryption,
new StringSink(recovered))
);
cout << "Message: " << message << endl;
cout << "Recovered: " << recovered << endl;
}
catch(const Exception& ex)
{
cerr << ex.what() << endl;
return 1;
}
return 0;
}
When using the encryption method above, you have to track the {iv,message} pair. The IV is needed to ensure uniqueness per-message since the password effectively fixes the AES key.
What if the user chooses a password that is longer then 32 Byte? In My case, the password would by truncated, which doesn't sound right to me.
A KDF handles it for you. It extracts the entropy regardless of how little or how much.
StringSource encryptor(textToEncrypt, true,
new StreamTransformationFilter(encryption,
new StringSink(verschluesselterText),
StreamTransformationFilter::NO_PADDING
)
There's no need to specify the padding mode. Also see the documentation for BlockPaddingScheme.
You should be very careful with modes like CTR. CTR mode xor's the keystream with the plain text. If someone reuses their password on different messages, then its possible to recover the keystream which leads to plaintext recovery.
If ivText is unique for each message, then you should add it to your KDF to ensure a unique keystream for each message. Add the IV as the salt parameter for HKDF. Here, "unique" means if I have a message "Hello World", then the IV is different each time I encrypt the message.
If the IV is truly just "Iv16BytesOfText..." (i.e., its fixed), then there's nothing unique about it. Just derive an additional 16 bytes from the user's password. Then, to avoid the keystream xor attack, switch to a mode like CBC.
Finally, you should probably use CCM, EAX or GCM mode. Right now, you only have confidentiality. Usually you want authenticity, too. To gain authenticity, you often select an Authenticated Encryption mode of operation.
I'm having an issue with allocating a new key for 3 key Triple DES in crypto++.
I've generated a new key as a string but need to allocate it to SecByteBlock for use in Crypto++.
Currently I generate a random key using the PRNG at the start, but when I attempt to change the key using string output from DES_EDE3, it appears to use the same key.
I think the issue is with the conversion between string and SecByteBlock, or the allocation to SecByteBlock as shown below.
Any help would be greatly appreciated!
SecByteBlock GENERATOR::setKey(string keyString){
SecByteBlock replacementKey(24);
replacementKey= SecByteBlock(reinterpret_cast<const byte*>(keyString.data()), keyString.size());
return newKey = replacementKey;
}
I attempt to change the key using string output from DES_EDE3, it appears to use the same key
It almost sounds like you are trying to use 3-DES as a PRF keyed with a password. If so, use HKDF. Its designed for these types of expand-then extract operations.
HKDF is available in Crypto++ 5.6.3 and above. If you need it for a downlevel client, then copy the header where you need it.
SecByteBlock GENERATOR::setKey(string keyString){
SecByteBlock replacementKey(24);
replacementKey= SecByteBlock(reinterpret_cast<const byte*>(keyString.data()), keyString.size());
return newKey = replacementKey;
}
Though you size replacementKey to 24, it could be resized by the assignment replacementKey= SecByteBlock(...).
You might want to try the following:
SecByteBlock GENERATOR::setKey(const string& keyString)
{
SecByteBlock key((const byte*)keyString.data(), keyString.size());
if(key,size() < DES_EDE3::KEYLENGTH)
key.CleanGrow(DES_EDE3::KEYLENGTH);
else
key.resize(DES_EDE3::KEYLENGTH);
return key;
}
CleanGrow sizes the memory block to DES_EDE3::KEYLENGTH and backfills the block with 0's as needed. resize will truncate to DES_EDE3::KEYLENGTH if its too large.
You could also do something like:
SecByteBlock key(DES_EDE3::KEYLENGTH);
size_t s = STDMIN(key.size(), keyString.size());
memcpy(key.data(), keyString.data(), s);
if(s < DES_EDE3::KEYLENGTH)
memset(key.data()+s, 0, DES_EDE3::KEYLENGTH-s);
-----
To combine the first two, you might consider this:
SecByteBlock GENERATOR::setKey(const string& keyString)
{
// Block is unintialized
SecByteBlock key(DES_EDE3::KEYLENGTH);
HKDF<SHA256> kdf;
kdf.Derivekey(key.data(), key.size(), (const byte*)keyString.data(), keyString.size(), NULL, 0);
return key;
}
-----
You can output a SecByteBlock with code like:
SecByteBlock b = GENERATOR::setKey(...);
...
cout << "Derived key: "
ArraySource as(b.data(), b.size(), true, new HexEncoder(new FileSink(cout)));
cout << endl;
The following with encode it using Base64:
ArraySource as(b.data(), b.size(), true, new Base64Encoder(new FileSink(cout)));