Password Breach Checker — C++ source
Check if a password has appeared in known data breaches using k-anonymity. Only the first 5 characters of the SHA-1 hash are sent - your full password never leaves your browser.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Password breach lookup via Have I Been Pwned's Pwned Passwords API, using
// k-anonymity: only the first 5 characters of the SHA-1 hash ever leave the
// process.
//
// Language: C++17 (standard library + OpenSSL for SHA-1)
// Ported from src/lib/breach-checker.ts (the canonical TypeScript
// implementation). display source — part of CosmoDev's polyglot tool pages.
//
// API: https://api.pwnedpasswords.com/range/{PREFIX} — free, no key. Returns
// one "SUFFIX:COUNT" line per hash sharing the prefix (~800 candidates). The
// suffix match happens locally. The network call is injectable (FetchFn) so
// the pure logic stays unit-testable without a socket, mirroring the TS
// signature's `fetchFn` parameter.
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <stdexcept>
#include <string>
#include <vector>
#include <openssl/evp.h>
namespace breach {
constexpr const char* HIBP_RANGE_URL = "https://api.pwnedpasswords.com/range/";
/** Minimal response shape the injected fetch must provide (the TS `Response`). */
struct FetchResponse {
bool ok = false; ///< HTTP 2xx
int status = 0; ///< the HTTP status code
std::string body; ///< the response text
};
/// Injectable transport: URL in, response out. Throw to signal a network error.
using FetchFn = std::function<FetchResponse(const std::string& url)>;
struct BreachResult {
bool breached = false; ///< true when the exact hash suffix appeared in the candidate list
long long count = 0; ///< appearances in breaches; 0 = never seen, -1 = lookup failed
std::string hashPrefix; ///< first 5 chars of the uppercase SHA-1 hex — the only part sent
std::string hashSuffix; ///< remaining 35 chars, matched locally against the response
std::string error; ///< set when the lookup failed (network error or non-200)
long long candidates = 0;///< candidate suffixes the API returned (all checked locally)
};
static std::string toUpper(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::toupper(c)); });
return s;
}
static std::string trim(const std::string& s) {
size_t b = s.find_first_not_of(" \t\r\n");
if (b == std::string::npos) return "";
size_t e = s.find_last_not_of(" \t\r\n");
return s.substr(b, e - b + 1);
}
static std::vector<std::string> splitLines(const std::string& body) {
std::vector<std::string> lines;
std::string cur;
for (char c : body) { // tolerates LF and CRLF (the TS split(/\r?\n/))
if (c == '\n') {
if (!cur.empty() && cur.back() == '\r') cur.pop_back();
lines.push_back(cur);
cur.clear();
} else {
cur += c;
}
}
lines.push_back(cur);
return lines;
}
/** SHA-1 of a UTF-8 string as uppercase hex (the format HIBP expects).
* std::string already carries UTF-8 bytes — no TextEncoder step needed. */
std::string sha1Hex(const std::string& input) {
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int len = 0;
if (EVP_Digest(input.data(), input.size(), digest, &len, EVP_sha1(), nullptr) != 1) {
throw std::runtime_error("SHA-1 is not available in this OpenSSL build");
}
static const char* HEX = "0123456789ABCDEF";
std::string out;
out.reserve(len * 2);
for (unsigned int i = 0; i < len; i++) {
out += HEX[digest[i] >> 4];
out += HEX[digest[i] & 0x0f];
}
return out;
}
struct HashSplit {
std::string prefix; ///< 5 chars
std::string suffix; ///< 35 chars
};
/** Split a 40-char uppercase hash into the k-anonymity prefix and suffix. */
HashSplit splitHash(const std::string& hash) {
const std::string h = toUpper(hash);
return HashSplit{h.substr(0, 5), h.size() > 5 ? h.substr(5) : std::string()};
}
/**
* Search an HIBP range response for a hash suffix and return its breach count.
* Never throws; returns 0 when the suffix is not present. Tolerates LF and
* CRLF line endings, blank lines, and leading/trailing whitespace per line.
*/
long long parseRangeBody(const std::string& body, const std::string& suffix) {
if (suffix.empty()) return 0;
for (const std::string& line : splitLines(body)) {
const size_t idx = line.find(':');
if (idx == std::string::npos) continue;
if (trim(line.substr(0, idx)) == suffix) {
const std::string countStr = trim(line.substr(idx + 1));
long long count = 0;
const char* begin = countStr.c_str();
char* end = nullptr;
count = std::strtoll(begin, &end, 10);
if (end == begin) return 0; // NaN in the TS Number.parseInt sense
return count < 0 ? 0 : count;
}
}
return 0;
}
/** Count the "SUFFIX:COUNT" candidate lines in a range response. */
long long countCandidates(const std::string& body) {
long long n = 0;
for (const std::string& line : splitLines(body)) {
const size_t idx = line.find(':');
if (idx != std::string::npos && !trim(line.substr(0, idx)).empty()) n++;
}
return n;
}
/**
* Check a password against the Pwned Passwords corpus using k-anonymity.
* Only `hashPrefix` is sent over the network; the suffix match is local.
* Never throws — a failed lookup returns { breached=false, count=-1, error }.
*/
BreachResult checkBreach(const std::string& password, const FetchFn& fetchFn) {
const HashSplit parts = splitHash(sha1Hex(password));
BreachResult result;
result.hashPrefix = parts.prefix;
result.hashSuffix = parts.suffix;
result.count = -1;
FetchResponse response;
try {
response = fetchFn(std::string(HIBP_RANGE_URL) + parts.prefix);
} catch (const std::exception& e) {
result.error = e.what();
return result;
} catch (...) {
result.error = "Network request failed";
return result;
}
if (!response.ok) {
result.error = "The breach database returned HTTP " + std::to_string(response.status);
return result;
}
const long long count = parseRangeBody(response.body, parts.suffix);
result.breached = count > 0;
result.count = count;
result.candidates = countCandidates(response.body);
return result;
}
} // namespace breach
Also available in 9 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →