CMAC

Header: #include <cryptopp/cmac.h> | Namespace: CryptoPP Since: Crypto++ 5.6.0 Thread Safety: Not thread-safe per instance; use separate instances per thread

CMAC (Cipher-based Message Authentication Code) is a block cipher-based MAC defined in NIST SP 800-38B. It uses a symmetric block cipher (typically AES) to generate authentication tags, making it a good choice when you’re already using AES and want to avoid adding a hash function dependency.

Quick Example

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include <cryptopp/osrng.h>

using namespace CryptoPP;

AutoSeededRandomPool rng;

// Generate 128-bit key (AES::DEFAULT_KEYLENGTH = 16 bytes)
SecByteBlock key(AES::DEFAULT_KEYLENGTH);
rng.GenerateBlock(key, key.size());

// Compute CMAC
std::string message = "Message to authenticate";
std::string mac;

CMAC<AES> cmac(key, key.size());
StringSource(message, true,
    new HashFilter(cmac,
        new HexEncoder(new StringSink(mac))
    )
);

// mac = "A1B2C3D4E5F6..." (32 hex chars = 16 bytes)

Usage Guidelines

ℹ️
  • Use AES-CMAC for new applications (AES is widely available)
  • Use same key size as underlying cipher (128, 192, or 256 bits for AES)
  • Generate keys using AutoSeededRandomPool
  • Use constant-time comparison for verification
⚠️
  • Don’t use CMAC for password hashing (use Argon2)
  • Don’t use weak ciphers (DES, 3DES)
  • Don’t expose timing information during verification
  • Don’t use the same key for encryption and CMAC

Constructor

// Default constructor (must call SetKey later)
CMAC();

// Constructor with key
CMAC(const byte* key, size_t keyLength);

Parameters

ParameterTypeDescription
keyconst byte*Pointer to key bytes
keyLengthsize_tKey length (16, 24, or 32 bytes for AES)

Methods

SetKey

void SetKey(const byte* key, size_t length);

Sets or changes the authentication key.

Update

void Update(const byte* input, size_t length);

Adds data to the MAC computation.

Final

void Final(byte* mac);

Computes the final MAC tag.

TruncatedFinal

void TruncatedFinal(byte* mac, size_t size);

Computes a truncated MAC tag.

Verify

bool Verify(const byte* mac);

Verifies a MAC tag in constant time.

VerifyTruncated

bool VerifyTruncatedDigest(const byte* mac, size_t size);

Verifies a truncated MAC tag.

Restart

void Restart();

Resets for new message (keeps key).

DigestSize

unsigned int DigestSize() const;

Returns MAC tag size (16 bytes for AES-CMAC).

Complete Examples

Example 1: Basic AES-CMAC

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include <cryptopp/osrng.h>
#include <iostream>

int main() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

    // Generate key
    SecByteBlock key(AES::DEFAULT_KEYLENGTH);  // 128-bit
    rng.GenerateBlock(key, key.size());

    std::string message = "The quick brown fox jumps over the lazy dog";

    // Method 1: Pipeline
    std::string mac1;
    CMAC<AES> cmac1(key, key.size());
    StringSource(message, true,
        new HashFilter(cmac1,
            new HexEncoder(new StringSink(mac1))
        )
    );

    // Method 2: Direct API
    CMAC<AES> cmac2(key, key.size());
    cmac2.Update((const byte*)message.data(), message.size());
    byte mac2[AES::BLOCKSIZE];
    cmac2.Final(mac2);

    std::cout << "AES-CMAC: " << mac1 << std::endl;

    return 0;
}

Example 2: CMAC Verification

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/osrng.h>
#include <iostream>

bool verifyMessage(const std::string& message,
                   const byte* mac,
                   const SecByteBlock& key) {
    using namespace CryptoPP;

    CMAC<AES> cmac(key, key.size());
    cmac.Update((const byte*)message.data(), message.size());

    return cmac.Verify(mac);  // Constant-time comparison
}

int main() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

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

    std::string message = "Authentic message";

    // Compute MAC
    CMAC<AES> cmac(key, key.size());
    cmac.Update((const byte*)message.data(), message.size());
    byte mac[AES::BLOCKSIZE];
    cmac.Final(mac);

    // Verify MAC
    if (verifyMessage(message, mac, key)) {
        std::cout << "Message is authentic" << std::endl;
    } else {
        std::cout << "Message has been tampered with!" << std::endl;
    }

    // Tamper with message
    std::string tamperedMessage = "Tampered message";
    if (!verifyMessage(tamperedMessage, mac, key)) {
        std::cout << "Tampering detected!" << std::endl;
    }

    return 0;
}

Example 3: File Authentication

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/files.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>

