Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
255: The hmac Module for Message Authentication
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.newwithhashlib.sha256. - Create a verification function
verify_signature(key, message, received_sig)that returnsTrueorFalseusinghmac.compare_digest. - Test your function with both the correct signature and a deliberately incorrect one (e.g., changing one character in the hex string).
There are no comments for now.