Last active
March 19, 2024 09:38
-
-
Save Birch-san/a027bf3ba6327371dffca8a491ddec5d to your computer and use it in GitHub Desktop.
Detect whether any password in your KeePassXC database was exposed in a data breach (using Troy Hunt's Pwned Passwords API)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Licensed by author Alex Birch under CC BY-SA 4.0 | |
# https://creativecommons.org/licenses/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. | |
# https://haveibeenpwned.com/Passwords | |
# usage: | |
# ./pwnedpass.sh 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: | |
: ' | |
"Group","Title","Username","Password","URL","Notes" | |
"Keepass2","Sample Entry","User Name","Password","http://keepass.info/","Notes" | |
"Keepass2","Sample Entry 2","Michael321","12345","http://keepass.info/help/kb/testform.html","" | |
' | |
# 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. | |
# https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf | |
# 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 "https://api.pwnedpasswords.com/range/$(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" | |
# echo "LINENUM='$LINENUM'; TITLE='$TITLE'; USERNAME='$USERNAME'; PASSWORD='$PASSWORD'" | |
if [ -z "$PASSWORD" ] \ | |
|| [ "$PASSWORD" == 'null' ]; then | |
echo "LINE '$LINENUM': skipped (empty)" >&2 | |
return | |
fi | |
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 "https://api.pwnedpasswords.com/range/$PREFIX")" | |
local MATCH="$(echo "$RESULTS" \ | |
| grep -im 1 "$SUFFIX")" | |
if [ -z "$MATCH" ]; then | |
echo "LINE '$LINENUM': safe!" >&2 | |
else | |
local MATCH="$(grep -im 1 "$SUFFIX" <<< "$RESULTS")" | |
local SEVERITY="$(echo "$MATCH" | awk -F: '{ print $2 }' | sed 's/[^0-9]*//g')" | |
echo "LINE '$LINENUM': SEVERITY='$SEVERITY'; TITLE='$TITLE'; USERNAME='$USERNAME'; PASSWORD='$PASSWORD'" | |
fi | |
} | |
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')" | |
pwned "$LINENUM" "$TITLE" "$USERNAME" "$PASSWORD" | |
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