CCM Mode
Documentation |
#include <cryptopp/ccm.h>
|
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 mode 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 mode ensures there is sufficient interleaving of encryption and authentication components before XORing the plain text and key stream.
The mode operates on both plain text data and additionally authenticated data called AAD. AAD data is only authenticated, and it is not encrypted. AAD is useful in some situations, like authenticating the fields of an IP packet when using IPsec. Obviously the source and destination addresses, and the source and destination ports cannot be encrypted for routing.
CCM restricts the size of AAD to less than or equal to 264-1; and plain text data 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]).
CCM Mode 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 AEAD 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.
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 plain text data (plain text data), CCM mode will provide authentication assurances over the AAD and provide both confidentiality and authentication over the plain text data. Note that either AAD or plain text data 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 plain text data, while Tag is a truncation of the authentication code (MAC) over both the AAD and plain text data. 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 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 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 AAD and plain text data 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
.
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 (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 two exceptions are SetKeyWithIV
requires an initialization vector 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 ss1( 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 ss2( 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 AAD must still be pushed before plain text 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();
Downloads
CCM-AE-Test.zip - CCM Test using only plain text data - 5KB
CCM-AEAD-Test.zip - CCM Test using both AAD and plain text data - 7KB