b0df33bf38
the > 10 GB pawned password hash list, which requires nearly 20 GB after decompression. The API does not transfer the queried password or its full SHA1 hash to the server, but only the first 5 characters of the hash. This allows to retrieve the full password hashes that match that prefix (typically in the order of 500) and then to check whether the password to test matches any of the hashes returned. Approved by: antoine (implicit)
131 lines
2.8 KiB
Bash
131 lines
2.8 KiB
Bash
#!/bin/sh
|
|
#
|
|
# Copyright (c) 2017 by Stefan Esser <se@freebsd.org>
|
|
# All rights reserved.
|
|
#
|
|
# Distributed under the BSD 2-clause Simplified License.
|
|
#
|
|
|
|
CFGFILE="%%PREFIX%%/etc/pwned-check.conf"
|
|
|
|
[ -r "$CFGFILE" ] && . $CFGFILE
|
|
: ${DBDIR:=/var/db/pwned-check}
|
|
: ${URLBASE:=https://downloads.pwnedpasswords.com/passwords}
|
|
|
|
# Helper functions
|
|
progname ()
|
|
{
|
|
basename "$0"
|
|
}
|
|
|
|
errexit ()
|
|
{
|
|
echo $(progname)": $@"
|
|
exit 1
|
|
}
|
|
|
|
usage ()
|
|
{
|
|
echo "usage: "$(progname)" [-u]"
|
|
exit 2
|
|
}
|
|
|
|
# Fetch files with pwned password hashes
|
|
fetchpwfiles ()
|
|
{
|
|
umask 022
|
|
mkdir -p $DBDIR || errexit "No write permission on data directory."
|
|
local f s_txt s_txt_7z hash
|
|
while read f s_txt s_txt_7z hash
|
|
do
|
|
local f7z="$f.7z"
|
|
echo "Checking '$DBDIR/$f' ..."
|
|
local s_txt_is=$(stat -f %z $f 2>/dev/null)
|
|
if [ "$s_txt_is" != "$s_txt" ]; then
|
|
local s_txt_7z_is=$(stat -f %z $f7z 2>/dev/null)
|
|
if [ "$s_txt_7z_is" != "$s_txt_7z" ]; then
|
|
echo "Fetching '$DBDIR/$f7z' ..."
|
|
fetch -S $s_txt_7z "$URLBASE/$f7z" || errexit "Could not fetch '$URLBASE/$f7z'."
|
|
fi
|
|
echo "Checking '$DBDIR/$f7z' ..."
|
|
local hash_is=$(sha1 -q "$f7z")
|
|
if [ "$hash_is" != "$hash" ]; then
|
|
rm -f "$f7z"
|
|
errexit "File '$f7z' fails SHA1 check: '$hash_is' should be '$hash'."
|
|
fi
|
|
echo "Extracting '$DBDIR/$f' ..."
|
|
tar xOf "$f7z" | cut -d ":" -f 1 > "$f" || errexit "Decompression of file '$f7z' failed."
|
|
local s_txt_is=$(stat -f %z "$f")
|
|
if [ "$s_txt_is" != "$s_txt" ]; then
|
|
rm -f "$f"
|
|
errexit "File '$f' has size $s_txt_is after decompression, should be $s_txt."
|
|
fi
|
|
fi
|
|
rm -f "$f7z"
|
|
done <<EOF
|
|
pwned-passwords-ordered-2.0.txt 20567110522 9647404191 87437926c6293d034a259a2b86a2d077e7fd5a63
|
|
EOF
|
|
echo "All data files have been successfully downloaded and extracted."
|
|
# delete old data files (their content is included in the new datafiles)
|
|
while read f
|
|
do
|
|
rm -f $f $f.7z
|
|
done <<EOF
|
|
pwned-passwords-1.0.txt
|
|
pwned-passwords-update-1.txt
|
|
pwned-passwords-update-2.txt
|
|
EOF
|
|
}
|
|
|
|
# Password lookup
|
|
exitcode=0
|
|
|
|
lookup ()
|
|
{
|
|
local hash=$(echo "$1" | tr 'a-z' 'A-Z')
|
|
if [ "$USEFILES" = yes ]; then
|
|
look "$hash" pwned-passwords*.txt > /dev/null
|
|
else
|
|
expected=${hash#?????}
|
|
prefix=${hash%$expected}
|
|
fetch -q -o - https://api.pwnedpasswords.com/range/$prefix 2>/dev/null | grep -i "^$expected:" >/dev/null
|
|
fi
|
|
}
|
|
|
|
checkpw ()
|
|
{
|
|
local pwd="$1"
|
|
local hash=$(echo -n "$pwd" | sha1)
|
|
if lookup "$hash"; then
|
|
echo "$pwd"
|
|
exitcode=1
|
|
elif expr "$pwd" : '[A-Fa-f0-9]\{40\}$' > /dev/null; then
|
|
if lookup "$pwd"; then
|
|
echo "$pwd"
|
|
exitcode=1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Main program
|
|
export LC_COLLATE=C
|
|
if cd "$DBDIR" && ls pwned-passwords*.txt; then
|
|
USEFILES=yes
|
|
fi >/dev/null 2>&1
|
|
|
|
if [ "$#" -gt 0 ]; then
|
|
if [ "$1" = "-u" ]; then
|
|
fetchpwfiles
|
|
exit 0
|
|
else
|
|
echo "usage: "$(progname)" [-u]"
|
|
exit 2
|
|
fi
|
|
fi
|
|
|
|
while read pwd
|
|
do
|
|
checkpw "$pwd"
|
|
done
|
|
|
|
exit $exitcode
|