Pulsars
0 %
Log inSign up

HMAC and the pitfalls

Constant-time comparison and the encryption/MAC ordering

A well-built MAC can still be defeated by the way it is used. Two classic pitfalls: comparing the tags and the ordering of operations.

The timing attack on comparison

Comparing two tags seems trivial. But a byte-by-byte comparison stops at the first differing byte:

def compare(a, b):           # NAIVE and DANGEROUS
    for i in range(len(a)):
        if a[i] != b[i]:
            return False     # early exit!
    return True

This early return makes the execution time vary according to the number of correct bytes at the start. An attacker who finely measures this time can guess the tag byte by byte:

tag tried : A?......   -> very fast rejection
tag tried : 3?......   -> slightly slower rejection  = 1st byte correct!
  ... we fix byte 1, then look for byte 2, etc.

This is a timing attack. In a few thousand timed requests, the entire tag can be reconstructed.

The countermeasure: constant-time comparison

You must compare all the bytes, whatever the result, without an early exit:

def constant_time(a, b):
    if len(a) != len(b): return False
    diff = 0
    for x, y in zip(a, b):
        diff |= x ^ y        # accumulates without ever stopping
    return diff == 0

The time no longer depends on the content. Libraries provide this primitive (hmac.compare_digest in Python, crypto.timingSafeEqual in Node).

The ordering: encrypt then authenticate

When you want both confidentiality (encryption) and authenticity (MAC), the order matters. Let us compare two approaches:

MAC-then-Encrypt          Encrypt-then-MAC   (RECOMMENDED)
------------------        -------------------
tag = MAC(M)              c   = Enc(M)
c   = Enc(M || tag)       tag = MAC(c)
sends c                   sends c || tag

With Encrypt-then-MAC, the receiver first verifies the tag on the ciphertext: if the tag is invalid, they reject without ever decrypting. This protects the decryptor against forged messages and padding oracle attacks. It is the most secure proven construction.

AEAD schemes

In modern practice, we no longer assemble encryption and MAC ourselves: we use an AEAD mode (Authenticated Encryption with Associated Data) which combines the two in a secure and proven way. The most common is AES-GCM; ChaCha20-Poly1305 is a very widespread equivalent on mobile. They provide confidentiality and authenticity in a single primitive, hard to misuse.

In summary

Comparing tags naively (byte by byte with an early exit) leaks information through the execution time: this is a timing attack, whose countermeasure is constant-time comparison. To combine encryption and MAC, Encrypt-then-MAC is the secure construction: the tag is verified before any decryption. In practice, AEAD modes like AES-GCM integrate encryption and authentication into a single robust primitive.