Is it explicitly allowed to use the same buffer for plaintext/ciphertext when performing AES encryption/decryption in CBC and ECB modes using Crypto++ (assuming the buffer size is sufficient to accomodate the encrypted data) as in the following code:
#include <cstdio>
#include <cassert>
#include "cryptopp\rsa.h"
#include "cryptopp\rijndael.h"
#include "cryptopp\modes.h"
int main()
{
using namespace CryptoPP;
byte key[32], iv[Rijndael::BLOCKSIZE];
char testdata[] = "Crypto++ Test"; // any data can be here
size_t buffer_size = (sizeof(testdata) + Rijndael::BLOCKSIZE) & ~(Rijndael::BLOCKSIZE - 1);
byte* buffer = new byte[buffer_size];
memcpy(buffer, testdata, sizeof(testdata));
// encrypt data inplace
CBC_Mode<Rijndael>::Encryption enc(key, sizeof(key), iv);
MeterFilter meter(new ArraySink(buffer, buffer_size));
ArraySource(buffer, sizeof(testdata), true, new StreamTransformationFilter(enc, new Redirector(meter), BlockPaddingSchemeDef::PKCS_PADDING));
assert(meter.GetTotalBytes() == buffer_size);
// decrypt data inplace
CBC_Mode<Rijndael>::Decryption dec(key, sizeof(key), iv);
MeterFilter meter2(new ArraySink(buffer, buffer_size));
ArraySource(buffer, buffer_size, true, new StreamTransformationFilter(dec, new Redirector(meter2), BlockPaddingSchemeDef::PKCS_PADDING));
assert(meter2.GetTotalBytes() == sizeof(testdata));
printf("%s\n", static_cast<char*>(buffer));
delete buffer;
}
In general, Crypto++ buffers can be the same or they can be distinct. I can't think of a situation where they are not allowed to be the same for in-place or in-situ processing of plain text or cipher text data. The only caveat is the buffer has to be larger enough for cipher text expansion.
You also have to be careful about overlap, but how you can get into trouble depends on the cipher. For example, AES in CBC_Mode operates on 16 byte blocks (the functions of interest are ProcessBlock, ProcessXorBlock, and friends). You can use an overlapped buffer as long as the difference between pointers is 17 bytes (or more). In the RSA case, you would likely need a difference of MaxPreImage size, which is based on the size of the modulus.
Finally, the old Crypto++ FAQ discusses it briefly as "in-line processing". See How do I use a block cipher in Crypto++ 4.x?
Related
I'm almost new in Cryptopp.
This is my encrypted data:
...
char* mDataBuffer = new char[length];
inFile.read((char*)(&mDataBuffer[0]), length);
By reading the inFile, now the mDataBuffer array contains encrypted binary data.
There's no problem with this mDataBuffer (tested carefully!).
Now I need to decrypt this array.
Note that the file (now presented as mDataBuffer array) is encrypted in java or .net in AES/CBC/PKCS7PADDING.
The decrypted data should be in char type (or could be converted to it!)
Here's my c++ code for decryption:
byte mkey[] = "12345678"; // note that the key is only 8 chars
CBC_Mode< AES >::Decryption decryptor;
decryptor.SetKeyWithIV(mkey, 16, mkey); // IV and key are same. I used 16 bytes and I think it causes Cryptopp to use PKCS#7 padding (Am I write?!)
char* decryptedBytes = new char[length];
Then I've tried many things, for example:
Try 1:
std::string plain;
StreamTransformationFilter stf(decryptor, new CryptoPP::StringSink(plain));
for (int i = 0; i < length; i++) {
stf.Put(mDataBuffer[i], sizeof(mDataBuffer[i]));
}
stf.MessageEnd();
With this code I get this error message:
CryptoPP::InvalidCiphertext at memory location
The mDataBuffer is binary data and contains 0x00 data (or '\0').
Try 2:
ArraySink cs((byte*)&decryptedBytes[0], length);
StreamTransformationFilter stf(decryptor, new Redirector(cs));
ArraySource((const char*)(&mDataBuffer[0]), true,
new StreamTransformationFilter(d, new Redirector(cs)));
With this code I get this error message:
CryptoPP::InvalidCiphertext at memory location
Note: I cant use FileSource (maybe I'm wrong!). Because the file has extra data and this data is removed manually after it has been read to mDataBuffer (for simplicity I didn't include the code here)
I wonder how I stock to this!
Thanks any help or hint.
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'm trying to implement a barebones, start-to-finish AES encryption that is correct and secure. For my purposes, I just want to generate the ciphertext of the output of some benchmarking programs, to simulate what kind of ciphertext would be written to say, a cloud storage server. After reading about it some, I came to the conclusion that for my purposes, authenticated encryption produced by AES in GCM mode is representative of ciphertexts I would see in use on real servers.
Starting from the example code on the Crypto++ Wiki page, I came up with the following snippet to read in some strings from a file and dump the ciphertext. The problem is that the ciphertexts I am getting out are very similar, the upper half being identical in many of the outputs. I suspect it is due to the way I am using/initializing the AutoSeededRandomPool, but I don't know enough about it to figure out how to fix it. It looks like the underlying code is making calls to the OS (Ubuntu 16.04) to generate entropy, which leads me to believe that perhaps there is not enough time between calls for this value to change.
Thanks in advance for any help you can give.
#include "osrng.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include "cryptlib.h"
#include "hex.h"
#include "filters.h"
#include "aes.h"
#include "gcm.h"
#include "secblock.h"
using namespace std;
using CryptoPP::AutoSeededRandomPool;
using CryptoPP::AutoSeededX917RNG;
using CryptoPP::HexEncoder;
using CryptoPP::HexDecoder;
using CryptoPP::StringSink;
using CryptoPP::StringSource;
using CryptoPP::AuthenticatedEncryptionFilter;
using CryptoPP::AuthenticatedDecryptionFilter;
using CryptoPP::AES;
using CryptoPP::GCM;
using CryptoPP::SecByteBlock;
// Single randomly seeded RNG
AutoSeededRandomPool rnd;
// Generate a string buffer for the input/output data
string plainText, cipherText, encoded;
// Generate a random key
SecByteBlock key(AES::DEFAULT_KEYLENGTH);
rnd.GenerateBlock(key, key.size());
// Generate an initial value vector (public but unique per msg)
SecByteBlock iv(AES::BLOCKSIZE);
rnd.GenerateBlock(iv, iv.size());
for (size_t i = 0; i < numLines; ++i)
{
getline(inputFileStream, plainText);
// Do encryption
GCM<AES>::Encryption e;
e.SetKeyWithIV(key, key.size(), iv, iv.size());
StringSource
(
plainText,
true,
new AuthenticatedEncryptionFilter
(
e,
new StringSink(cipherText)
)
);
encoded.clear();
StringSource
(
cipherText,
true,
new HexEncoder
(
new StringSink(encoded)
)
);
cout << "Cipher Text: " << encoded << endl;
}
Here is an example of output ciphertexts:
Cipher Text:
9DE3A67D5FF42A15834460CD4489B20A352ECEB5F801F7349F3A989DAE8C02675CB48ADDD00604139353F2DEC6335DF8156DA66ACEF953F2E573BB3D88E7AF7D59EE311DC8056CDB0B90B30A232DD7ECB219FB2F9F2D9898B98A1B6749FC8B88D00B5E08DC4EF9C3A52521298D6FBFD75A9A71E8A253D8B9F06D17B07442DA543B8E1CCCEC1E7D7084A1A24DAA71CB688AC2ECD840731F5D57AA7BC61DE5837411596561C36659D95451A003A0E27697528B9BB6B67763F6
Cipher Text:
9DE3A67D5FF42A15834460CD4489B20A352ECEB5F801F7349F3A989DAE8C02675CB48ADDD00604139353F2DEC6335DF8156DA66ACEF953F2E573BB3D88E7AF7D59EE311DC8056CDB0B90B30A232DD7ECB219FB2F9F2D9898B98A1B6749FC8B88D00B5E08DC4EF9C3A52521298D6FBFD75A9A71E8A253D8B9F06D17B07442DA543B8E1CCCEC1E7D7084A1A24DAA71CB688AC2ECD840731F5D57AA7BC61DE5837411596561C36659D95451A003A0E27697528B9BB6B67763F6D8B9F0691B616C2E996B5E473860EE348C09A1F0FC
Moving the IV generation outside of the loop, there is similar behavior:
Cipher Text:
25C638C32E67A7A2C65BF37ABA7C30C19C2714D95FBB68E6E57560CCE2C20F266E6C30768108CF7E01C195991B61AF7FE4F4FA691AFEAAFCFB74292EBE30B9236147722F5785D8F21070D3ACF9E476E39235B4C362D14BF7B2FC2A5BFC0297FCBCBEC37795626029CC30B404A6DB67EA652F5A7FA294D039C4A09BC611F74D8C9FFBD26F49C54470E2C41463440AF050D7FF160CD923FFD0CA6FAF1DB66947C5896B1A39A8E9B694A025E2F521229BDC15C48C5F3AD27A87
Cipher Text:
25C638C32E67A7A2C65BF37ABA7C30C19C2714D95FBB68E6E57560CCE2C20F266E6C30768108CF7E01C195991B61AF7FE4F4FA691AFEAAFCFB74292EBE30B9236147722F5785D8F21070D3ACF9E476E39235B4C362D14BF7B2FC2A5BFC0297FCBCBEC37795626029CC30B404A6DB67EA652F5A7FA294D039C4A09BC611F74D8C9FFBD26F49C54470E2C41463440AF050D7FF160CD923FFD0CA6FAF1DB66947C5896B1A39A8E9B694A025E2F521229BDC15C48C5F3AD27A87FA3272CFE669E235CE452FCEEA59CC8CA34554FF25
Addendum to the answer below:
In the code I neglected to clear the string "cipherText" at each iteration of the loop. Apparently the StringSink operations were just appending the new cipher texts to the end of the cipherText, which is why they were identical. Thanks for all the help!
You're generating the key and IV outside the for loop. That means that you will use the same key / IV combination for the lines your are encrypting. GCM is basically CTR encryption with an authentication tag. That means that similar bits in the plaintext will result in the exact same value. GCM is only secure as long as the IV never repeats.
Put the IV generation inside the loop and you should be OK. Note that, for GCM, 12 bytes IV is more efficient and possibly more secure than a 16 byte IV. Random IV's are often put in front of the ciphertext (by writing them to the StringSink manually).
In your original code you made two mistakes. You printed out the key instead of the IV, which made you believe your RNG was wrong. Furthermore, you wrote to the ciphertext string variable without clearing it first. This made the StringSink append the next ciphertext (and authentication tag) to the ciphertext string. So you got both the first and the second ciphertext when you printed it out.
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've implemented a C++ wrapper library for Crypto++ v5.6.2 and have a question about combinations of symmetric algorithms (e. g. Blowfish) and block modes (e. g. GCM).
I am able to encrypt and decrypt data via Blowfish/EAX, but I can't achieve the same by using Blowfish/GCM. AES/EAX and AES/GCM both work.
The following simple application demonstrates my problem:
#include <iostream>
#include <string>
#include "cryptopp/blowfish.h"
#include "cryptopp/filters.h"
#include "cryptopp/eax.h"
#include "cryptopp/gcm.h"
#include "cryptopp/osrng.h"
#include "cryptopp/hex.h"
std::string encrypt(
CryptoPP::AuthenticatedSymmetricCipher &encryption,
std::string const kPlainText,
CryptoPP::SecByteBlock const kKey,
unsigned const char * kIV) {
std::string cipher_text;
// TODO Is this the source of the problem?
// BlockSize always returns 0 which leads to an exception if GCM block mode is used!
std::cout << encryption.BlockSize() << " bytes" << std::endl;
encryption.SetKeyWithIV(
kKey,
kKey.size(),
kIV
);
CryptoPP::StringSink *string_sink = new CryptoPP::StringSink(cipher_text);
CryptoPP::BufferedTransformation *transformator = NULL;
// The AuthenticatedEncryptionFilter adds padding as required.
transformator = new CryptoPP::AuthenticatedEncryptionFilter(
encryption,
string_sink);
bool const kPumpAll = true;
CryptoPP::StringSource(
kPlainText,
kPumpAll,
transformator);
return cipher_text;
}
std::string decrypt(
CryptoPP::AuthenticatedSymmetricCipher &decryption,
std::string const kCipherText,
CryptoPP::SecByteBlock const kKey,
unsigned const char * kIV) {
std::string recovered_plain_text;
decryption.SetKeyWithIV(
kKey,
kKey.size(),
kIV);
CryptoPP::StringSink *string_sink = new CryptoPP::StringSink(
recovered_plain_text);
CryptoPP::BufferedTransformation *transformator = NULL;
CryptoPP::AuthenticatedDecryptionFilter *decryption_filter = NULL;
decryption_filter = new CryptoPP::AuthenticatedDecryptionFilter(
decryption,
string_sink);
transformator = new CryptoPP::Redirector(*decryption_filter);
bool const kPumpAll = true;
CryptoPP::StringSource(
kCipherText,
kPumpAll,
transformator);
return recovered_plain_text;
}
int main() {
CryptoPP::AutoSeededRandomPool prng;
CryptoPP::SecByteBlock key(CryptoPP::Blowfish::DEFAULT_KEYLENGTH);
prng.GenerateBlock(key, key.size());
byte iv[CryptoPP::Blowfish::BLOCKSIZE];
prng.GenerateBlock(iv, sizeof(iv));
// Creates templated mode objects of block ciphers.
// This works...
// CryptoPP::EAX<CryptoPP::Blowfish>::Encryption encryption;
// CryptoPP::EAX<CryptoPP::Blowfish>::Decryption decryption;
// This does NOT work...
CryptoPP::GCM<CryptoPP::Blowfish>::Encryption encryption;
CryptoPP::GCM<CryptoPP::Blowfish>::Decryption decryption;
std::string plain_text = "Block Mode Test";
std::string cipher_text = encrypt(encryption, plain_text, key, iv);
// terminate called after throwing an instance of 'CryptoPP::InvalidArgument'
// what(): Blowfish/GCM: block size of underlying block cipher is not 16
std::cout << "cipher text: " << std::hex << cipher_text << std::endl;
std::cout << "recovered plain text: " << decrypt(decryption, cipher_text, key, iv) << std::endl;
}
A CryptoPP::InvalidArgument exception is thrown if running the code above with the following text:
Blowfish/GCM: block size of underlying block cipher is not 16
But when running the code instead with the block mode EAX, no exception is thrown. So my questions are:
Does GCM only work with AES? Can GCM also be used with Blowfish or 3DES?
Is there a matrix available which lists all possible combinations of symmetric algorithms with block modes?
Or is this a bug in Crypto++? Because the method BlockSize() always returns 0 but the exception is only raised if using Blowfish (or 3DES) instead of AES. This seems to raise the exception mentioned.
GCM has been designed to work with 128-bit (=16 byte) block size only. You can find this in the original paper in Section 5.1.
Blowfish is a 64-bit block size algorithm, so the two are not compatible as an "out-of-the-box" authenticated encryption combination. The same is true for 3DES. The exception is not a bug in Crypto++.
GCM will work with other Crypto++ objects that have 128-bit block sizes. They include AES, Cast-256, Rijndael Cameilla, MARS, Serpent and Twofish. A table of the block sizes is available at Applied Crypto++: Block Ciphers.
GCM will not work with larger block sizes either. For example, Rijndael (the parent of AES) offers 192-bit and 256-bit block sizes (AES only specifies the 128-bit block size). GCM will not work with the larger block sizes. And the same is true for SHACAL-2, with a 256-bit block size.
Crypto++'s BlockSize() sometimes returns 0 (it has to do with the template parameters). Instead, use the compile time constants like AES::BLOCKSIZE, Camellia::BLOCKSIZE and Rijndael::BLOCKSIZE. This could be considered a bug.