// Compute CMAC and return raw bytes
void computeFileCMAC(const std::string& filename,
                     const SecByteBlock& key,
                     byte* mac) {
    using namespace CryptoPP;

    CMAC<AES> cmac(key, key.size());

    FileSource(filename.c_str(), true,
        new HashFilter(cmac,
            new ArraySink(mac, AES::BLOCKSIZE)
        )
    );
}

// Verify using constant-time comparison
bool verifyFileCMAC(const std::string& filename,
                    const byte* expectedMAC,
                    const SecByteBlock& key) {
    using namespace CryptoPP;

    CMAC<AES> cmac(key, key.size());

    FileSource(filename.c_str(), true,
        new HashFilter(cmac)
    );

    // Constant-time verification
    return cmac.Verify(expectedMAC);
}

Example 4: API Request Signing

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include <sstream>

std::string signAPIRequest(const std::string& method,
                           const std::string& path,
                           const std::string& timestamp,
                           const std::string& body,
                           const SecByteBlock& apiKey) {
    using namespace CryptoPP;

    // Create canonical string
    std::ostringstream canonical;
    canonical << method << "\n"
              << path << "\n"
              << timestamp << "\n"
              << body;

    std::string signature;
    CMAC<AES> cmac(apiKey, apiKey.size());

    StringSource(canonical.str(), true,
        new HashFilter(cmac,
            new HexEncoder(new StringSink(signature))
        )
    );

    return signature;
}

// Usage:
// std::string sig = signAPIRequest("POST", "/api/v1/data", "2025-01-15T12:00:00Z", body, key);
// Set header: X-Signature: sig

Example 5: 256-bit Key CMAC

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/osrng.h>

int main() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

    // 256-bit key for AES-256-CMAC
    SecByteBlock key(AES::MAX_KEYLENGTH);  // 32 bytes
    rng.GenerateBlock(key, key.size());

    CMAC<AES> cmac(key, key.size());

    std::string message = "High security message";
    cmac.Update((const byte*)message.data(), message.size());

    byte mac[AES::BLOCKSIZE];
    cmac.Final(mac);

    return 0;
}

Example 6: Truncated CMAC

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/osrng.h>

// Some protocols use truncated MACs (e.g., 8 bytes instead of 16)
void truncatedCMAC() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

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

    std::string message = "Message";

    CMAC<AES> cmac(key, key.size());
    cmac.Update((const byte*)message.data(), message.size());

    // Get only 8 bytes (64 bits) of MAC
    byte truncatedMAC[8];
    cmac.TruncatedFinal(truncatedMAC, sizeof(truncatedMAC));

    // Verify truncated MAC
    cmac.Restart();
    cmac.Update((const byte*)message.data(), message.size());
    bool valid = cmac.VerifyTruncatedDigest(truncatedMAC, sizeof(truncatedMAC));
}

Example 7: Incremental Update

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>
#include <cryptopp/osrng.h>

void incrementalCMAC() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

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

    CMAC<AES> cmac(key, key.size());

    // Process data in chunks
    std::string part1 = "First part of ";
    std::string part2 = "the message to ";
    std::string part3 = "authenticate";

    cmac.Update((const byte*)part1.data(), part1.size());
    cmac.Update((const byte*)part2.data(), part2.size());
    cmac.Update((const byte*)part3.data(), part3.size());

    byte mac[AES::BLOCKSIZE];
    cmac.Final(mac);

    // Same result as hashing the full message at once
}

CMAC vs HMAC

FeatureCMACHMAC
Based onBlock cipher (AES)Hash function (SHA)
Output sizeBlock size (16 bytes for AES)Hash size (32 for SHA-256)
Key sizeCipher key size (16-32 bytes)Any length
Speed (with AES-NI)Very fastFast
Speed (without AES-NI)MediumFast
StandardNIST SP 800-38BRFC 2104

When to use CMAC:

  • Already using AES for encryption
  • Hardware AES acceleration available
  • Want to minimize dependencies
  • NIST compliance required

When to use HMAC:

  • General-purpose MAC
  • Variable-length output needed
  • No AES acceleration available

Performance

Benchmarks (approximate)

ConfigurationSpeed
AES-128-CMAC with AES-NI~2 GB/s
AES-256-CMAC with AES-NI~1.5 GB/s
AES-128-CMAC (software)~200 MB/s

Hardware Detection

#include <cryptopp/aes.h>
#include <iostream>

void checkHardwareSupport() {
    using namespace CryptoPP;

    AES::Encryption aes;
    std::cout << "AES provider: " << aes.AlgorithmProvider() << std::endl;

    // Output:
    // "AES-NI" - Intel/AMD with hardware acceleration
    // "ARMv8" - ARM with Crypto Extensions
    // "C++" - Software implementation
}

