-
-
Save IcyApril/56c3fdacb3a640f37c245e5813b98b99 to your computer and use it in GitHub Desktop.
#!/bin/bash | |
echo -n Password: | |
read -s password | |
echo | |
hash="$(echo -n $password | openssl sha1)" | |
upperCase="$(echo $hash | tr '[a-z]' '[A-Z]')" | |
prefix="${upperCase:0:5}" | |
response=$(curl -s https://api.pwnedpasswords.com/range/$prefix) | |
while read -r line; do | |
lineOriginal="$prefix$line" | |
if [ "${lineOriginal:0:40}" == "$upperCase" ]; then | |
echo "Password breached." | |
exit 1 | |
fi | |
done <<< "$response" | |
echo "Password not found in breached database." | |
exit 0 |
Quick & dirty caching:
#!/bin/bash
echo -n Password:
read -s password
echo
hash="$(echo -n "$password" | sha1sum | awk '{print $1}')"
upperCase="$(echo $hash | tr '[a-z]' '[A-Z]')"
prefix="${upperCase:0:5}"
cache=/tmp/passcache.txt
pcache=/tmp/prefixes.txt
seen=/tmp/seen.txt
touch "$cache" "$pcache" "$seen"
readline () {
while read -r line; do
lineOriginal="${prefix}${line}"
if [ "${lineOriginal:0:40}" == "$upperCase" ]; then
echo "Password breached."
exit 1
fi
done
}
grep -qF "$upperCase" "$seen" && { echo "Password not found in breached database."; exit; }
readline < "$cache"
grep -q "^${prefix}" "$pcache" && { echo "Password not found in breached database."; exit; }
response=$(curl -s https://api.pwnedpasswords.com/range/${prefix} | tr -d $'\r')
echo "$response" >> "$cache"
echo "$prefix" >> "$pcache"
readline <<< "$response"
echo "Password not found in breached database."
echo "$upperCase" >> "$seen"
exit 0
I tried this script as-is from the article, which gives a false sense of password not being breached if the output from openssl is not in the format expected. Once I tried "password" and it said even that was not found, I knew something was up. Maybe it could have a sanity self-check added for safety sake.
In any case, I note a possible fix for this above, but here was mine:
hash="$(echo -n $password | openssl sha1 -hex -r | cut -d\ -f1)"
Note the necessary extra space after the backslash.
Thanks for this. Hope you don't mind, I borrowed the meat of the script and mashed it up with @agilebits' 1Password CLI tool, to check your 1Password entries against the breached password list. Admittedly, 1Password users are probably not the right audience for such a tool, as users of password managers are probably (hopefully?) using randomly generated, strong passwords, but it was a fun exercise.
I left it largely untouched, as I didn't experience any of the issues other commenters mentioned above.
There are a number of issues with that script:
-
without
-r
andIFS=
,read
would fail to preserve leading and
trailing space or tab characters in the input or backslashes.
See
https://unix.stackexchange.com/questions/209123/understanding-ifs-read-r-line
For instance, it would say that "test\123" is breached. -
with
echo -n
, that would not work properly for passwords like
-nenene
, and possibly (depending on the environment) some that
contain backslashes. See
https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo -
leaving a variable unquoted has a very special meaning in
shells like bash. See
https://unix.stackexchange.com/questions/171346/security-implications-of-forgetting-to-quote-a-variable-in-bash-posix-shells
Withecho $password
, $password would undergo split+glob. So
for instance, it could say that the "***" password is not
breached, becauseecho -n $password
would output the list of
non-hidden files in the current directory. -
On my system,
$ echo -n | openssl sha1
(stdin)= da39a3ee5e6b4b0d3255bfef95601890afd80709
see the leading "(stdin)= " which needs to be removed
tr '[a-z]' '[A-Z]'
only makes sense in the POSIX/C locale.
There's not much guarantee what you'll get in other locales.
tr '[:lower:]' '[:upper:]'
ortr abcdef ABCDEF
are better for
that. Recent versions of bash now also have builtin operators
for case conversion (hash=${hash^^}
)
How about:
IFS= read -rsp 'Password: ' password
echo
hash=$(printf %s "$password" | openssl sha1 | tr abcdef ABCDEF)
hash=${hash##* }
prefix=${hash:0:5}
suffix=${hash:5}
if
curl -s "https://api.pwnedpasswords.com/range/$prefix" |
grep "^$suffix" > /dev/null
then
echo "Password breached."
exit 1
else
echo "Password not found in breached database."
exit 0
fi
For fun, a two-line version (after input checking) that uses AWK for the dirty work:
#!/bin/sh
if [ -z "$1" ]
then
echo "Usage: ${0##*/} <password>"
exit 1
fi
HASH="$(printf "$1" | openssl sha1)"
curl -s "https://api.pwnedpasswords.com/range/${HASH:0:5}" |
awk -F":" -v SUFFIX="${HASH:5}" '$1 == toupper(SUFFIX) { print $2 }'
@croose using your version I get the following error Bad substitution
@stephane-chazelas yours worked perfectly - thanks
Thanks for your work on this and the really interesting blog post, great stuff.
This script fails with some versions of openssl (mine is 1.0.2n) due to some extraneous output:
This can be fixed by adding a line after line 6: