CCM Mode

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

CCM, or Counter with CBC-MAC, is a mode of operation for cryptographic block ciphers. The mode is defined in NIST's SP 800-38C (2004), P1363, and RFC 3610. CCM provides both confidentiality and authentication. The underlying block cipher must have 128-bit blocks and is operated in CTR mode to generate a stream. The formatting function of CCM ensures there is sufficient interleaving of encryption and authentication components before XORing the plain text and key stream.

CCM restricts the size of ADATA to less than or equal to 264-1; and PDATA to 264-1. Due to CCM's formatting function, the following must be observed when selecting IV sizes and Tag sizes:

Finally, Whiting, Housley, and Ferguson recommend using the largest key size available to defend against precomputation attacks (§1.8 and §1.11[1]).

Counter with CBC-MAC mode is the earliest NIST approved authenticated encryption mode. Additional modes, such as GCM, supports pipelining and parallel processing. For a comparison of 4th generation authenticated encryption modes, visit AtE Comparison. If confidentiality is not required, CMAC mode offers only authentication assurances. CMAC properly addresses variable length messages, so its use is preferred over the deficient CBC-MAC.

Background

Given two message inputs, ADATA (additional authenticated data) and PDATA (plain text data), CCM 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. An simple example would be communications over the internet: the message sent would be the pair { iv, ciphertext }. The iv would be authenticated (and sent 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 a truncation of the authentication code (MAC) over both the ADATA and PDATA. Since the tag size is a compile time constant, it is trivial to split the cipher text data into 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 CCM through the use of a CCM mode object and a pair of filters: AuthenticatedEncryptionFilter and AuthenticatedEncryptionFilter. Each filter combines a block cipher (which should be AES) operated in CCM mode with a HashFilter to generate the MAC tag and a HashVerificationFilter to verify the MAC tag. In addition to CCM mode, GCM Mode also uses the interface.

Both the ADATA and PDATA must be fully available to the AuthenticatedSymmetricCipher objects due to the nature of the mode and the operation of the formatting function. This is known as offline. The implication is that Crypto++ objects cannot buffer data. In addition, operations on the channel data must be performed in strict order.

Two important parameters of CCM mode are the nonce/iv size and the tag size. Refer to SP 800-38C Section 5 and Appendix B for guidance during selection. The tag size is a truncation of the MAC and must be supplied at compile time since it is a template parameter. Changing the tag size results in a change in the MAC value (due to the formatting function), which results in a change of the tag's value. The parameters which must be supplied and used by both parties are:

  • key and key size
  • iv and iv size
  • tag size

From a programmer's perspective, GCM mode is generally easier to use than CCM mode in Crypto++. GCM mode also has additional benefits (such as arbitrary length IVs and paralellization) which usually make it more appealing than CCM mode. If the requirement is simply authenticated encryption, it may be advantageous to use GCM mode.

CCM mode's formatting function is dependent upon the tag size parameter. As such, the tag size should be changed through the template parameter CCM< AES, tag size>, and not AuthenticatedEncryptionFilter or AuthenticatedDecryptionFilter (as with GCM). The tag size is specified in bytes, not bits.

Finally, do not use a StreamTransformationFilter on a CCM object to recover the plain text in the primary data channel. The StreamTransformationFilter will throw an exception.

Construction

The constructors accept a block cipher parameter (which is usually AES) and an optional DefaultDigestSize parameter. The default DefaultDigestSize parameter is 16. Valid values for DefaultDigestSize are 4, 6, 8, 10, 12, 14, and 16.

CCM< AES >::Encryption e;
CCM< AES, 12 >::Encryption e;   // 96 bit Tag
CCM< AES >::Decryption d;
CCM< AES, 12 >::Decryption d;   // 96 bit Tag

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 CCM mode in datatest.cpp, function TestAuthenticatedSymmetricCipher. Please see the discussion of the TestAuthenticatedSymmetricCipher.

Sample Programs

Two sample programs are provided for CCM mode. In the samples below, a few points are noteworthy:

  • Tag size is a compile time constant
  • The IV/Nonce size is not that of the block cipher
  • Changing Tag size will change the MAC value and hence the tag's value
  • 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 the THROW_EXCEPTION flag during construction.

AE

The first sample, CCM-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 two exceptions are SetKeyWithIV requires an iv size, and SpecifyDataLengths which must be called.

AutoSeededRandomPool prng;

SecByteBlock key( AES::DEFAULT_KEYLENGTH );
prng.GenerateBlock( key, key.size() );

// { 7, 8, 9, 10, 11, 12, 13 }
byte iv[ 12 ];
prng.GenerateBlock( iv, sizeof(iv) );    

// { 4, 6, 8, 10, 12, 14, 16 }
const int TAG_SIZE = 8;

// Plain text
string pdata="Authenticated Encryption";

// Encrypted, with Tag
string cipher;

// Recovered plain text
string rpdata;

/*********************************\
\*********************************/

try
{
    CCM< AES, TAG_SIZE >::Encryption e;
    e.SetKeyWithIV( key, key.size(), iv, sizeof(iv) );
    e.SpecifyDataLengths( 0, pdata.size(), 0 );

    StringSource( pdata, true,
        new AuthenticatedEncryptionFilter( e,
            new StringSink( cipher )
        ) // AuthenticatedEncryptionFilter
    ); // StringSource
}
catch( CryptoPP::Exception& e )
{
    cerr << "Caught Exception..." << endl;
    cerr << e.what() << endl;
    cerr << endl;
}

/*********************************\
\*********************************/

try
{
    CCM< AES, TAG_SIZE >::Decryption d;
    d.SetKeyWithIV( key, key.size(), iv, sizeof(iv) );
    d.SpecifyDataLengths( 0, cipher.size()-TAG_SIZE, 0 );

    AuthenticatedDecryptionFilter df( d,
        new StringSink( rpdata )
    ); // 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 )
    ); // 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 << "Caught Exception..." << endl;
    cerr << e.what() << endl;
    cerr << endl;
}

