Tools for self-hosting the Have I Been Pwned Password database. Xodus importer and query library
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ralph Schaer 0cce24f94b upgrade
2026-10-09 09:40:49 +02:00
.github/workflows Improve importer safety and query reliability 2026-09-06 09:43:11 +02:00
.mvn/wrapper upgrade 2026-05-31 15:50:32 +02:00
config Upgrade 2023-03-11 21:05:25 +01:00
demo upgrade 2026-10-09 09:40:49 +02:00
importer upgrade 2026-10-09 09:40:49 +02:00
query upgrade 2026-10-09 09:40:49 +02:00
LICENSE Initial commit 2019-06-14 21:47:30 +02:00
mvnw Upgrade 2025-07-26 12:56:26 +02:00
mvnw.cmd Upgrade 2025-07-26 12:56:26 +02:00
pom.xml Update dependencies and improve query functionality 2026-06-20 06:00:07 +02:00
README.md Improve importer safety and query reliability 2026-09-06 09:43:11 +02:00

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:

Build

./mvnw -B clean verify

On Windows:

.\mvnw.cmd -B clean verify

Import

  1. 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 sorted 35_CHARACTER_SUFFIX:COUNT lines.

    The official downloader creates a single file by default, so disable its single-file mode:

    haveibeenpwned-downloader hashes -s false
    
  2. Download importer.

  3. 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.