TL;DR
This guide shows you how to use a Bloom filter to stop replay attacks against signed HTTP requests. We’ll cover setting up the filter, adding signatures to your requests, and checking for duplicates before processing them.
What are Replay Attacks?
A replay attack happens when someone intercepts a valid HTTP request (like one with a digital signature) and sends it again. If your system doesn’t check if the request has already been processed, it might execute the same action multiple times – potentially causing problems like double payments or unintended data changes.
How Bloom Filters Help
A Bloom filter is a space-efficient way to test if an element is present in a set. It can tell you definitively if something isn’t in the set, but it might give a false positive (saying something is there when it isn’t). For replay attack prevention, this is okay – we just need to be sure we don’t process the same request more than once. It’s much faster and uses less memory than storing every request ID.
Setting Up Your Bloom Filter
- Choose a Library: Several libraries are available for different languages. For Python,
pybloomfilteris popular.pip install pybloomfilter - Determine Filter Size and Hash Functions: The size of the filter (number of bits) affects its false positive rate. More bits mean fewer false positives but more memory usage. The number of hash functions also impacts performance and accuracy.
Use this formula to estimate the required filter size (m) based on expected number of items (n) and desired false positive probability (p):
m = -n * ln(p) / (ln(2)^2). A good starting point is often a few hash functions (e.g., 3-7). - Create the Filter:
from pybloomfilter import BloomFilter n = 10000 # Expected number of requests p = 0.01 # Desired false positive rate (1%) bloomf = BloomFilter(capacity=n, error_rate=p)
Adding Signatures to Requests
- Generate a Unique Request ID: Create a unique identifier for each request. This could be a timestamp combined with a random number, or a UUID.
import uuid request_id = str(uuid.uuid4()) - Sign the Request ID: Use your chosen signing method (e.g., HMAC-SHA256) to create a signature based on the request ID and a secret key.
import hmac hash_key = b'your_secret_key' message = request_id.encode('utf-8') signature = hmac.new(hash_key, message, digestmod='sha256').hexdigest() - Include Signature in Request: Add both the
request_idandsignatureas headers to your HTTP request.Example:
- Header:
X-Request-ID: <request_id> - Header:
X-Signature: <signature>
- Header:
Checking for Replay Attacks
- Verify Signature: Before processing the request, verify that the signature is valid using your secret key.
import hmac hash_key = b'your_secret_key' message = request_id.encode('utf-8') expected_signature = hmac.new(hash_key, message, digestmod='sha256').hexdigest() if signature != expected_signature: # Signature is invalid - reject the request print("Invalid signature!") return False - Check Bloom Filter: If the signature is valid, check if the
request_idalready exists in the Bloom filter.if request_id in bloomf: # Request ID is likely a duplicate - reject the request print("Possible replay attack!") return False - Add to Filter: If the
request_idisn’t in the filter, add it.bloomf.add(request_id) - Process Request: Now you can safely process the request.
Important Considerations
- Filter Lifetime: Bloom filters grow over time. You may need to periodically reset or recreate them to maintain performance and accuracy.
- False Positives: Be prepared for occasional false positives. Your system should be designed to handle these gracefully (e.g., by logging the event and potentially requiring additional verification).
- Secret Key Security: Protect your secret key! If it’s compromised, attackers can create valid signatures and bypass the replay protection.