Safeguard
Application Security•

ASVS 5.0 V11.2.4: Make Crypto Comparisons Constant Time

ASVS V11.2.4 requires constant time crypto operations with no short circuit comparisons. Use compare_digest and friends, and test for timing differences.

Safeguard Security Team
3 min read

ASVS 5.0 V11.2.4 (Level 3) requires that all cryptographic operations are constant time, with no short circuit behaviour in comparisons, calculations or returns that could leak information through timing.

Why it matters

A normal string comparison stops at the first differing byte. If you compare a submitted MAC with the expected one that way, response time tells the attacker how many leading bytes were right. Repeating the request lets them recover a valid tag one byte at a time, over a network, with no key.

The same idea applies to early returns inside crypto code, table lookups indexed by secret data, and branches on secret values.

How to implement it

For comparisons of secrets (MACs, tokens, password hashes, signatures), use the constant time function your platform provides and never ==.

import hmac
if not hmac.compare_digest(expected_tag, received_tag):
    return reject()
const crypto = require('crypto');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(received, 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return reject();
if subtle.ConstantTimeCompare(expected, received) != 1 { return reject() }

Note the length check in the Node example: timingSafeEqual throws on unequal lengths, and length alone usually is not secret. Do the check first and return the same failure.

Beyond comparisons, rely on libraries that are designed to be constant time for the primitive itself; do not rewrite them. In your own code, avoid branching on secret values: do the same work on both outcomes, and return one error for every failure reason.

Verify the full request path too. If a failed MAC returns instantly and a passed one triggers a slow database call, the timing difference leaks the result even though the compare was safe. Normalise by doing the lookup in both cases or by rejecting uniformly after a fixed step.

How to test it

Grep for ==, equals, strcmp and === applied to variables named tag, mac, token, signature, digest or hash. Each hit is a review item.

Then measure. Send many requests with a tag that fails at the first byte and many with one that fails at the last byte, interleaved, and compare median latency. A statistical test over thousands of samples should show no significant gap. Remember that network noise is large, so test against a local instance and use a tool built for timing analysis where you can.

Common mistakes

  • Using == on hex strings of MACs
  • Comparing hashes after converting to lowercase in a data dependent way
  • Constant time compare, followed by an early return with a distinct error
  • Different status codes for different failure causes
  • Assuming the compiler or interpreter preserves constant time for hand written loops

Quick check

Why does a normal string compare leak?

It stops at the first mismatch, so run time reveals how many leading bytes matched.

Is the length of a MAC secret?

Usually not, so a length check before comparing is acceptable.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.