GCM Mode
Documentation |
#include <cryptopp/gcm.h>
|
Galios/Counter Mode, or GCM Mode, is a mode of operation that uses a universal hash function over a binary Galois field to provide authenticated encryption. The mode is defined in NIST's SP 800-38D, and P1619. GCM is a high performance mode which offers both pipelining and parallelization. The mode accepts initialization vectors of arbitrary length, which simplifies the requirement that all IVs should be distinct. For a comparison of 4th generation authenticated encryption modes, visit AEAD Comparison.
GCM uses a key size of 128, 192 or 256 bits according to AES, and the block size of 128 bits. The initialization vector (iv) is restricted to lengths less than or equal to 264-1 in multiples of 8. You cannot use a symmetric cipher with a smaller block size because GCM was designed for 128-bit ciphers. For example, Blowfish, with a 64-bit block size, will not work.
Regarding the iv, SP 800-38D recommends limiting the iv to 96 bits or less to "promote interoperability, efficiency, and simplicity of design". And Microsoft's WinCrypt implementation only allows iv's of 96-bits or 128-bits. Also see How do I use AES-GMAC with a secret in BCrypt? on Stack Overflow.
GCM produces authentication tags of 128, 120, 112, 104, or 96 bits. 64 and 32 bits are available but not recommended under most circumstances. Shorter tags are created by truncating the 128 bit tag. The default authentication tag size for Crypto++'s implementation is 128 bits. To change the authentication tag size, an alternate contructor for AuthenticatedEncryptionFilter
and AuthenticatedDecryptionFilter
should be used.
GCM restricts the size of additional authenticated data (aad) to less than or equal to 264-1; and confidential data to 239-256.
When only aad is presented to GCM, the resulting authentication tag is simply a GMAC. The GMAC is a special case of GCM where no plain text is presented (i.e., there is no confidential data which will receive both confidentiality and authentication). NIST recognizes GMAC as yet another mode of operation of a block cipher when used in the context of GCM.
GCM is online, meaning the data does not need to be known in advance - it can be streamed into the object (there are some practical implementation constraints).
Crypto++ offers several modes of operation, including ECB, CBC, OFB, CFB, CBC-CTS, CTR, XTS, CCM, EAX, GCM and OCB.
Crypto++ does not provide a way to retrieve the current IV or counter used for encryption or decryption. If you need the current IV or counter then you need to manage it yourself. Some ciphers allow you to seek a number of bytes or blocks in the stream.
If you are used to working in languages like Java or libraries like OpenSSL, then you might want to visit the Init-Update-Final wiki page. Crypto++ provides the transformation model, but its not obvious because its often shrouded behind Pipelines.
Background
Given two message inputs, aad (additional authenticated data) and confidential data (plain text data), GCM mode will provide authentication assurances over the aad and provide both confidentiality and authentication over the confidential data. Note that either aad or confidential data may be NULL or unused, but at least one must be present. A simple example would be securely saving a file to disk: the file data would consist of the pair { iv, ciphertext }. The iv would be authenticated (and persisted in the clear), while the cipher text would have both encryption and authentication applied.
The output of the mode is a single cipher text message which has two components: the concatenated pair { Encrypted, Tag }. Encrypted is the result of encrypting the confidential data, while authentication tag is the output of the GHASH over both the aad and confidential data. Since the tag size is known, it is trivial to split the cipher text data into its constituent components. Note that the original aad is not returned in this operation - only the cipher text and tag.
Crypto++ Implementation
Crypto++ 5.6 intoduced a new interface for working with authenticated encryption objects: AuthenticatedSymmetricCipher
. Crypto++ exposes GCM through the use of a GCM mode object and a pair of filters: AuthenticatedEncryptionFilter
and AuthenticatedDecryptionFilter
. Each filter combines a block cipher (which should be AES) operated in GCM mode with a HashFilter
to generate the MAC and and a HashVerificationFilter
to verify the GMAC digest. In addition to GCM mode, CCM Mode also uses the interface.
Both the aad and confidential data should be fully available to the AuthenticatedSymmetricCipher
objects form compatibility with other authenticate and encrypt modes such as CCM. With this in mind, changing modes to evaluate performance will be a trivial task. As with CCM mode, operations on the channel data must be performed in strict order.
The parameters which must be supplied and used by both parties are:
- key and key size
- iv and iv size
- tag size
Finally, do not use a StreamTransformationFilter
on a GCM object to recover only the primary data channel (the cipher text). The StreamTransformationFilter
will throw an exception.
Construction
The constructors accept a block cipher parameter (which is usually AES) and an optional GCM_TablesOption
parameter. The default GCM_TablesOption
parameter is GCM_2K_Tables
. A second Crypto++ table offering is GCM_64K_Tables
.
GCM< AES >::Encryption e; GCM< AES, GCM_64K_Tables >::Encryption e;
GCM< AES >::Decryption d; GCM< AES, GCM_64K_Tables >::Decryption d;
Because the choice of tables is a tradeoff between memory and speed, an Encryption/Authentication object will be compatible with a Decryption/Verification object using a different table:
GCM< AES, GCM_2K_Tables >::Encryption e; GCM< AES, GCM_64K_Tables >::Decryption d;
Though both the encryption and decryption object can be used directly if combined with the proper HashFilter
or HashVerificationFilter
, it is generally easier to use the provided AuthenticatedEncryptionFilter
or AuthenticatedDecryptionFilter
.
Sample Programs
Two sample programs are provided for GCM mode. In the samples below, a few points are noteworthy:
- The IV/Nonce size can be nearly any size
- Unlike CCM Mode, a call to
SpecifyDataLengths
is not required - Pushing data into the objects in the wrong order will result in an exception
- Data flow into the encryptor is slightly different than data flow into the decryptor/verifier
- Exceptions for a verification failure can be supressed by not including
THROW_EXCEPTION
flag during construction. - Tag size is altered by specifying a supported value for the
truncatedDigestSize
parameter of eitherAuthenticatedEncryptionFilter
orAuthenticatedDecryptionFilter
. The value is specified in bytes, not bits
AE
The first sample, GCM-AE-Test.zip, performs authenticated encryption. It does not perform authentication over additional authenticated data (aad). Since only encryption is performed, only access to the default channel is needed. So the code below is similar to what one might expect for other modes such as CBC. The exception is SetKeyWithIV
requires an iv size.
AutoSeededRandomPool prng; SecByteBlock key( AES::DEFAULT_KEYLENGTH ); prng.GenerateBlock( key, key.size() ); byte iv[ AES::BLOCKSIZE ]; prng.GenerateBlock( iv, sizeof(iv) ); const int TAG_SIZE = 12; // Plain text string pdata="Authenticated Encryption"; // Encrypted, with Tag string cipher, encoded; // Recovered plain text string rpdata; /*********************************\ \*********************************/ try { GCM< AES >::Encryption e; e.SetKeyWithIV( key, key.size(), iv, sizeof(iv) ); StringSource ss1( pdata, true, new AuthenticatedEncryptionFilter( e, new StringSink( cipher ), false, TAG_SIZE ) // AuthenticatedEncryptionFilter ); // StringSource } catch( CryptoPP::Exception& e ) { cerr << e.what() << endl; exit(1); } /*********************************\ \*********************************/ try { GCM< AES >::Decryption d; d.SetKeyWithIV( key, key.size(), iv, sizeof(iv) ); AuthenticatedDecryptionFilter df( d, new StringSink( rpdata ), DEFAULT_FLAGS, TAG_SIZE ); // AuthenticatedDecryptionFilter // The StringSource dtor will be called immediately // after construction below. This will cause the // destruction of objects it owns. To stop the // behavior so we can get the decoding result from // the DecryptionFilter, we must use a redirector // or manually Put(...) into the filter without // using a StringSource. StringSource ss2( cipher, true, new Redirector( df /*, PASS_EVERYTHING */ ) ); // StringSource // If the object does not throw, here's the only // opportunity to check the data's integrity if( true == df.GetLastResult() ) { cout << "recovered text: " << rpdata << endl; } } catch( CryptoPP::Exception& e ) { cerr << e.what() << endl; exit(1); }
AEAD
The second sample, GCM-AEAD-Test.zip, performs authenticated encryption with additional authenticated data. It explicitly uses Put
on the two channels rather than using pipelining. The default data channel provides confidentiality and authentication; the second channel (AAD_CHANNEL), provides only authentication. The download includes P1619 test vectors from IEEE for verifying correctness of the implementation. Additional test vectors can be found in SP 800-38D, Appendix B.
Finally, the encryption/authentication routine uses a StringSink
. Using this method will have the AuthenticatedEncryptionFilter
place the cipher text in the string
. Decryption/verification uses a second method: it explicitly requests the recovered plain text from the channel.
// P1619 Test Vector 003 // KEY 0000000000000000000000000000000000000000000000000000000000000000 // IV 000000000000000000000000 // HDR 00000000000000000000000000000000 // PTX 00000000000000000000000000000000 // CTX cea7403d4d606b6e074ec5d3baf39d18 // TAG ae9b1771dba9cf62b39be017940330b4 byte key[32]; memset( key, 0, sizeof(key) ); byte iv[12]; memset( iv, 0, sizeof(iv) ); string adata( 16, (char)0x00 ); string pdata( 16, (char)0x00 ); const int TAG_SIZE = 16; // Encrypted, with Tag string cipher, encoded; // Recovered (decrypted) string radata, rpdata; /*********************************\ \*********************************/ try { GCM< AES >::Encryption e; e.SetKeyWithIV( key, sizeof(key), iv, sizeof(iv) ); // AuthenticatedEncryptionFilter defines two // channels: DEFAULT_CHANNEL and AAD_CHANNEL // DEFAULT_CHANNEL is encrypted and authenticated // AAD_CHANNEL is authenticated AuthenticatedEncryptionFilter ef( e, new StringSink( cipher ), false, TAG_SIZE /* MAC_AT_END */ ); // AuthenticatedEncryptionFilter // Authenticated data *must* be pushed before // Confidential/Authenticated data. Otherwise // we must catch the BadState exception ef.ChannelPut( AAD_CHANNEL, adata.data(), adata.size() ); ef.ChannelMessageEnd(AAD_CHANNEL); // Confidential data comes after authenticated data. // This is a limitation due to CCM mode, not GCM mode. ef.ChannelPut( DEFAULT_CHANNEL, pdata.data(), pdata.size() ); ef.ChannelMessageEnd(DEFAULT_CHANNEL); } catch( CryptoPP::Exception& e ) { cerr << "Caught Exception..." << endl; cerr << e.what() << endl; cerr << endl; } /*********************************\ \*********************************/ // // The pair { adata, cipher } is sent to // the other party or persisted to storage // // Attack the first and last byte of the // encrypted data and tag // if( cipher.size() > 1 ) // { // cipher[ 0 ] |= 0x0F; // cipher[ cipher.size()-1 ] |= 0x0F; // } /*********************************\ \*********************************/ try { GCM< AES >::Decryption d; d.SetKeyWithIV( key, sizeof(key), iv, sizeof(iv) ); // Break the cipher text out into it's // components: Encrypted and MAC string enc = cipher.substr( 0, cipher.length()-TAG_SIZE ); string mac = cipher.substr( cipher.length()-TAG_SIZE ); // Sanity checks assert( cipher.size() == enc.size() + mac.size() ); assert( enc.size() == pdata.size() ); assert( TAG_SIZE == mac.size() ); // Not recovered - sent via clear channel radata = adata; // Object *will* throw an exception // during decryption\verification _if_ // verification fails. AuthenticatedDecryptionFilter df( d, NULL, MAC_AT_BEGIN | THROW_EXCEPTION, TAG_SIZE ); // The order of the following calls are important df.ChannelPut( DEFAULT_CHANNEL, mac.data(), mac.size() ); df.ChannelPut( AAD_CHANNEL, adata.data(), adata.size() ); df.ChannelPut( DEFAULT_CHANNEL, enc.data(), enc.size() ); // If the object throws, it will most likely occur // during ChannelMessageEnd() df.ChannelMessageEnd( AAD_CHANNEL ); df.ChannelMessageEnd( DEFAULT_CHANNEL ); // If the object does not throw, here's the only // opportunity to check the data's integrity bool b = false; b = df.GetLastResult(); assert( true == b ); // Remove data from channel string retrieved; size_t n = (size_t)-1; // Plain text recovered from enc.data() df.SetRetrievalChannel( DEFAULT_CHANNEL ); n = (size_t)df.MaxRetrievable(); retrieved.resize( n ); if( n > 0 ) { df.Get( (byte*)retrieved.data(), n ); } rpdata = retrieved; assert( rpdata == pdata ); // All is well - work with data cout << "Decrypted and Verified data. Ready for use." << endl; cout << endl; cout << "adata length: " << adata.size() << endl; cout << "pdata length: " << pdata.size() << endl; cout << endl; cout << "recovered adata length: " << radata.size() << endl; cout << "recovered pdata length: " << rpdata.size() << endl; cout << endl; } catch( CryptoPP::Exception& e ) { cerr << "Caught Exception..." << endl; cerr << e.what() << endl; cerr << endl; }
In the preceeding code, the MAC_AT_BEGIN
was specified during contruction to indicate that the authentication tag was the first item being inserted into the AuthenticatedDecryptionFilter
. If the authentication tag is to be inserted at the end of the process, the following code would be used. Note that aad must still be pushed before confidential data.
AuthenticatedDecryptionFilter df( d, NULL, MAC_AT_END ); df.ChannelPut( AAD_CHANNEL, adata.data(), adata.size() ); df.ChannelPut( DEFAULT_CHANNEL, enc.data(), enc.size() ); df.ChannelPut( DEFAULT_CHANNEL, tag.data(), tag.size() ); // Signal End on both channels df.MessageEnd();
Block Sizes
GCM requires the block cipher to have a block size of 16 bytes. If you try to use the mode with a block cipher like Blowfish with a 8-byte blocksize, then you will receive a Crytpo++ exception block size of underlying block cipher is not 16:
$ ./Driver.exe key: 3D79897DFAE98DDA381196AB3319C8EC iv: 4E3E871C7B2DC44E plain text: GCM Mode Test Blowfish/GCM: block size of underlying block cipher is not 16
You will encounter a similar error when using a block cipher like Kalyna with a 256-bit or 512-bit blocksize:
$ ./test.exe Kalyna-512(512)/GCM: block size of underlying block cipher is not 16
The Kalyna team sent us polynomials for 256-bit and 512-bit block sizes, but we have not made the design and implementation changes to use them. The polynomials for Kalyna are listed below.
- 128-bit block: x127 + x7 + x2 + x + 1
- 256-bit block: x256 + x10 + x5 + x + 1
- 512-bit block: x512 + x8 + x5 + x2 + 1
Downloads
GCM-AE-Test.zip - GCM Test using only confidential data
GCM-AEAD-Test.zip - GCM Test using both aad and confidential data
Twofish-GCM-Filter.zip - Demonstrates encryption and decryption using Twofish in GCM mode with filters
Serpent-GCM-Filter.zip - Demonstrates encryption and decryption using Serpent in GCM mode with filters
Camellia-GCM-Filter.zip - Demonstrates encryption and decryption using Camellia in GCM mode with filters
AES-GCM-Filter.zip - Demonstrates encryption and decryption using AES in GCM mode with filters