Create a gist now

Instantly share code, notes, and snippets.

Embed
#!/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

This comment has been minimized.

Show comment
Hide comment
@gosko

gosko 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)= }

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

This comment has been minimized.

Show comment
Hide comment
@dmerge

dmerge 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

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

This comment has been minimized.

Show comment
Hide comment
@PH0lder

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

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

This comment has been minimized.

Show comment
Hide comment
@tdwalton

tdwalton Feb 24, 2018

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

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

This comment has been minimized.

Show comment
Hide comment
@stephane-chazelas

stephane-chazelas Mar 14, 2018

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

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

This comment has been minimized.

Show comment
Hide comment
@croose

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

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 }'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment