-
Notifications
You must be signed in to change notification settings - Fork 11
/
pwhatch.sh
executable file
·269 lines (219 loc) · 6.59 KB
/
pwhatch.sh
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/bin/bash
# SPDX-License-Identifier: GPL-3.0-or-later
function help
{
cat <<EOF
password generator that creates secure and easy to communicate passwords
call: $0
$0 [OPTS] [#WORDS]
Example password: kingdoms-racists-sociologist-mute
See http://xkcd.com/936/ for the general idea.
This password generator creates a password via randomly
selecting words from a dictionary. By default 4 words are
selected.
Easy to communicate means that the password is easy to write down
and easy to phrase over a (secure!) telephone line (no marking of
characters like O vs. 0, l vs. I vs. 1, ..., no spelling of
single characters necessary). As a consequence, a short sequence
of real words is easier to remember than a long sequence of
random alphanumeric characters.
With an - say - english dictionary of 119773 words the generated
password has an entropy of log2(119773^4)~=67 bits. In contrast
to that a randomly generated alphanumeric password of 11
characters has log2((26*2+10)^11)~=64 bits of entropy.
The generated password is printed to stdout, while its entropy is
printed to stderr.
Options:
-c don't use a dict, just random alphanumeric characters
(#WORDS is number of characters then)
-f FILENAME wordlist filename (default: ~/.cache/words.gz)
-h show this help (more times for more help)
-m N specify minium #bits entropy (default: N=60)
-q don't print entropy message to stderr
-w N number of words (default: 4; the -c default: 12)
2016-2019, Georg Sauthoff <[email protected]>, GPLv3+
EOF
if [ $1 -gt 1 ]; then
add_help
fi
}
function add_help
{
cat <<EOF
## Entropy Examples
method bits expr
-------------------+-----------+--------------------
8 char alphnum 47 log2((26*2)+10)^8)
3 word dict 50 log2(119773^3)
9 char alphnum 53 log2((26*2)+10)^9)
10 char alphnum 59 log2((26*2)+10)^10)
11 char alphnum 65 log2((26*2)+10)^11)
4 word dict 67 log2(119773^4)
12 char alphnum 71 log2((26*2)+10)^12)
13 char alphnum 77 log2(((26*2)+10)^13)
14 char alphnum 83 log2(((26*2)+10)^14)
5 word dict 84 log2(119773^5)
15 char alphnum 89 log2(((26*2)+10)^15)
16 char alphnum 95 log2(((26*2)+10)^16)
6 word dict 101 log2(119773^6)
## Related Generators
### pwgen (http://sf.net/projects/pwgen)
Generates 'to be easily memorized' passwords that are 8
characters long, by default. The easy to memorize property thus
yields an entropy less than log2((26*2+10)^8)~=47 bits.
Example password: ooye0iSh
If those passwords are easy to remember depends on the used
definition of easy-to-remember.
### pwqgen (http://www.openwall.com/passwdqc/)
Generates 'quality controllable random passphrase[s]' with 47
bits of entropy, by default. Supports up to 85 bits of entropy.
Example: Home2Visual&Pizza
This goes into the right direction. A combination of words is
easy to communicate. The special characters and the the upper
case characters might be there to satisfy password policies but
otherwise they make it harder to remember.
### pwmake (http://libpwquality.fedorahosted.org/)
Generates 'random relatively easily pronounceable passwords'. It
has a minimum of 56 bits of entropy.
The man page contains some recommendations regarding the level of
entropy.
Example: ]4h#0J#@zEG#
It is easy to pronounce if one considers a random selection of
printable characters including left-bracket, hash, ampersand etc.
easy to pronounce.
2016, Georg Sauthoff <[email protected]>
EOF
}
set -eu
function check_entropy
{
local entropy=$1
if [ $quiet -eq 0 ]; then
echo "$entropy bits of entropy" >&2
fi
if [ "$entropy" -lt "$min_entropy" ]; then
echo "not enough entropy ($min_entropy)\
- use bigger dictionary and/or more words" >&2
exit 1
fi
}
function gen_nodict
{
entropy=$(echo "l((26*2+10)^$words)/l(2)" | bc -l | sed 's/\..*$//')
check_entropy $entropy
dd if=/dev/urandom status=none | LC_ALL=C tr -d -c 'A-Za-z0-9' \
| dd bs=1 count=$words status=none
echo
}
function parse_args
{
if [ $# -gt 0 ]; then
if [ "$1" = '--help' ]; then help 1; exit 0; fi
fi
quiet=0
use_dict=1
use_help=0
if command -v "$shuf" > /dev/null ; then : ; else shuf=""; fi
while getopts "cf:hm:nqw:" arg; do
case $arg in
c)
words=12
use_dict=0
;;
f)
wfile=$OPTARG
;;
h)
use_help=$((use_help+1))
;;
m)
min_entropy=$OPTARG
;;
n)
shuf=""
;;
q)
quiet=1
;;
w)
words=$OPTARG
;;
?)
exit 1
;;
esac
done
if [ $OPTIND -eq $# -o $OPTIND -lt $# ]; then
words=${!OPTIND}
fi
if [ $use_help -gt 0 ]; then
help $use_help
exit 0
fi
}
function init_wordlist
{
if [ -f "$wfile" ]; then
return
fi
if command -v "$aspell" > /dev/null ; then
for i in $lang; do
"$aspell" -d "$i" dump master | "$aspell" -l $i expand
done | "$sort" -u | gzip > "$wfile"
elif [ -f "/usr/share/myspell/en_US.dic" ]; then
for i in $lang; do
sed 's@/.*$@@' /usr/share/myspell/"$i"*.dic
done | "$sort" -u | gzip > "$wfile"
fi
}
function gen_dict
{
init_wordlist
local n=$(zcat "$wfile" | wc -l)
if [ "$n" -eq 0 ]; then
echo "Wordlist $wfile is empty" >&2
exit 1
fi
local entropy=$(echo "l($n^$words)/l(2)" | bc -l | sed 's/\..*$//')
check_entropy "$entropy"
if [ -n "$shuf" ]; then
zcat "$wfile" | "$shuf" --random-source /dev/urandom -r -n $words \
| "$awk" '{print(tolower($0))}' \
| tr -d "'" \
| "$paste" -sd '-'
else
# NB: 1048576 = 2**20
if [ "$n" -ge 1048576 ]; then
echo "Wordlist $wfile is too large" >&2
exit 1
fi
for i in `seq 1 $words`; do
zcat "$wfile" \
| "$sed" -n $(< /dev/urandom od -tu4 -An -v -w4 | awk '{ $1 = $1 % 1048576 } $1 < '$n' { print $1+1; exit; }')p \
| "$awk" '{print(tolower($0))}' \
| tr -d "'"
done | "$paste" -sd '-'
fi
}
: ${aspell:=aspell}
: ${awk:=awk}
: ${lang:=en}
min_entropy=60
# GNU coreutils paste
: ${paste:=paste}
: ${sed:=sed}
# GNU coreutils shuf
: ${shuf:=shuf}
: ${sort:=sort}
: ${wfile:=~/.cache/words.gz}
words=4
function main
{
parse_args "$@"
if [ $use_dict -ne 0 ]; then
gen_dict
else
gen_nodict
fi
}
main "$@"