CMAC
CMAC is a block cipher-based MAC algorithm specified in NIST SP 800-38B. A CMAC is the block cipher equivalent of an HMAC. CMACs can be used when a block cipher is more readily available than a hash function. A CMAC accepts variable length messages (unlike CBC-MAC) and is equivalent to OMAC1.
Sample Program
The sample programs below demonstrate using filters in a pipeline and C-style input/output using Update, Final and Verify on using HashTransofrmation base class.
Pipeline and Filters
The sample program below demonstrates a CMAC with AES using filters. The key is declared on the stack and a SecByteBlock is used to ensure the sensitive material is zeroized. Similar could be used for the message and MAC if desired.
AutoSeededRandomPool prng; SecByteBlock key(AES::DEFAULT_KEYLENGTH); prng.GenerateBlock(key, key.size()); string plain = "CMAC Test"; string mac, encoded; /*********************************\ \*********************************/ // Pretty print key encoded.clear(); StringSource ss1(key, key.size(), true, new HexEncoder( new StringSink(encoded) ) // HexEncoder ); // StringSource cout << "key: " << encoded << endl; cout << "plain text: " << plain << endl; /*********************************\ \*********************************/ try { CMAC<AES> cmac(key.data(), key.size()); StringSource ss2(plain, true, new HashFilter(cmac, new StringSink(mac) ) // HashFilter ); // StringSource } catch(const CryptoPP::Exception& e) { cerr << e.what() << endl; exit(1); } /*********************************\ \*********************************/ // Pretty print encoded.clear(); StringSource ss3(mac, true, new HexEncoder( new StringSink(encoded) ) // HexEncoder ); // StringSource cout << "cmac: " << encoded << endl;
A typical output is shown below. Note that each run will produce different results because the key is randomly generated.
$ ./test.exe key: B8B34DA2D4C4D578D8494390E3DFE7A7 plain text: CMAC Test cmac: 8C72D147FF9B25699B6898379AF44D8F
Though a CMAC uses a block cipher, the CMAC does not use an IV (see section 6.2 of SP 800-38B). Calling IVRequirement on a CMAC object will return INTERNALLY_GENERATED_IV. Attempting to set an IV will result in exception, AlgorithmParametersBase: parameter "IV" not used. The following will produce the exception when attempting to set an IV:
SecByteBlock key(AES::DEFAULT_KEYLENGTH); // Null string SecByteBlock iv(AES::BLOCKSIZE); // Null string ... CMAC<AES> cmac; cmac.SetKeyWithIV(key, key.size(), iv);
To verify a CMAC on a message, use a HashVerificationFilter.
try { CMAC<AES> cmac(key.data(), key.size()); const int flags = HashVerificationFilter::THROW_EXCEPTION | HashVerificationFilter::HASH_AT_END; StringSource ss(plain + mac, true, new HashVerificationFilter(cmac, NULL, flags) ); // StringSource cout << "Verified message" << endl; } catch(const CryptoPP::Exception& e) { cerr << e.what() << endl; ... }
You can also specify the length of the CMAC. By default, the HashVerificationFilter uses the size of the underlying block cipher's block size.
cout << "NIST SP 800-38B, Example 12" << endl; key = HexDecode("603deb10 15ca71be 2b73aef0 857d7781 1f352c07 3b6108d7 2d9810a3 0914dff4"); message = HexDecode("6bc1bee2 2e409f96 e93d7e11 7393172a ae2d8a57 1e03ac9c 9eb76fac 45af8e51" \ "30c81c46 a35ce411 e5fbc119 1a0a52ef f69f2445 df4f9b17 ad2b417b e66c3710"); mac = HexDecode("e1992190 549f6ed5 696a2c05 6c315410"); CMAC<AES> cmac; cmac.SetKey(key.data(), key.size()); try { StringSource ss(message + mac, true, new HashVerificationFilter(cmac, NULL, THROW_EXCEPTION | HASH_AT_END, mac.size()) ); // StringSource } catch(const CryptoPP::Exception& e) { cerr << e.what() << endl; }
We can tamper with a message as follows, which will cause the HashVerificationFilter to throw the exception, HashVerificationFilter: message hash or MAC not valid:
CMAC<AES> cmac(key.data(), key.size()); // Tamper with message plain[0] ^= 0x01; StringSource ss(plain + mac, true, new HashVerificationFilter(cmac, NULL, THROW_EXCEPTION | HASH_AT_END) ); // StringSource
Switching to another block cipher, such as TDEA, is a simple as the following:
CMAC< DES_EDE3 > cmac(key.data(), key.size()); StringSource ss(plain, true, new HashFilter(cmac, new StringSink(mac) ) // HashFilter ); // StringSource
HashTransformation
The sample program below demonstrates a CMAC with AES using C-style input/output and Update, Final and Verify from the HashTransformation base class.
Under the hood, the Pipeline and Filter example does this for you. The HashFilter knows to call Update and Final, while the HashVerificationFilter knows to call Update and Verify.
#include "cryptlib.h" #include "secblock.h" #include "osrng.h" #include "files.h" #include "cmac.h" #include "aes.h" #include "hex.h" using namespace CryptoPP; #include <iostream> #include <string> using namespace std; int main(int argc, char* argv[]) { AutoSeededRandomPool prng; SecByteBlock key(AES::DEFAULT_KEYLENGTH); prng.GenerateBlock(key, key.size()); string mac, plain = "CMAC Test"; HexEncoder encoder(new FileSink(cout)); /*********************************\ \*********************************/ // Pretty print key cout << "key: "; encoder.Put(key, key.size()); encoder.MessageEnd(); cout << endl; cout << "plain text: "; encoder.Put((const byte*)plain.data(), plain.size()); encoder.MessageEnd(); cout << endl; /*********************************\ \*********************************/ try { CMAC<AES> cmac(key.data(), key.size()); cmac.Update((const byte*)plain.data(), plain.size()); mac.resize(cmac.DigestSize()); cmac.Final((byte*)&mac[0]); } catch(const CryptoPP::Exception& e) { cerr << e.what() << endl; exit(1); } /*********************************\ \*********************************/ // Pretty print cout << "cmac: "; encoder.Put((const byte*)mac.data(), mac.size()); encoder.MessageEnd(); cout << endl; /*********************************\ \*********************************/ // Verify try { CMAC<AES> cmac(key.data(), key.size()); cmac.Update((const byte*)plain.data(), plain.size()); // Call Verify() instead of Final() bool verified = cmac.Verify((byte*)&mac[0]); if (!verified) throw Exception(Exception::DATA_INTEGRITY_CHECK_FAILED, "CMAC: message MAC not valid"); cout << "Verified message MAC" << endl; } catch(const CryptoPP::Exception& e) { cerr << e.what() << endl; exit(1); } return 0; }
Typical output of the program is:
$ ./test.exe key: 54FE5717559053CF76A14C86582B1892 plain text: 434D41432054657374 cmac: 74A8A4E4200D945BECCA16314C3B4ED8 Verified message MAC
Downloads
CMAC-AES-Filter.zip - Demonstrates an AES based CMAC with filters
Cmac-sp800-38b.zip - Program that consumes NIST SP 800-38B text vectors