Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Detect whether any password in your KeePassXC database was exposed in a data breach (using Troy Hunt's Pwned Passwords API)
#!/usr/bin/env bash
# Licensed by author Alex Birch under CC BY-SA 4.0
# detects whether any of your passwords have been exposed in a data breach, by
# submitting (prefixes of hashes of) all your passwords to Troy Hunt's
# Pwned Passwords API.
# usage:
# ./ keepassxc_exported_password_database.csv
# dependencies:
# brew install csvkit jq
# example output:
: "
LINE '1': SEVERITY='117316'; TITLE='Sample Entry'; USERNAME='User Name'; PASSWORD='Password'
LINE '2': SEVERITY='2333232'; TITLE='Sample Entry 2'; USERNAME='Michael321'; PASSWORD='12345'
LINE '3': safe!
LINE '4': skipped (empty)
# SEV is how many times that particular password has been exposed in a data breach.
# example contents of a compatible CSV:
: '
"Keepass2","Sample Entry","User Name","Password","","Notes"
"Keepass2","Sample Entry 2","Michael321","12345","",""
# if you are creating the CSV through some obscure method: be careful to adhere properly to the CSV
# format; passwords pose lots of text-escaping challenges.
# I use sandbox-exec in front of invocations of csvjson and jq
# since I trust them less than the utilities that ship with macOS.
# if you are on a different OS and less cautious, you could remove the sandbox-exec.
# here's a one-liner to lookup a pwned password:
# PASS='monkey' && SHA1="$(echo -n "$PASS" | shasum | cut -b 1-40)" && curl -s "$(echo -n "$SHA1" | cut -b 1-5)" | grep -i "$(echo -n $SHA1 | cut -b 6-)"
set -eo pipefail
function pwned() {
local LINENUM="$1"
local TITLE="$2"
local USERNAME="$3"
local PASSWORD="$4"
if [ -z "$PASSWORD" ] \
|| [ "$PASSWORD" == 'null' ]; then
echo "LINE '$LINENUM': skipped (empty)" >&2
local SHA1="$(echo -n "$PASSWORD" | shasum | cut -b 1-40)"
local PREFIX="$(echo "$SHA1" | cut -b 1-5)"
local SUFFIX="$(echo "$SHA1" | cut -b 6-)"
local RESULTS="$(curl -sf "$PREFIX")"
local MATCH="$(echo "$RESULTS" \
| grep -im 1 "$SUFFIX")"
if [ -z "$MATCH" ]; then
echo "LINE '$LINENUM': safe!" >&2
local MATCH="$(grep -im 1 "$SUFFIX" <<< "$RESULTS")"
local SEVERITY="$(echo "$MATCH" | awk -F: '{ print $2 }' | sed 's/[^0-9]*//g')"
while read -r line; do
TITLE="$(echo "$line" \
| sandbox-exec 2>/dev/null -n no-internet jq -r '.Title')"
USERNAME="$(echo "$line" \
| sandbox-exec 2>/dev/null -n no-internet jq -r '.Username')"
PASSWORD="$(echo "$line" \
| sandbox-exec 2>/dev/null -n no-internet jq -r '.Password')"
LINENUM="$(echo "$line" \
| sandbox-exec 2>/dev/null -n no-internet jq -r '.line_number')"
done < <(sandbox-exec 2>/dev/null -n no-internet csvcut -l -c Title,Username,Password "${1:-/dev/stdin}" \
| sandbox-exec 2>/dev/null -n no-internet csvjson \
| sandbox-exec 2>/dev/null -n no-internet jq -c '.[]' )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.