Skip to content

Instantly share code, notes, and snippets.

@IcyApril
Last active May 7, 2023 04:55
Show Gist options
  • Save IcyApril/56c3fdacb3a640f37c245e5813b98b99 to your computer and use it in GitHub Desktop.
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
@gosko
Copy link

gosko commented Feb 22, 2018

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:

$ hash=$(echo -n asdfasdf | openssl sha1)
$ echo $hash
(stdin)= 92429d82a41e930486c6de5ebda9602d55c39986

This can be fixed by adding a line after line 6:

hash=${hash#(stdin)= }

@dmerge
Copy link

dmerge commented Feb 22, 2018

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

@PH0lder
Copy link

PH0lder commented Feb 23, 2018

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.

@tdwalton
Copy link

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.

1passwordpwnedcheck.sh

@stephane-chazelas
Copy link

There are a number of issues with that script:

  1. without -r and IFS=, 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.

  2. 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

  3. 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
    With echo $password, $password would undergo split+glob. So
    for instance, it could say that the "***" password is not
    breached, because echo -n $password would output the list of
    non-hidden files in the current directory.

  4. On my system,

  $ echo -n | openssl sha1
  (stdin)= da39a3ee5e6b4b0d3255bfef95601890afd80709

see the leading "(stdin)= " which needs to be removed

  1. 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:]' or tr 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

@croose
Copy link

croose commented May 31, 2018

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 }'

@wavesailor
Copy link

@croose using your version I get the following error Bad substitution

@stephane-chazelas yours worked perfectly - thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment