{"id":37066067,"url":"https://github.com/tylerchr/pwnedpass","last_synced_at":"2026-01-14T07:45:00.520Z","repository":{"id":57516251,"uuid":"124642425","full_name":"tylerchr/pwnedpass","owner":"tylerchr","description":"Package pwnedpass is a Go package for querying a local instance of Troy Hunt's Pwned Passwords database.","archived":false,"fork":false,"pushed_at":"2023-11-13T07:05:22.000Z","size":3153,"stargazers_count":15,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-02T10:57:58.247Z","etag":null,"topics":["golang","haveibeenpwned","hibp","offline","pwnedpasswords","security","selfhosted"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tylerchr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-10T09:48:16.000Z","updated_at":"2024-07-09T09:50:37.000Z","dependencies_parsed_at":"2024-06-20T06:07:36.936Z","dependency_job_id":null,"html_url":"https://github.com/tylerchr/pwnedpass","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/tylerchr/pwnedpass","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerchr%2Fpwnedpass","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerchr%2Fpwnedpass/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerchr%2Fpwnedpass/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerchr%2Fpwnedpass/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tylerchr","download_url":"https://codeload.github.com/tylerchr/pwnedpass/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerchr%2Fpwnedpass/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413486,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","haveibeenpwned","hibp","offline","pwnedpasswords","security","selfhosted"],"created_at":"2026-01-14T07:44:59.777Z","updated_at":"2026-01-14T07:45:00.515Z","avatar_url":"https://github.com/tylerchr.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `pwnedpass` [![GoDoc](https://godoc.org/github.com/tylerchr/pwnedpass?status.svg)](https://godoc.org/github.com/tylerchr/pwnedpass)\n\nPackage `pwnedpass` is a Go package for querying a local instance of Troy Hunt's Pwned Passwords database. It also implements an http.Handler that reproduces the online Pwned Passwords HTTP API.\n\nFor a complete HTTP server built on top of this package, see sub-package [pwnd](https://github.com/tylerchr/pwnedpass/tree/master/cmd/pwnd).\n\n## Usage\n\nThe `pwnedpass` package exports two primary functions, `Pwned` and `Scan`, which loosely mirror the official [password](https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByPassword) and [range](https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange) APIs respectively.\n\nQuerying a local Pwned Passwords database requires a local copy of the Pwned Passwords database; see \"Database File\" below for details on how to generate this.\n\n```go\nod, _ := pwnedpass.NewOfflineDatabase(\"pwned-passwords-v8.bin\") // see \"Database File\" below\n```\n\n### Pwned Password\n\nThe `Pwned` method indicates whether the given password appears in the dataset by returning its number of occurrences. This number will be zero for unpwned passwords.\n\n```go\n// search by password\nfreq, _ := od.Pwned(sha1.Sum([]byte(\"P@ssword\")))\nfmt.Println(freq)\n\n// 7491\n\n// Compare with https://api.pwnedpasswords.com/pwnedpassword/P@ssword\n```\n\n### Range Scan\n\nThe `Scan` method iterates efficiently through the range of hashes included between `startPrefix` and `endPrefix` inclusive. In other words, iteration begins with the first hash to begin with `startPrefix` and continues through and including the last hash that begins with `endPrefix`. Observe that if the same value is provided for both the `startPrefix` and `endPrefix` arguments, then `Scan` iterates only through hashes with exactly that prefix.\n\nNote that these prefixes are 3-byte prefixes (6 hex digits), as opposed to the 2.5-byte (5 hex digit) prefixes accepted by the online Range API. Users wishing to emulate the 5-digit semantics should append a `0` to the `startPrefix` and a `F` to the `endPrefix`, as in this example.\n\n```go\n// search by range\nvar (\n\tstartPrefix = [3]byte{0x21, 0xBD, 0x10}\n\tendPrefix   = [3]byte{0x21, 0xBD, 0x1F}\n)\n\nvar hash [20]byte\nod.Scan(startPrefix, endPrefix, hash[:], func(freq uint16) bool {\n\tfmt.Printf(\"%x:%d\\n\", hash, freq)\n})\n\n// 21BD10018A45C4D1DEF81644B54AB7F969B88D65:1\n// 21BD100D4F6E8FA6EECAD2A3AA415EEC418D38EC:2\n// 21BD1011053FD0102E94D6AE2F8B83D76FAF94F6:1\n// ...\n// 21BD1FE867A959E87530DED79F9709D4E7BDCD5D:2\n// 21BD1FE92D1CF40DCB5C9BAE484B1CABCC9112E1:6\n// 21BD1FF185A609DEA5042A77EF4238E4BD7C5E72:3\n\n// Compare with https://api.pwnedpasswords.com/range/21BD1\n```\n\n## Database File\n\nUsing the `pwnedpass` package depends on having a Pwned Passwords database file. To minimize storage and memory requirements, this package uses a binary encoded variation on the stock [Pwned Passwords database file](https://haveibeenpwned.com/Passwords).\n\nThe file format is extremely simple and is documented below. Additionally, this repository contains a utility (see sub-command [pwngen](https://github.com/tylerchr/pwnedpass/tree/master/cmd/pwngen)) that produces the binary encoding from the [stock ASCII version](https://haveibeenpwned.com/Passwords).\n\n```bash\n$ go install github.com/tylerchr/pwnedpass/cmd/pwngen\n$ 7z e -so pwned-passwords-sha1-ordered-by-hash-v8.7z pwned-passwords-sha1-ordered-by-hash-v8.txt | pwngen pwned-passwords-v8.bin\nReserving space for the index segment...\nWriting data segment...\nWriting index segment...\nOK\n```\n\nThis process takes approximately 19m50s on my 2021 MacBook Pro (or 1m13s if the hashes are already decompressed) and results in a 15.12GiB `pwned-passwords-v8.bin` file. Note that you must use the _ordered by hash_ database file for correct results here.\n\n| File                        | SHA-1 of stock 7-Zip file                | SHA-1 of binary file                     |\n| --------------------------- | ---------------------------------------- | ---------------------------------------- |\n| Version 2 (ordered by hash) | 87437926c6293d034a259a2b86a2d077e7fd5a63 | 9ea32216da1ab11ac2c9a29e19c33f1c2e6ecd1a |\n| Version 3 (ordered by hash) | 10c001292d52a04dc0fb58a7fb7dd0b6ea7f7212 | 2b2117287cfed6771f1e217cc57b05d8bd0196d4 |\n| Version 4 (ordered by hash) | d81c649cda9cddb398f2b93c629718e14b7f2686 | 70758c9557a138664cc4a99759f219a2bc49da49 |\n| Version 5 (ordered by hash) | 4f505d687a7dd3d67980983787adb33cb768c7b2 | 1282ad6cff4c03613d5c99d47a11dda354898494 |\n| Version 6 (ordered by hash) | f0447a064aee7e3b658959fab54dba79b926f429 | a2eefe0f53fe1ec273bce1eb1e24a17adafc6ef0 |\n| Version 7 (ordered by hash) | dba43bd82997d5cef156219cb0d295e1ab948727 | 6454ac4b9807ababbc6d0295aad2162f8873d628 |\n| Version 8 (ordered by hash) | 3499a3f82bb94f62cbd9bc782d6d20324e7cde8e | 08422b1ae8536047affebe8fb61d8a0448d18a73 |\n\n### File Format\n\nThe binary file format consists of two concatenated segments: an index segment and a data segment. The data segment contains every hash in the dataset paired with a 16-bit expression of its appearance frequency, while the index segment contains every 3-byte prefix paired with a pointer into the data segment of the first hash with that prefix.\n\nHashes exist in the dataset for all 16,777,216 3-byte prefixes (`256^3`), and since byte offsets are expressed as big-endian uint64 values the total size of the index segment is always exactly `16,777,216 * 8 bytes = 128 MB`.\n\n```\n+-----------------+-----------------+-----------------+-- ~ --+-----------------+\n| ptr to 0x000000 | ptr to 0x000001 | ptr to 0x000002 |  ...  | ptr to 0xFFFFFF |\n+-----------------+-----------------+-----------------+-- ~ --+-----------------+\n      8 bytes           8 bytes           8 bytes                   8 bytes\n```\n\nThe data segment contains each hash in sorted order, paired with a 16-bit big-endian representation of its frequency. To save space, the first 3 bytes of each hash are omitted as they can be recovered from the index as discussed above. Combined with the frequency value, this means that each hash occupies `17 + 2 = 19 bytes`.\n\n```\n+--------------------------------------+---+--------------------------------------+---+-- ~ --\n| 0x005AD76BD555C1D6D771DE417A4B87E4B4 | 3 | 0x00A8DAE4228F821FB418F59826079BF368 | 2 |  ...  \n+--------------------------------------+---+--------------------------------------+---+-- ~ --\n                 17 bytes                ^                  17 bytes                ^\n                                         |                                          |\n                                      2 bytes                                    2 bytes\n```\n\nThis sequence repeats for all hashes in the dataset, which in the Version 8 export is 847,223,402. The observant reader might notice at this point that all these numbers line up:\n\n```\n$ ls -la pwned-passwords-v*.bin\n-rw-r--r--   1 tylerchr  staff  16231462366 Dec 20 01:48 pwned-passwords-v8.bin\n\n# (256^3 * 8) + (847,223,402 * (17 + 2)) = 16231462366 bytes\n# (256^3 * 8) + (847,223,402 * 19)       = 16231462366 bytes\n# 134,217,728 + 16,097,244,638           = 16231462366 bytes\n```\n\nFor more details on the design choices of this file format, see [the associated blog post](https://github.com/tylerchr/pwnedpass/blob/master/DETAILS.md).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerchr%2Fpwnedpass","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftylerchr%2Fpwnedpass","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerchr%2Fpwnedpass/lists"}