HIGH bleichenbacher attackdjangohmac signatures

Bleichenbacher Attack in Django with Hmac Signatures

Bleichenbacher Attack in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A Bleichenbacher attack is a cryptographic padding oracle technique originally described against PKCS#1 v1.5–style RSA encryption and signatures. In the context of Django, it can manifest when an application verifies HMAC-based signatures (for example, signed cookies, signed querystring tokens, or webhook payloads) in a way that leaks information about signature validity through timing differences or error messages.

Consider a Django view that validates a query parameter signature over a payload using HMAC-SHA256:

import hmac
import hashlib
from django.http import HttpResponse, HttpResponseBadRequest
from django.conf import settings

def verify_payload(request):
    data = request.GET.get('data', '')
    received_sig = request.GET.get('signature', '')
    if not data or not received_sig:
        return HttpResponseBadRequest('Missing data or signature')
    expected_sig = hmac.new(
        key=settings.SECRET_KEY.encode(),
        msg=data.encode(),
        digestmod=hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected_sig, received_sig):
        return HttpResponseBadRequest('Invalid signature')
    return HttpResponse('OK')

If the comparison is performed with hmac.compare_digest, the comparison is constant-time and does not itself act as an oracle. However, a vulnerable implementation might compare character-by-character and return early on mismatch, or return distinct errors for malformed base64 versus invalid signatures, enabling an attacker to iteratively recover the correct signature byte-by-byte.

More critically, Bleichenbacher-style attacks are not only about signatures: if the same HMAC key is reused across different security boundaries (e.g., signing user identifiers in an IDOR context or tokens used for password reset), an attacker who can obtain signature validity feedback for one context may infer secrets or elevate privileges. For example, an endpoint that signs a user’s role in a cookie and then reflects different behavior depending on signature validity can become an oracle for that cookie’s HMAC. In a webhook scenario where a signature is verified without authentication, an attacker can send many crafted payloads and observe success/failure responses to mount a padding oracle and recover the signing key or forge valid tokens.

Django’s use of HMAC signatures is common with packages that implement signed cookies, JSON Web Tokens, or webhook integrations. When these endpoints do not enforce strict input validation, avoid leaking timing or error information, and do not scope keys per context or per request, the combination of predictable data and an oracle-friendly verification path can expose the system to Bleichenbacher-style recovery.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

Remediation focuses on eliminating timing leaks, avoiding signature reuse across security domains, and ensuring deterministic verification behavior. Always use hmac.compare_digest for signature comparisons; avoid early-exit or branching logic based on signature validity.

Example of a safe verification pattern in Django:

import hmac
import hashlib
from django.http import HttpResponse, HttpResponseBadRequest
from django.conf import settings

def verify_payload_safe(request):
    data = request.GET.get('data', '')
    received_sig = request.GET.get('signature', '')
    if not data or not received_sig:
        return HttpResponseBadRequest('Missing data or signature')
    expected_sig = hmac.new(
        key=settings.SECRET_KEY.encode(),
        msg=data.encode(),
        digestmod=hashlib.sha256
    ).hexdigest()
    # Constant-time comparison; no early returns on mismatch
    if not hmac.compare_digest(expected_sig, received_sig):
        # Return a generic error to avoid leaking validity details
        return HttpResponseBadRequest('Invalid request')
    # Only after constant-time verification, process the data
    return HttpResponse('OK')

To mitigate cross-context Bleichenbacher risks, scope your signing keys or use distinct contexts. For example, include a context identifier in the signed message:

import hmac
import hashlib
import json

def make_token(user_id, context='auth', expiration=None):
    payload = {
        'context': context,
        'user_id': user_id,
        'exp': expiration
    }
    message = json.dumps(payload, sort_keys=True).encode()
    sig = hmac.new(
        key=settings.SECRET_KEY.encode(),
        msg=message,
        digestmod=hashlib.sha256
    ).hexdigest()
    return payload, sig

def verify_token(received_payload, received_sig, expected_context='auth'):
    # Ensure payload contains the expected context
    if received_payload.get('context') != expected_context:
        return False
    expected_sig = hmac.new(
        key=settings.SECRET_KEY.encode(),
        msg=json.dumps(received_payload, sort_keys=True).encode(),
        digestmod=hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_sig, received_sig)

For webhook signatures, adopt a scheme where the signature covers a canonical representation of the payload and a timestamp/nonce, and perform verification before any side-effects. Avoid using the same HMAC key for signing tokens, cookies, and webhooks; rotate keys periodically and store them using a secure secrets manager integrated with your deployment.

Finally, ensure your error handling does not distinguish between a malformed request and an invalid signature. By removing oracle behavior and scoping signatures to their security context, you remove the conditions that enable practical Bleichenbacher attacks against HMAC-based protections in Django.

Frequently Asked Questions

Why is using hmac.compare_digest important in Django HMAC verification?
hmac.compare_digest performs a constant-time comparison, preventing timing side-channels that an attacker can use to learn about the correct signature byte-by-byte, which is the basis of Bleichenbacher-style oracle attacks.
How can I prevent HMAC signature reuse across different security contexts in Django?
Include a context identifier (e.g., 'auth', 'webhook') inside the signed payload and verify it before checking the signature; use different keys or key identifiers per context and rotate keys periodically.