CMAC

From Crypto++ Wiki
Jump to navigation Jump to search
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