Security Properties

  • Primitive: CMAC is a message authentication code (MAC) built from a block cipher. In Crypto++, CMAC<AES> is the most common instantiation.

  • Block & tag size:

    • For CMAC<AES> the block size is 128 bits.
    • The MAC tag can be up to 128 bits (16 bytes); shorter tags are supported via truncation.
  • Security level / bounds:

    • Security is fundamentally limited by the block/tag size, not the AES key size.
    • For AES-CMAC with a 128-bit tag, the generic forgery bound is on the order of a birthday attack, i.e. about 2^64 blocks per key.
    • In practice, follow NIST guidance and treat ~2^48 message blocks per key as a conservative upper limit.
  • Key sizes:

    • AES keys: 128, 192, or 256 bits (all supported by CMAC<AES>).
    • Larger AES keys protect against block-cipher key-recovery, but do not increase the birthday bound imposed by the 128-bit block/tag size.
  • Deterministic MAC: CMAC is deterministic: for a fixed key and message, the tag is always the same. There is no nonce or randomisation in the scheme itself.

Security Notes

  • Message / data limits:

    • Keep total usage per key well below the theoretical birthday bound.
    • As a rule of thumb, re-key long before you reach ~2^48 blocks of data authenticated under a single CMAC key.
    • For ciphers with a 64-bit block size (e.g. 3DES), limits are much stricter (birthday bound ~2^32 blocks); such variants are legacy and should only be used when required for compatibility.
  • Tag length choices:

    • Use a 128-bit tag for new designs unless there is a strong reason to truncate.
    • If you truncate (e.g. to 64 or 96 bits), your forgery resistance drops to roughly 2^(-t) for a t-bit tag; choose t accordingly.
  • Key separation:

    • Do not reuse the same key for CMAC and for encryption, or across different MAC algorithms.
    • Derive separate sub-keys for encryption, CMAC, and any other MAC/KDF using a key hierarchy appropriate for your protocol.
  • Key quality & storage:

    • CMAC keys must be uniformly random and secret (e.g. generated via AutoSeededRandomPool).
    • Store keys in secure memory where possible and protect them according to your application’s key-management policy.
  • Verification:

    • Always perform tag checks in constant time.
    • In Crypto++, prefer CMAC::Verify / VerifyTruncatedDigest or other constant-time comparison utilities, rather than operator== on strings or buffers.
  • No confidentiality:

    • CMAC provides integrity and authenticity only. It does not encrypt or hide the message.
    • For authenticated encryption, use AES-GCM or ChaCha20-Poly1305 rather than “encrypt + CMAC” unless your protocol is explicitly designed and reviewed.
  • Replay & protocol context:

    • Because CMAC is deterministic, you must handle replay protection at the protocol level (sequence numbers, nonces, counters, or timestamps).
    • When authenticating structured data, include all relevant context (protocol version, direction, headers, etc.) in the CMAC input so that tags cannot be replayed out of context.

Thread Safety

CMAC objects are not thread-safe:

// WRONG - shared across threads
CMAC<AES> sharedCMAC(key, keyLen);

// CORRECT - per-thread
void computeInThread(const std::string& message, const SecByteBlock& key) {
    CMAC<AES> cmac(key, key.size());
    cmac.Update((const byte*)message.data(), message.size());
    byte mac[AES::BLOCKSIZE];
    cmac.Final(mac);
}

Error Handling

#include <cryptopp/cmac.h>
#include <cryptopp/aes.h>

void safeCMAC(const SecByteBlock& key, const std::string& message) {
    using namespace CryptoPP;

    try {
        // Key size validation happens in SetKey
        CMAC<AES> cmac(key, key.size());

        cmac.Update((const byte*)message.data(), message.size());

        byte mac[AES::BLOCKSIZE];
        cmac.Final(mac);

    } catch (const InvalidKeyLength& e) {
        std::cerr << "Invalid key length: " << e.what() << std::endl;
        // AES requires 16, 24, or 32 byte keys
    } catch (const Exception& e) {
        std::cerr << "CMAC error: " << e.what() << std::endl;
    }
}

Alternative Ciphers

While AES is most common, CMAC works with any block cipher:

// 3DES-CMAC (legacy, not recommended for new applications)
#include <cryptopp/des.h>
CMAC<DES_EDE3> cmac3des(key, DES_EDE3::DEFAULT_KEYLENGTH);

// Camellia-CMAC
#include <cryptopp/camellia.h>
CMAC<Camellia> cmacCamellia(key, Camellia::DEFAULT_KEYLENGTH);

See Also