- Java 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github/workflows | ||
| .mvn/wrapper | ||
| config | ||
| demo | ||
| importer | ||
| query | ||
| LICENSE | ||
| mvnw | ||
| mvnw.cmd | ||
| pom.xml | ||
| README.md | ||
Self-host Have I Been Pwned Passwords
Tool for importing the Have I Been Pwned Passwords list into a local JetBrains Xodus database, plus a Java library and Spring Boot demo for querying that database.
Prerequisites
- Java 25 or newer. A JRE is sufficient for running the released importer; a JDK is required for building this repository.
- Sufficient drive storage. Expect about 57 GB during import and about 24 GB for normal operation.
Java downloads:
- https://adoptium.net/
- https://jdk.java.net/25/
- https://aws.amazon.com/corretto/
- https://www.azul.com/downloads/?package=jdk
Build
./mvnw -B clean verify
On Windows:
.\mvnw.cmd -B clean verify
Import
-
Download the SHA-1 Pwned Passwords. Either use the official downloader or hibp-passwords-downloader. The importer expects one file per 5-character hash prefix (for example,
7C4A8.txt) with sorted35_CHARACTER_SUFFIX:COUNTlines.The official downloader creates a single file by default, so disable its single-file mode:
haveibeenpwned-downloader hashes -s false -
Download importer.
-
Run the import tool and point it to the directory that contains the downloaded hash files. The database directory must be empty and must not be inside the hashes directory.
java -jar hibp-passwords-importer.jar import <hashesdir> <database_directory_name>
Query
Library
Add the library to your project.
<!-- Maven -->
<dependency>
<groupId>ch.rasc.hibppasswords</groupId>
<artifactId>query</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
// Gradle
dependencies {
implementation 'ch.rasc.hibppasswords:query:1.0.1-SNAPSHOT'
}
An application can query the database with a plain text password or SHA-1 hash. Both methods return how many times a password appears in the data set, or null if the password is not found.
haveIBeenPwnedRange implements the k-anonymity range-query model and searches by the first 5 characters of a SHA-1 hash:
https://haveibeenpwned.com/API/v3#PwnedPasswords
SHA-1 inputs are case-insensitive, but must contain valid hexadecimal characters and have the expected length: 40 characters for a full SHA-1 hash, 5 characters for a range query prefix.
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import ch.rasc.hibppasswords.query.HibpPasswordsQuery;
import ch.rasc.hibppasswords.query.RangeQueryResult;
Path db = Paths.get("..."); // Path to local database
Integer count = HibpPasswordsQuery.haveIBeenPwnedPlain(db, "123456");
count = HibpPasswordsQuery.haveIBeenPwnedSha1(db, "FFFFFFBFAD0B653BDAC698485C6D105F3C3682B2");
List<RangeQueryResult> result = HibpPasswordsQuery.haveIBeenPwnedRange(db, "FFFFF");
These three methods open and close the database for each call. To speed up queries, instantiate the Xodus environment once and pass it as the first argument.
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import ch.rasc.hibppasswords.query.HibpPasswordsQuery;
import ch.rasc.hibppasswords.query.RangeQueryResult;
import jetbrains.exodus.env.Environment;
Path db = Paths.get("..."); // Path to local database
try (Environment environment = HibpPasswordsQuery.openDatabase(db)) {
Integer count = HibpPasswordsQuery.haveIBeenPwnedPlain(environment, "123456");
count = HibpPasswordsQuery.haveIBeenPwnedSha1(environment, "FFFFFFBFAD0B653BDAC698485C6D105F3C3682B2");
List<RangeQueryResult> result = HibpPasswordsQuery.haveIBeenPwnedRange(environment, "FFFFF");
}
See the Spring Boot example.
Command Line
The local database can also be queried with the import tool.
java -jar hibp-passwords-importer.jar query-plain 123456 <path_to_database>
java -jar hibp-passwords-importer.jar query-sha1 FFFFFFFEE791CBAC0F6305CAF0CEE06BBE131160 <path_to_database>
java -jar hibp-passwords-importer.jar query-range FFFFF <path_to_database>
The plain-text and full-hash commands print the breach count or not found. The range command prints one HASH_SUFFIX:COUNT result per line.
HTTP Demo
The repository contains a Spring Boot demo with HTTP endpoints.
git clone https://github.com/ralscha/selfhost-hibp-passwords.git
cd selfhost-hibp-passwords/demo
JAVA_HOME=<path_to_jdk> ../mvnw spring-boot:run -Dspring-boot.run.arguments=--app.hibp-database-dir=<path_to_database>
# in another shell
curl http://localhost:8080/range/7C4A8
001CE884342580D934A29D94060B3796C30:2
00AD0FC3FA522D0474F9A28FD478C06669D:1
...
curl http://localhost:8080/plain/mypassword
38621
curl http://localhost:8080/sha1/7C4A8D7F20D435D1F9F7FFA96C28E216E98163
13
The demo binds to 127.0.0.1 by default and has no authentication. Set server.address explicitly only if you intend to expose it on another interface and protect it appropriately.