A New Defense for Pwned Passwords: Randomized Padding
The Pwned Passwords API, part of Troy Hunt's Have I Been Pwned service, handles tens of millions of requests each day from browsers, extensions, and online services. Thanks to Cloudflare's caching, roughly 99% of those requests never hit the origin server. Today, the API is adding a new layer of protection: clients can now request responses padded with random data.
The goal is to thwart passive traffic analysis. An attacker watching encrypted traffic could potentially infer which hashed-password bucket a user is querying by measuring the size of the API response. Security researcher Matt Weir, met at PasswordsCon in Stockholm, demonstrated this risk with a proof-of-concept and pushed for the padding solution.
To use the feature, clients send the header Add-Padding: true. The response is padded to a minimum of 800 hash suffixes, plus a random extra 0–200 entries. That padding consists of randomly generated hash suffixes with a usage count of 0.
Implementations that match strings on the hash suffix will find no performance penalty—most code already ignores the count field and simply searches for the suffix. The chance of a false positive match against a random padding entry is astronomically low: 619/(235*4) ≈ 4.44 x 10-40. You would need on the order of 1040 queries to reach a 44.4% probability of a collision.
Eventually, unpadded responses will be deprecated and all responses will be padded, once clients have had time to update.
A quick test with curl demonstrates the feature:
curl -H Add-Padding:true https://api.pwnedpasswords.com/range/FFFFF
How the API is Structured
The Pwned Passwords API uses a k-anonymity model. Clients submit the first 5 hexadecimal characters of a SHA-1 password hash (20 bits). The API returns all remaining 35 characters (140 bits) for every breached password in that bucket, each suffix followed by a colon and the breach count.

Without padding, the response size varies with the number of suffixes in the queried bucket. That variance is the weakness—it can be fingerprinted over TLS. The new padding is inserted in the HTTP content itself, masking the bucket's true size. The difference between two unpadded buckets is easy to observe:
$ curl -so /dev/null https://api.pwnedpasswords.com/range/E0812 -w '%{size_download} bytes\n'
17022 bytes
$ curl -so /dev/null https://api.pwnedpasswords.com/range/834EF -w '%{size_download} bytes\n'
25118 bytes
Padded entries are distinguishable from real ones: they carry the :0 suffix. In the example below, the top three entries are legitimate; the last three are padding.
FF1A63ACC70BEA924C5DBABEE4B9B18C82D:10
FF8A0382AA9C8D9536EFBA77F261815334D:12
FFEE791CBAC0F6305CAF0CEE06BBE131160:2
2F811DCB8FF6098B838DDED4D478B0E4032:0
A1BABA501C55ACB6BDDC6D150CF585F20BE:0
9F31397459FF46B347A376F58506E420A58:0
Why Random Padding Matters
Cloudflare supports GZip and Brotli for compression. However, hashing's avalanche effect means that hash suffixes within a single bucket have no meaningful similarity, so compression gains are limited. If responses were padded with uniform data—such as 000...—the compression algorithm might expose the padding pattern post-compression. Even without compression, static padding would leave responses distinguishable.
To avoid that, padding is generated randomly, structured to look like genuine hash suffixes. The 0 usage count is the only giveaway, which is safe because clients must discard zero-count entries anyway.
Dynamic Padding with Workers and Lava Lamps
Cloudflare Workers provides a unique advantage in this design: Workers run before cache lookups. This allows padding to be generated fresh on every single request, rather than being stored in cached responses. The number of entries and overall response size changes from request to request, defeating attempts to analyze cached, static payload sizes.
For cryptographic randomness, the Workers runtime uses the Web Crypto API's crypto.getRandomValues(). That determines how much padding to add. The padding content itself, since it doesn't need to be cryptographically secure, is generated with Math.random() for performance.
Under the hood, Cloudflare's entropy comes from a wall of lava lamps in the San Francisco office, captured by photoreceptors feeding random seed data into servers—one of several entropy sources. That seed is used for cryptographically secure pseudorandom number generators (CSPRNGs). The Workers runtime uses the v8 JavaScript engine, which pulls randomness from /dev/urandom.
The output is evident in practice. Repeating identical queries yields between 800 and 1,000 lines each time:
$ for run in {1..10}; do curl -s -H Add-Padding:true https://api.pwnedpasswords.com/range/FFFFF | wc -l; done
831
956
870
980
932
868
856
961
912
827
And the download size varies per response:
$ for run in {1..10}; do curl -so /dev/null -H Add-Padding:true https://api.pwnedpasswords.com/range/FFFFF -w '%{size_download} bytes\n'; done
35572 bytes
37358 bytes
38194 bytes
33596 bytes
32304 bytes
37168 bytes
32532 bytes
37928 bytes
35154 bytes
33178 bytes
What's Next
The research community has produced complementary approaches, including Google and Stanford's work on password breach alerting presented at Usenix. Academic collaborators at Cornell have also published protocols for checking compromised credentials—frequency smoothing bucketization (FSB) and identifier-based bucketization (IDB)—which aim to further reduce information leakage in APIs.
Those protocols still need production-hardening before shipping. Until then, the padding added today represents an interim step toward stronger privacy guarantees for every Pwned Passwords query.



