A very simple bash script for searching Troy Hunt's pwnedpasswords.

Pwned Passwords

A few days ago Troy Hunt released a V2 update to Pwned Passwords.

Pwned Passwords is a service that allows you to check if your passwords have been leaked in security breaches. You can do this directly via haveibeenpwned.com or via the API.

In Troy's latest update he takes the number of available passwords via the service to just over half a billion.

At the same time he has also provided a new API that allows you to lookup a password without handing over the password that you want to check.

The new API functionality uses a mathematical property called k-anonymity to prevent the user having to handover the full hash of the password. The API for Pwned Passwords uses SHA1 hashes of the original passwords to protect any PII (Persoanlly Identifiable Information) that may exist in the original passwords.

Let's say you have a password hash:

21BD12DC183F740EE76F27B78EB39C8AD972A757

k-anonymity has been applied to this problem by saying that we only need to pass the first 5 characters of the hash (21BD1, in our example) to the API. The API will then respond with the matching suffixes that match the prefix we provided. We can then check the few hundred returned hash suffixes for the password we want to check.

It's a simple and brilliant idea!

The explanation I've provided here is very brief. I recommend you check our Troy's original blog post and Junade Ali's Cloudflare blog post Validating Leaked Passwords with k-Anonymity.

pwnedpasswords.sh

Below is my contribution to Troy's cause, a simple bash script that will allow you to check your password against the pwnedpasswords API.

pwnedpasswords.sh

The following script is also available via the jamesridgway/pwnedpasswords.sh GitHub repository.

#!/bin/bash

usage()
{
  printf "%b" "
This is a simple bash script for searching Troy Hunt's pwnedpassword API using the k-anonymity algorithm

Usage

  ${BASH_SOURCE[0]} [options] PASSWORD

Options:

    -h, --help    Shows this message

Arguments:

	PASSWORD    Provide the password as the first argument or leave blank to provide via STDINT or prompt

"
}

pre_requisites()
{
	if ! [ -x "$(command -v curl)" ]; then
		echo "ERROR: curl is required, please install curl."
		exit 1
	fi
}

pwned_password()
{
	local password sha1 short_sha1 sha1_suffix http_status http_body http_response
	password="$1"
	sha1=$(echo -n "$password" | sha1sum | awk '{print toupper($1)}')
	short_sha1=${sha1:0:5}
	sha1_suffix=${sha1:5}

	http_response=$(curl -s -w "\nHTTPSTATUS:%{http_code}\n" "https://api.pwnedpasswords.com/range/${short_sha1}")
	http_body="$(echo "$http_response" | sed '$d')"
	http_status=$(echo "$http_response" | tail -1 | sed -e 's/.*HTTPSTATUS://')

	if [ ! "$http_status" -eq 200 ]; then
	  echo "Error [HTTP status: $http_status]"
	  return 1
	fi

	MATCHES=$(echo "${http_body}" | grep "${sha1_suffix}" | awk -F ':' '{print $2}' | tr -d '[:space:]')
	return 0
}

OPTS=$(getopt -o ":h" -l "help" -n "$PROGRAM $COMMAND" -- "$@")
eval set -- "${OPTS}"
while true; do
  case "$1" in
    -h | --help )
      usage
      exit 0
      ;;
    -- )
      shift
      break
      ;;
    * )
      break
      ;;
  esac
done


 if [ -t 0 ]; then
 	PASSWORD="$1"
else
	PASSWORD=$(cat /dev/stdin)
fi

if [ -z "${PASSWORD}" ]; then
	echo "Enter password:"
	read -s PASSWORD
fi

pre_requisites
pwned_password "${PASSWORD}"

if [ -z "$MATCHES" ]; then
	echo "This password has not appeared in any data breaches!"
else
	echo "This password has appeared ${MATCHES} times in data breaches."
	exit 2
fi

Enjoy!

Edited 26 Feb 2018 23:05: Updated to support password input via stdin and explicit prompt.