AEAD

The second sample, CCM-AEAD-Test.zip, performs authenticated encryption with addional authenticated data. It explicitly uses Put on the two channels of data rather than a pipelining. As before, the default data channel provides confidentiality and authentication; the second channel ("AAD"), provides only authentication. The download includes Brian Gladman's test vectors for verifying correctness of the implementation. Additional test vectors can be found in SP 800-38C, Appendix C.

// Gladman's Test Vector 003
byte key[]={0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,
    0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f};

byte iv[]={0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
    0x18,0x19,0x1a,0x1b};

const byte aa[] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
    0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13 };
string adata = string( (const char*)aa, sizeof(aa) );

const byte pa[] = { 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
    0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
    0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37 };
string pdata = string( (const char*)pa, sizeof(pa) );

const int TAG_SIZE = 8;

//CTX e3b201a9f5b71a7a9b1ceaeccd97e70b6176aad9a4428aa5
//TAG 484392fbc1b09951

string cipher, encoded;

// Recovered (decrypted)
string radata, rpdata;

/*********************************\
\*********************************/

try
{
    CCM< AES, TAG_SIZE >::Encryption e;
    e.SetKeyWithIV( key, sizeof(key), iv, sizeof(iv) );
    e.SpecifyDataLengths( adata.size(), pdata.size(), 0 );

    AuthenticatedEncryptionFilter ef( e,
        new StringSink( cipher )
    ); // 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
    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
//

// Tamper with the first and last byte of the
//  encrypted data and tag
// if( cipher.size() > 1 )
// {
//    cipher[ 0 ] ^= 0x01;
//    cipher[ cipher.size()-1 ] ^= 0x01;
// }

/*********************************\
\*********************************/

try
{
    // Not recovered - sent via clear text
    radata = adata;

    // Break the cipher text out into it's
    //  components: Encrypted Data and MAC Tag Value
    string enc = cipher.substr( 0, cipher.length()-TAG_SIZE );
    string tag = cipher.substr( cipher.length()-TAG_SIZE );

    // Sanity checks
    assert( cipher.size() == enc.size() + tag.size() );
    assert( enc.size() == pdata.size() );
    assert( TAG_SIZE == tag.size() );

    CCM< AES, TAG_SIZE >::Decryption d;
    d.SetKeyWithIV( key, sizeof(key), iv, sizeof(iv) );
    d.SpecifyDataLengths( radata.size(), enc.size(), 0 );

    // The object *will* throw an exception
    //  during decryption\verification _if_
    //  verification fails.
    AuthenticatedDecryptionFilter df( d, NULL,
        AuthenticatedDecryptionFilter::MAC_AT_BEGIN |
        AuthenticatedDecryptionFilter::THROW_EXCEPTION
    ); // AuthenticatedDecryptionFilter

    // The order of the following calls are important
    //  when using the MAC_AT_BEGIN flag
    df.ChannelPut( DEFAULT_CHANNEL, tag.data(), tag.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 last
    //  opportunity to check the status of the
    //  data's integrity (both ADATA and PDATA)
    //  before use.
    bool b = false;
    b = df.GetLastResult();
    assert( true == b );

    // Remove data from the channel
    string retrieved;
    int n = -1;

    // Plain text (recovered from enc.data())
    df.SetRetrievalChannel( DEFAULT_CHANNEL );
    n = 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;
}
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

CCM-AE-Test.zip - CCM Test using only PDATA - 5KB

CCM-AEAD-Test.zip - CCM Test using both ADATA and PDATA - 7KB