Timing Attack in Flask with Hmac Signatures
Timing Attack in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A timing attack against HMAC verification in Flask exploits the fact that many implementations compare signature bytes sequentially and return early on the first mismatch. When the comparison is performed with a simple equality check (e.g., hmac.compare_digest is not used or is bypassed), the runtime can vary measurably based on how many leading bytes match. In Flask, if the developer uses == or manually iterates bytes to compare the computed signature with the client-supplied signature, an attacker can infer correctness byte-by-byte by measuring response times. This is particularly relevant when the server computes HMAC over a secret key and public data, and the attacker can craft many requests while observing timing differences.
In practice, this can occur when the API uses HMAC for request authentication (e.g., signing a request body or selected headers with a shared secret) but the comparison is not constant-time. For example, a Flask route might read a client-supplied X-Signature header, compute hmac.new(key, payload, 'sha256').hexdigest(), and then compare it with the header using standard string comparison. Because standard comparison short-circuits, an attacker who can send many authenticated requests (or make blind guesses about parts of the signature) can gradually learn the correct HMAC output. Successful inference of the full HMAC allows an attacker to forge valid requests, potentially bypassing authentication or tampering with message integrity.
The risk is compounded when the secret key is static and the payload structure is predictable, as the attacker does not need to recover the key itself—only a valid signature for a chosen message. In a black-box scenario where the scanner can only send requests and measure timing, this manifests as a detectable deviation in response times across many requests. Although the scan tests unauthenticated attack surfaces, if an endpoint accepts HMAC-signed input without requiring prior authentication, the timing discrepancy can be observed externally. The 12 security checks run in parallel, and the BOLA/IDOR and Unsafe Consumption checks may surface timing-related anomalies when signature validation logic is involved.
Using non-constant-time comparison functions is the root cause; even small variations in CPU cycles, branch prediction, or memory access patterns can be measurable over networked requests. This is not a theoretical concern—researchers have demonstrated practical timing attacks against HMAC verification in networked services. For Flask applications, the mitigation is to ensure all signature comparisons use constant-time utilities and that no early-exit logic exists in the verification path.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
Remediation centers on replacing standard equality checks with a constant-time comparison and ensuring the HMAC computation does not leak information via side channels. In Flask, always use hmac.compare_digest to compare the computed signature with the received signature. This function is designed to take equal time regardless of where the first mismatch occurs, preventing timing-based inference.
Below is a secure example of HMAC-SHA256 verification in Flask. The server computes the HMAC over the raw request body using a shared secret and compares it to the value provided by the client in a custom header. The comparison is performed with hmac.compare_digest, and the route returns a 400 for malformed input and 401 for invalid signatures without revealing which part failed.
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
# In production, load this from a secure secret store
SECRET_KEY = b'your-256-bit-secret'
def verify_hmac_signature(data: bytes, received_signature_hex: str) -> bool:
computed = hmac.new(SECRET_KEY, data, hashlib.sha256).hexdigest()
# Constant-time comparison to prevent timing attacks
return hmac.compare_digest(computed, received_signature_hex)
@app.route('/api/action', methods=['POST'])
def api_action():
signature = request.headers.get('X-Signature')
if not signature:
abort(400, description='Missing signature')
if not verify_hmac_signature(request.get_data(), signature):
abort(401, description='Invalid signature')
# Process the verified request
return {'status': 'ok'}, 200
if __name__ == '__main__':
app.run()
Additional best practices include:
- Always compute HMAC over the exact bytes the client signed; avoid implicit encoding conversions that differ between client and server.
- Use a strong, randomly generated secret stored outside the application code (e.g., environment variables or a secrets manager).
- Ensure the signature is encoded consistently (e.g., hex or base64) and that normalization is applied before comparison.
- Combine HMAC verification with other transport protections (e.g., TLS) to prevent on-path manipulation that could obscure timing differences but still corrupt integrity.
By adopting constant-time comparison and disciplined HMAC handling, Flask applications can neutralize timing-based inference while preserving the integrity benefits of HMAC authentication.