Skip to Content
Course content

255: The hmac Module for Message Authentication

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine we're building a system where a payment provider sends a webhook to our server every time a customer pays for a subscription. The provider sends a JSON payload, and we need to be 100% sure that the request actually came from them and wasn't spoofed by someone trying to get a free account.

The provider gives us a shared secret key—something like "super-secret-webhook-key". Now, my first instinct might be to just hash the message and the key together. Let's see how that looks in code.

The "Just Glue Them Together" Approach

import hashlib

secret_key = b"super-secret-webhook-key"
message = b"customer_id=123&status=paid"

# Let's just concatenate them and hash it
signature = hashlib.sha256(secret_key + message).hexdigest()
print(f"Signature: {signature}")

At first glance, this looks fine. If you don't know the key, you can't produce that specific SHA-256 hash. But here's the thing: this is actually vulnerable to something called a "length extension attack." Because of how Merkle-Damgård hashes (like SHA-256) work, an attacker who sees the message and the signature can sometimes append their own data to the end of the message and calculate a valid new signature without ever knowing the secret key. It's a bit of a deep rabbit hole, but it's exactly why we don't just "glue" keys to messages.

Switching to the HMAC Standard

Instead of rolling my own authentication scheme, I'm going to use the hmac module. HMAC (Hash-based Message Authentication Code) is designed specifically to prevent those length extension attacks by hashing the key and message in two separate passes with different internal padding.

Let's rewrite that previous attempt using the actual module:

import hmac
import hashlib

secret_key = b"super-secret-webhook-key"
message = b"customer_id=123&status=paid"

# hmac.new takes the key, the message, and the digest algorithm
signature = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
print(f"HMAC Signature: {signature}")

This is much cleaner. I'm telling Python: "Use this key, this message, and SHA-256 to create a secure MAC." Note that I'm using bytes here; hmac expects bytes, not strings, so if you have strings, you'll need to call .encode() on them first.

The Subtle Danger of the == Operator

Now, let's say the webhook hits our server. We receive the message and a signature in the header. The natural next step is to calculate the signature ourselves and compare it to the one we received. I'd probably write something like this:

received_sig = "a1b2c3d4..." # From the request header
calculated_sig = hmac.new(secret_key, message, hashlib.sha256).hexdigest()

if received_sig == calculated_sig:
    print("Valid request!")
else:
    print("Fraud detected!")

Wait. I almost let a critical security flaw slide right past me. The == operator in Python compares strings character by character and returns False the very instant it finds a mismatch. This creates a "timing attack." An attacker can send thousands of requests and measure the nanoseconds it takes for the server to respond. If the first character is correct, the response takes slightly longer. They can brute-force the signature one character at a time just by watching the clock.

To fix this, we use hmac.compare_digest(). This function takes "constant time," meaning it checks every character regardless of whether a mismatch was found early on. It hides the timing difference from the attacker.

import hmac

# The secure way to compare signatures
if hmac.compare_digest(received_sig, calculated_sig):
    print("Valid request!")
else:
    print("Fraud detected!")

It's a small change, but in the world of security, these "small" details are usually where things break. Using hmac.new for creation and hmac.compare_digest for verification is the professional standard for message authentication in Python.




📋 Practical Task

Exercise: Securing a Simulated API Request

You are tasked with implementing a signature verification system for a simulated API. You have been given a shared secret key and a request payload. Your goal is to write a script that generates a valid HMAC-SHA256 signature for the payload and then verifies a provided signature using a constant-time comparison.

Requirements:

  • Use the secret key: b"dev-secret-key-2024"
  • Use the payload: b"user_id=99&action=delete_account"
  • Generate the signature using hmac.new with hashlib.sha256.
  • Create a verification function verify_signature(key, message, received_sig) that returns True or False using hmac.compare_digest.
  • Test your function with both the correct signature and a deliberately incorrect one (e.g., changing one character in the hex string).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.