GCM Mode

From Crypto++ Wiki
Jump to navigation Jump to search
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 either AuthenticatedEncryptionFilter or AuthenticatedDecryptionFilter. 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