# How to create an offline self-hosted haveibeenpwned API service

**URL:** <https://community.passbolt.com/t/how-to-create-an-offline-self-hosted-haveibeenpwned-api-service/5541>\
**Category:** Community Feedback\
**Created:** [July 3, 2022, 8:02pm UTC](https://community.passbolt.com/t/how-to-create-an-offline-self-hosted-haveibeenpwned-api-service/5541 "2022-07-03T20:02:16Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![AnatomicJC](https://yyz1.discourse-cdn.com/flex031/user_avatar/community.passbolt.com/anatomicjc/32/962_2.png) [@AnatomicJC](https://community.passbolt.com/u/AnatomicJC)\
**Post date:** [July 3, 2022, 8:02pm UTC](https://community.passbolt.com/t/how-to-create-an-offline-self-hosted-haveibeenpwned-api-service/5541/1 "2022-07-03T20:02:16Z")

</div>

## Intro

When you create your account for the first time, or when you want to change your passphrase, passbolt checks if this passphrase is not part of a breach by sending requests to [api.pwnedpasswords.com](http://api.pwnedpasswords.com).

If you run passbolt in a very restrictive environment with no access to [api.pwnedpasswords.com](http://api.pwnedpasswords.com) and still want to check if your passphrase is not part of a breach, or if you don’t want to sent requests to this API, you could be interested by this tutorial.

I will explain here how to self-host an offline haveibeenpwned API service on a Linux server in your local network. I used Debian 11 for my tests but you can use the Linux distro of your choice.

The offline API service is this project from Felix Engelmann: [https://github.com/felix-engelmann/haveibeenpwned-api](https://github.com/felix-engelmann/haveibeenpwned-api), please read the [README](https://github.com/felix-engelmann/haveibeenpwned-api/blob/master/README.md) to fully understand how it works. Use it at your own risk !

## Add a DNS entry

We cannot define a custom haveibeenpwned service in passbolt, that’s why you will have to make your local computer (requests to the API are made from the passbolt browser extension) believe [api.pwnedpasswords.com](http://api.pwnedpasswords.com) is running on your Linux server.

Create a new DNS entry where x.x.x.x is the IP address of the server who will run the self-hosted service.

x.x.x.x → [api.pwnedpasswords.com](http://api.pwnedpasswords.com)

## Create self-signed SSL certificates

[api.pwnedpasswords.com](http://api.pwnedpasswords.com) is served through https, so you have to create self-signed certificates and make your local computer trust them by importing the certificate in your computer: [https://www.google.com/search?q=how+to+import+self+signed+certificate](https://www.google.com/search?q=how+to+import+self+signed+certificate)

Create the SSL certificates:

```auto
openssl req -x509 \
    -newkey rsa:4096 \
    -days 3650 \
    -subj "/C=LU/ST=Your-country/L=Your-town/O=Your-org/OU=Your-team/CN=api.pwnedpasswords.com/" \
    -nodes \
    -addext "subjectAltName = DNS:api.pwnedpasswords.com" \
    -keyout key.pem \
    -out cert.pem

```

As you will trust these certificates on your network, you will be able to reach the service with SSL on your local network without issues.

## The setup

Here is what you need on your Linux server:

- nginx to act as a reverse proxy for the offline-pwnedpasswords API and Handle SSL certificates
- p7zip-full to be able to extract pwnedpasswords archive
- aria2 to speedup the download
- [docker.io](http://docker.io) to quickly be able to run docker images

```auto
sudo apt install nginx aria2 p7zip-full docker.io

```

Download the offline archive of pwned passwords, the aria2c command will use 16 connections to speedup the download, it will create a ~15Go file (you can get the list here: [https://haveibeenpwned.com/Passwords](https://haveibeenpwned.com/Passwords) ):

```auto
aria2c -x16 https://downloads.pwnedpasswords.com/passwords/pwned-passwords-sha1-ordered-by-hash-v8.7z

```

Extract it (will create a ~25Go file):

```auto
7z x pwned-passwords-sha1-ordered-by-hash-v8.7z

```

This list contains lines like this:

```auto
000000005AD76BD555C1D6D771DE417A4B87E4B4:10
00000000A8DAE4228F821FB418F59826079BF368:4
00000000DD7F2A1C68A35673713783CA390C9E93:873

```

To be able to use the offline API, you must have lines like this:

```auto
000000005AD76BD555C1D6D771DE417A4B87E4B4:0000000010
00000000A8DAE4228F821FB418F59826079BF368:0000000004
00000000DD7F2A1C68A35673713783CA390C9E93:0000000873

```

This python script will help you to get the correct line format:

```auto
wget https://raw.githubusercontent.com/felix-engelmann/haveibeenpwned-api/master/scripts/prepare.py

```

Execute the script to get the correct line format, will create a ~28Go file:

```auto
python3 scripts/prepare.py pwned-passwords-sha1-ordered-by-hash-v8.txt 10 pwned.txt

```

# docker image

From there, you have a `pwned.txt` file, it is your offline hashed passwords list. You can launch an offline pwned password API with docker. You can run the docker image from felixengelmann:

```auto
sudo docker run -d --restart always -v "$PWD/pwned.txt:/srv/pwned.txt" -p 127.0.0.1:5000:5000 felixengelmann/haveibeenpwned-api

```

> _This image seems to be rebuilt on a regular basis but is quite huge (~1Go 😮). If you care about security, you can use an image I built. My image is ~60Mo and is a distroless one._  
> _Distroless means you won’t find any shell utility such as bash, cd, or mkdir. This image can run only the pwned-api and nothing else._  
> _You can run it like this:_
> 
> _ **sudo docker run -d --restart always -v “$PWD/pwned.txt:/srv/pwned.txt” -p 127.0.0.1:5000:5000 --entrypoint /usr/local/bin/python anatomicjc/haveibeenpwned-api run.py** _
> 
> _You will the sources of this image [here](https://gitlab.com/AnatomicJC/haveibeenpwned-api/-/blob/main/Dockerfile) and automated gitlab pipelines [here](https://gitlab.com/AnatomicJC/haveibeenpwned-api/-/pipelines) (image is rebuilt once a week)_

This will expose your offline API on localhost on port 5000. To really simulate the pwnedpasswords API, you must serve it with https. You can create this nginx configuration file on `/etc/nginx/sites-enabled/offline-pwnedpasswords.conf`:

```auto
server {

  listen 443 ssl http2;

  server_name api.pwnedpasswords.com;

  ssl_certificate /etc/nginx/cert.pem;
  ssl_certificate_key /etc/nginx/key.pem;

  ssl_session_timeout 1d;
  ssl_session_cache shared:MozSSL:10m; # about 40000 sessions

  ssl_session_tickets off;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_prefer_server_ciphers off;
  
  location / {
    proxy_pass http://127.0.0.1:5000;
  }

}

```

This will forward [api.haveibeenpwned.com](http://api.haveibeenpwned.com) requests on https to the docker container. Verify the path to your SSL certificates, and verify your nginx configuration is valid:

```auto
sudo nginx -t

```

If it is ok, you can restart nginx:

```auto
sudo systemctl restart nginx.service

```

passbolt should now be able to check is your passphrase is not part of a breach with your local haveibeenpwned self-hosted API.

Don’t hesitate to ask if some parts of this tutorial are unclear.

Please enjoy,

---

<div class="post-metadata">

**Author:** ![arwho](https://yyz1.discourse-cdn.com/flex031/user_avatar/community.passbolt.com/arwho/32/4418_2.png) [@arwho](https://community.passbolt.com/u/arwho)\
**Post date:** [September 12, 2024, 8:39am UTC](https://community.passbolt.com/t/how-to-create-an-offline-self-hosted-haveibeenpwned-api-service/5541/2 "2024-09-12T08:39:15Z")

</div>

Does this also work with Passbolt CE?

---

<div class="post-metadata">

**Author:** ![AnatomicJC](https://yyz1.discourse-cdn.com/flex031/user_avatar/community.passbolt.com/anatomicjc/32/962_2.png) [@AnatomicJC](https://community.passbolt.com/u/AnatomicJC)\
**Post date:** [September 13, 2024, 10:35am UTC](https://community.passbolt.com/t/how-to-create-an-offline-self-hosted-haveibeenpwned-api-service/5541/3 "2024-09-13T10:35:53Z")

</div>

Yes, I made this POC with passbolt CE 🙂

---

<div class="post-metadata">

**Author:** ![oschonrock](https://yyz1.discourse-cdn.com/flex031/user_avatar/community.passbolt.com/oschonrock/32/4636_2.png) [@oschonrock](https://community.passbolt.com/u/oschonrock)\
**Post date:** [November 26, 2024, 9:29am UTC](https://community.passbolt.com/t/how-to-create-an-offline-self-hosted-haveibeenpwned-api-service/5541/4 "2024-11-26T09:29:27Z")

</div>

I just published a different project which includes an efficient downloader and server.

very quick to set up… ~ 7minutes 😉

[https://github.com/oschonrock/hibp](https://github.com/oschonrock/hibp)

Feedback wanted!

---

<div class="post-metadata">

**Author:** ![AnatomicJC](https://yyz1.discourse-cdn.com/flex031/user_avatar/community.passbolt.com/anatomicjc/32/962_2.png) [@AnatomicJC](https://community.passbolt.com/u/AnatomicJC)\
**Post date:** [November 26, 2024, 2:29pm UTC](https://community.passbolt.com/t/how-to-create-an-offline-self-hosted-haveibeenpwned-api-service/5541/5 "2024-11-26T14:29:35Z")

</div>

Wow, it looks like very interesting 🙂

Thanks for this !
