1. Overview
The HibpPasswordProvider module integrates BastionGuard’s Identity Leak subsystem with the Have I Been Pwned – Pwned Passwords service. It checks whether a user-supplied password appears in known breach corpora while preserving privacy through the k-anonymity model.
The provider:
- Computes the SHA-1 hash of the password (uppercase hex)
- Splits the hash into a 5-character prefix and a 35-character suffix
- Queries the HIBP
/range/{prefix}API - Searches returned suffixes for an exact match
- Returns whether the password is compromised and the breach occurrence count
This approach avoids sending the raw password to any external service and limits disclosure to a short hash prefix.
2. Dependencies
- OpenSSL – SHA-1 hashing (
SHA1()) - HttpClient – HTTPS request execution (libcurl-based)
- C++ standard library – string formatting, parsing, and containers
3. Data Model
The provider returns a PasswordLeakResult structure. The implementation assumes the following fields:
compromised– boolean indicating whether the password appears in breach datasetscount– occurrence count as reported by HIBP (when compromised)
Default/unknown states are represented by leaving compromised as false and count as 0.
4. Hashing and k-Anonymity Workflow
4.1 SHA-1 Hash Computation (Uppercase HEX)
The password is hashed using SHA-1 and encoded as a 40-character uppercase hexadecimal string:
- Digest length: 20 bytes
- Hex length: 40 characters
- Uppercase enforced to match HIBP response formatting
Hashing is performed by:
SHA1(reinterpret_cast<const unsigned char*>(input.data()),
input.size(),
hash);
4.2 Prefix/Suffix Split
The k-anonymity design uses:
- Prefix: first 5 characters of the SHA-1 hash
- Suffix: remaining 35 characters
prefix = hash.substr(0, 5)
suffix = hash.substr(5)
Only the prefix is transmitted to HIBP.
5. Network Request
5.1 Endpoint
The provider queries the HIBP Pwned Passwords range API:
https://api.pwnedpasswords.com/range/<PREFIX>
The response is plaintext and contains multiple candidate suffixes with associated counts.
5.2 Headers
The provider sets explicit headers to ensure compatibility and to align with recommended client behavior:
- User-Agent:
BastionGuard/IdentityLeak - Accept:
text/plain - Add-Padding:
true
Add-Padding is used to request padded responses, which reduces the usefulness of size-based side-channel inference. This does not affect correctness.
5.3 Timeouts and Failure Behavior
The request is executed with a timeout of 15 seconds. If the HTTP status code is not 200, the provider returns the default result (interpreted as “unknown / not confirmed compromised”).
This includes network errors and rate-limiting scenarios. The module intentionally does not throw, ensuring that provider failure does not destabilize upstream UI workflows.
6. Response Parsing
6.1 Line Format
The HIBP API response is parsed line-by-line. Each line is expected to follow:
SUFFIX:COUNT
Where:
SUFFIXis a 35-character uppercase hex stringCOUNTis a decimal integer representing occurrence count
6.2 CRLF Handling
To accommodate CRLF line endings, each line is trimmed of trailing \r and \n characters via rtrim_crlf().
6.3 Suffix Match
For each parsed line:
- The delimiter
:is located - The left side is treated as the suffix candidate
- Its length is validated against the expected 35-character suffix length
- An exact string comparison is performed against the locally computed suffix
HIBP returns suffixes in uppercase, matching the local hashing format.
6.4 Count Parsing
When the suffix matches, the module parses COUNT using a digit-only scan:
- Iterates over characters, accepting only
0..9 - Accumulates into an
unsigned long long - Stops parsing on first non-digit character
On a successful match:
result.compromised = trueresult.countis set to the parsed count- Parsing stops early (break)
7. Return Semantics
The function returns a PasswordLeakResult with the following semantics:
- If password is empty or hashing fails → default result (not compromised)
- If network request fails or returns non-200 → default result (unknown; treated as not compromised)
- If suffix match is found →
compromised=trueandcount>0 - If no suffix match is found → default result (not compromised)
Upstream components should treat “non-200” outcomes as “no determination,” not as a strong “clean” verdict, if a strict security posture is required.
8. Runtime and Security Considerations
- k-anonymity privacy model: only the first 5 characters of the SHA-1 hash are transmitted; the full hash and password remain local
- No plaintext password exposure: the password is never logged or sent over the network
- TLS verification: requests rely on
HttpClientwith TLS verification enabled by default - Side-channel mitigation:
Add-Padding: truereduces response-size inference risk - Rate limiting behavior: non-200 responses do not raise exceptions and return an “unknown” state; UI should communicate this nuance if necessary
- Hash algorithm choice: SHA-1 is required by the HIBP Pwned Passwords API design (hashing for lookup, not for cryptographic signing)
- Performance: parsing is linear in response size; the range API responses are bounded and typically suitable for real-time UI checks