CMAC
Documentation |
#include <cryptopp/cmac.h>
|
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 Programs
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 initialization vector (IV) (see section 6.2 of SP 800-38B). Calling IVRequirement
on a CMAC object will return INTERNALLY_GENERATED_IV
. Attempting to set an initialization vector 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