-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpwnedcheck
executable file
·73 lines (64 loc) · 2.29 KB
/
pwnedcheck
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env bash
#
# pwnedcheck (#) 1.0.1
# Renee Margaret McConahy <[email protected]>
#<
# Usage: pwnedcheck
# pwnedcheck -h
#
# Commands:
# -h Show this help.
#
# This reads newline-terminated passwords from STDIN and securely checks each
# against the Have I Been Pwned database of leaked passwords. For each
# candidate, it prints a colon-delimited pairing of the password and the number
# of times it appears in HIBP's database. It returns non-zero if any candidate
# is matched.
#
# Security: This computes the SHA-1 hash and sends the first five hexadecimal
# digits to the API endpoint; the endpoint returns a list of hashes that begin
# with the specified prefix. Thus, only 20 bits of the 160-bit unsalted SHA-1
# hash of each password is disclosed.
#>
fail() {
[[ -n ${1-} ]] && echo "Error: $1, quitting!" >&2
exit 2
}
trap 'fail "Uncaught error at $(basename -- "$0"):$LINENO"' ERR
set -Euo pipefail
LC_ALL=C # Force traditional collation for Bash's regex matching.
while getopts ph opt; do
case $opt in
h|*)
sed -ne '/^#</,/^#>/ { /^#\(<\|>\)/d; s/^# \?//; p; }' -- "$0"
[[ $opt == "?" ]] && exit 1
exit 0 ;;
esac
done
shift $((OPTIND - 1))
type openssl &>/dev/null || quit "Could not find required executable openssl"
declare -i found=0
while read -r password; do
hash=$(printf "%s" "$password" | openssl dgst -sha1 |
sed -e "s/^.* //" -e "y/abcdef/ABCDEF/")
[[ $hash =~ ^[0-9A-F]{40}$ ]] || fail "Hash failed: \"$hash\""
mapfile -t output < <(curl --disable --silent --write-out \
'\n%{http_code}\n' "https://api.pwnedpasswords.com/range/${hash::5}")
status=${output[-1]}
[[ $status == 200 ]] || fail "Got HTTP $status"
unset 'output[${#output[@]}-1]'
((${#output[@]} < 100)) &&
echo "Warning: We only got ${#output[@]} hashes." >&2
for entry in "${output[@]}"; do
# Each entry is a colon-delimited list whose first element is the SHA-1
# hash with the first five hexidecimal digits omitted and whose second
# element is the number of matches.
if [[ ${entry%%:*} == "${hash:5}" ]]; then
found=1
printf "%s:%s\\n" "$password" "${entry##*:}"
continue 2
fi
done
printf "%s:0\\n" "$password"
done
exit $found