GCM Mode

From Crypto++ Wiki
(Redirected from GCM)
Jump to: navigation, search

GCM (Galios/Counter 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 AtE Comparison.

GCM uses a block cipher with a block size of 128 bits and produces tags of 128, 120, 112, 104, or 96 bits (64 and 32 bits are available but not recommended under most circumstances). 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.

GCM produces tags shorter than 128 bit by truncating the 128 bit tag. The default tag size for Crypto++'s implementation is 128 bits. To change the tag size, an alternate contructor for AuthenticatedEncryptionFilter and AuthenticatedDecryptionFilter should be used.

GCM restricts the size of ADATA to less than or equal to 264-1; and PDATA to 239-256. For completeness, the IV is restricted to lengths less than or equal to 264-1 in multiples of 8. However, SP 800-38D recommends limiting the IV to 96 bits or less to "promote interoperability, efficiency, and simplicity of design".

When only ADATA is presented to GCM, the resulting tag is simply a GMAC. The GMAC is a special case of GCM where no plain text is presented (i.e., there is no PDATA 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).

Background

Given two message inputs, ADATA (additional authenticated data) and PDATA (plain text data), GCM mode will provide authentication assurances over the ADATA and provide both confidentiality and authentication over the PDATA. Note that either ADATA or PDATA 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 PDATA, while tag is the output of the GHASH over both the ADATA and PDATA. Since the tag size is known, it is trivial to split the cipher text data into its constituent components. Note that the original ADATA 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 ADATA and PDATA 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.

Crypto++ Demonstration

Crypto++ demonstrates the use of GCM mode in datatest.cpp, function TestAuthenticatedSymmetricCipher. Please see the discussion of the TestAuthenticatedSymmetricCipher.

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 (ADATA). 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 * 16 ];
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( 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( 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 ef( e,
        new StringSink( cipher ), false,
        TAG_SIZE /* MAC_AT_END */
    ); // AuthenticatedEncryptionFilter

    // AuthenticatedEncryptionFilter::ChannelPut
    //  defines two channels: DEFAULT_CHANNEL and AAD_CHANNEL
    //   DEFAULT_CHANNEL is encrypted and authenticated
    //   AAD_CHANNEL is authenticated
    ef.ChannelPut( AAD_CHANNEL, adata.data(), adata.size() );
    ef.ChannelMessageEnd(AAD_CHANNEL);

    // Authenticated data *must* be pushed before
    //  Confidential/Authenticated data. Otherwise
    //  we must catch the BadState exception
    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 tag was the first item being inserted into the AuthenticatedDecryptionFilter. If the tag is to be inserted at the end of the process, the following code would be used. Note that ADATA must still be pushed before PDATA.

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();

Downloads

GCM-AE-Test.zip - GCM Test using only PDATA

GCM-AEAD-Test.zip - GCM Test using both ADATA and PDATA

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