b28665aebb4c1b07a57890eb59551bc51d0acf37
- Author
- Roman Perepelitsa <roman.perepelitsa@gmail.com>
- Committer
- GitHub <noreply@github.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/plugins/genpass/README.md b/plugins/genpass/README.md
2index e6e7a513825f6c0802024a1de5a819928e6df073..a5ff4a876008e503c7d9fcdc935863cfe26c72ab 100644
3--- a/plugins/genpass/README.md
4+++ b/plugins/genpass/README.md
5@@ -5,21 +5,22 @@ has at least a 128-bit security margin and generates passwords from the
6 cryptographically secure `/dev/urandom`. Each generator can also take an
7 optional numeric argument to generate multiple passwords.
8
9-Requirements:
10+To use it from an interactive ZSH, add `genpass` to the plugins array in your
11+zshrc file:
12
13-* `grep(1)`
14-* GNU coreutils (or appropriate for your system)
15-* Word list providing `/usr/share/dict/words`
16+ plugins=(... genpass)
17
18-To use it, add `genpass` to the plugins array in your zshrc file:
19+You can also invoke password generators directly (they are implemented as
20+standalone executable files), which can be handy when you need to generate
21+passwords in a script:
22
23- plugins=(... genpass)
24+ ~/.oh-my-zsh/plugins/genpass/genpass-apple 3
25
26 ## genpass-apple
27
28 Generates a pronounceable pseudoword passphrase of the "cvccvc" consonant/vowel
29 syntax, inspired by [Apple's iCloud Keychain password generator][1]. Each
30-pseudoword has exactly 1 digit placed at the edge of a "word" and exactly 1
31+password has exactly 1 digit placed at the edge of a "word" and exactly 1
32 capital letter to satisfy most password security requirements.
33
34 % genpass-apple
35diff --git a/plugins/genpass/genpass-apple b/plugins/genpass/genpass-apple
36new file mode 100755
37index 0000000000000000000000000000000000000000..963ab644723ed5b492d5fa1a645ee38b10142e9e
38--- /dev/null
39+++ b/plugins/genpass/genpass-apple
40@@ -0,0 +1,79 @@
41+#!/usr/bin/env zsh
42+#
43+# Usage: genpass-apple [NUM]
44+#
45+# Generate a password made of 6 pseudowords of 6 characters each
46+# with the security margin of at least 128 bits.
47+#
48+# Example password: xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp
49+#
50+# If given a numerical argument, generate that many passwords.
51+
52+emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var
53+
54+if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then
55+ print -ru2 -- "usage: $0 [NUM]"
56+ return 1
57+fi
58+
59+zmodload zsh/system zsh/mathfunc || return
60+
61+{
62+ local -r vowels=aeiouy
63+ local -r consonants=bcdfghjklmnpqrstvwxz
64+ local -r digits=0123456789
65+
66+ # Sets REPLY to a uniformly distributed random number in [1, $1].
67+ # Requires: $1 <= 256.
68+ function -$0-rand() {
69+ local c
70+ while true; do
71+ sysread -s1 c || return
72+ # Avoid bias towards smaller numbers.
73+ (( #c < 256 / $1 * $1 )) && break
74+ done
75+ typeset -g REPLY=$((#c % $1 + 1))
76+ }
77+
78+ local REPLY chars
79+
80+ repeat ${1-1}; do
81+ # Generate 6 pseudowords of the form cvccvc where c and v
82+ # denote random consonants and vowels respectively.
83+ local words=()
84+ repeat 6; do
85+ words+=('')
86+ repeat 2; do
87+ for chars in $consonants $vowels $consonants; do
88+ -$0-rand $#chars || return
89+ words[-1]+=$chars[REPLY]
90+ done
91+ done
92+ done
93+
94+ local pwd=${(j:-:)words}
95+
96+ # Replace either the first or the last character in one of
97+ # the words with a random digit.
98+ -$0-rand $#digits || return
99+ local digit=$digits[REPLY]
100+ -$0-rand $((2 * $#words)) || return
101+ pwd[REPLY/2*7+2*(REPLY%2)-1]=$digit
102+
103+ # Convert one lower-case character to upper case.
104+ while true; do
105+ -$0-rand $#pwd || return
106+ [[ $vowels$consonants == *$pwd[REPLY]* ]] && break
107+ done
108+ # NOTE: We aren't using ${(U)c} here because its results are
109+ # locale-dependent. For example, when upper-casing 'i' in Turkish
110+ # locale we would get 'İ', a.k.a. latin capital letter i with dot
111+ # above. We could set LC_CTYPE=C locally but then we would run afoul
112+ # of this zsh bug: https://www.zsh.org/mla/workers/2020/msg00588.html.
113+ local c=$pwd[REPLY]
114+ printf -v c '%o' $((#c - 32))
115+ printf "%s\\$c%s\\n" "$pwd[1,REPLY-1]" "$pwd[REPLY+1,-1]" || return
116+ done
117+} always {
118+ unfunction -m -- "-${(b)0}-*"
119+} </dev/urandom
120diff --git a/plugins/genpass/genpass-monkey b/plugins/genpass/genpass-monkey
121new file mode 100755
122index 0000000000000000000000000000000000000000..94ff5e13123ccec1e32a6a0a070478f6fa1ec3f6
123--- /dev/null
124+++ b/plugins/genpass/genpass-monkey
125@@ -0,0 +1,32 @@
126+#!/usr/bin/env zsh
127+#
128+# Usage: genpass-monkey [NUM]
129+#
130+# Generate a password made of 26 alphanumeric characters
131+# with the security margin of at least 128 bits.
132+#
133+# Example password: nz5ej2kypkvcw0rn5cvhs6qxtm
134+#
135+# If given a numerical argument, generate that many passwords.
136+
137+emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var
138+
139+if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then
140+ print -ru2 -- "usage: $0 [NUM]"
141+ return 1
142+fi
143+
144+zmodload zsh/system || return
145+
146+{
147+ local -r chars=abcdefghjkmnpqrstvwxyz0123456789
148+ local c
149+ repeat ${1-1}; do
150+ repeat 26; do
151+ sysread -s1 c || return
152+ # There is uniform because $#chars divides 256.
153+ print -rn -- $chars[#c%$#chars+1]
154+ done
155+ print
156+ done
157+} </dev/urandom
158diff --git a/plugins/genpass/genpass-xkcd b/plugins/genpass/genpass-xkcd
159new file mode 100755
160index 0000000000000000000000000000000000000000..a486ccb40e2057593bb997c46d162362ccaa375d
161--- /dev/null
162+++ b/plugins/genpass/genpass-xkcd
163@@ -0,0 +1,68 @@
164+#!/usr/bin/env zsh
165+#
166+# Usage: genpass-xkcd [NUM]
167+#
168+# Generate a password made of words from /usr/share/dict/words
169+# with the security margin of at least 128 bits.
170+#
171+# Example password: 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster
172+#
173+# If given a numerical argument, generate that many passwords.
174+#
175+# The name of this utility is a reference to https://xkcd.com/936/.
176+
177+emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var -o extended_glob
178+
179+if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then
180+ print -ru2 -- "usage: $0 [NUM]"
181+ return 1
182+fi
183+
184+zmodload zsh/system zsh/mathfunc || return
185+
186+local -r dict=/usr/share/dict/words
187+
188+if [[ ! -e $dict ]]; then
189+ print -ru2 -- "$0: file not found: $dict"
190+ return 1
191+fi
192+
193+# Read all dictionary words and leave only those made of 1-6 characters.
194+local -a words
195+words=(${(M)${(f)"$(<$dict)"}:#[a-zA-Z](#c1,6)}) || return
196+
197+if (( $#words < 2 )); then
198+ print -ru2 -- "$0: not enough suitable words in $dict"
199+ return 1
200+fi
201+
202+if (( $#words > 16#7FFFFFFF )); then
203+ print -ru2 -- "$0: too many words in $dict"
204+ return 1
205+fi
206+
207+# Figure out how many words we need for 128 bits of security margin.
208+# Each word adds log2($#words) bits.
209+local -i n=$((ceil(128. / log2($#words))))
210+
211+{
212+ local c
213+ repeat ${1-1}; do
214+ print -rn -- $n
215+ repeat $n; do
216+ while true; do
217+ # Generate a random number in [0, 2**31).
218+ local -i rnd=0
219+ repeat 4; do
220+ sysread -s1 c || return
221+ (( rnd = (~(1 << 23) & rnd) << 8 | #c ))
222+ done
223+ # Avoid bias towards words in the beginning of the list.
224+ (( rnd < 16#7FFFFFFF / $#words * $#words )) || continue
225+ print -rn -- -$words[rnd%$#words+1]
226+ break
227+ done
228+ done
229+ print
230+ done
231+} </dev/urandom
232diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh
233index e6a1cef348ff2611fcfa4c207b638d6521358357..a0ea841cdc46983f60bfa2d25c03562cf34f698c 100644
234--- a/plugins/genpass/genpass.plugin.zsh
235+++ b/plugins/genpass/genpass.plugin.zsh
236@@ -1,106 +1 @@
237-autoload -U regexp-replace
238-zmodload zsh/mathfunc
239-
240-genpass-apple() {
241- # Generates a 128-bit password of 6 pseudowords of 6 characters each
242- # EG, xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp
243- # Can take a numerical argument for generating extra passwords
244- local -i i j num
245-
246- [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1
247-
248- local consonants="$(LC_ALL=C tr -cd b-df-hj-np-tv-xz < /dev/urandom \
249- | head -c $((24*$num)))"
250- local vowels="$(LC_ALL=C tr -cd aeiouy < /dev/urandom | head -c $((12*$num)))"
251- local digits="$(LC_ALL=C tr -cd 0-9 < /dev/urandom | head -c $num)"
252-
253- # The digit is placed on a pseudoword edge using $base36. IE, Dvccvc or cvccvD
254- local position="$(LC_ALL=C tr -cd 056bchinotuz < /dev/urandom | head -c $num)"
255- local -A base36=(0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 a 10 b 11 c 12 d 13 \
256- e 14 f 15 g 16 h 17 i 18 j 19 k 20 l 21 m 22 n 23 o 24 p 25 q 26 r 27 s 28 \
257- t 29 u 30 v 31 w 32 x 33 y 34 z 35)
258-
259- for i in {1..$num}; do
260- local pseudo=""
261-
262- for j in {1..12}; do
263- # Uniformly iterate through $consonants and $vowels for each $i and $j
264- # Creates cvccvccvccvccvccvccvccvccvccvccvccvc for each $num
265- pseudo="${pseudo}${consonants:$((24*$i+2*${j}-26)):1}"
266- pseudo="${pseudo}${vowels:$((12*$i+${j}-13)):1}"
267- pseudo="${pseudo}${consonants:$((24*$i+2*${j}-25)):1}"
268- done
269-
270- local -i digit_pos=${base36[${position[$i]}]}
271- local -i char_pos=$digit_pos
272-
273- # The digit and uppercase character must be in different locations
274- while [[ $digit_pos == $char_pos ]]; do
275- char_pos=$base36[$(LC_ALL=C tr -cd 0-9a-z < /dev/urandom | head -c 1)]
276- done
277-
278- # Places the digit on a pseudoword edge
279- regexp-replace pseudo "^(.{$digit_pos}).(.*)$" \
280- '${match[1]}${digits[$i]}${match[2]}'
281-
282- # Uppercase a random character (that is not a digit)
283- regexp-replace pseudo "^(.{$char_pos})(.)(.*)$" \
284- '${match[1]}${(U)match[2]}${match[3]}'
285-
286- # Hyphenate each 6-character pseudoword
287- regexp-replace pseudo '^(.{6})(.{6})(.{6})(.{6})(.{6})(.{6})$' \
288- '${match[1]}-${match[2]}-${match[3]}-${match[4]}-${match[5]}-${match[6]}'
289-
290- printf "${pseudo}\n"
291- done
292-}
293-
294-genpass-monkey() {
295- # Generates a 128-bit base32 password as if monkeys banged the keyboard
296- # EG, nz5ej2kypkvcw0rn5cvhs6qxtm
297- # Can take a numerical argument for generating extra passwords
298- local -i i num
299-
300- [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1
301-
302- local pass=$(LC_ALL=C tr -cd '0-9a-hjkmnp-tv-z' < /dev/urandom \
303- | head -c $((26*$num)))
304-
305- for i in {1..$num}; do
306- printf "${pass:$((26*($i-1))):26}\n"
307- done
308-}
309-
310-genpass-xkcd() {
311- # Generates a 128-bit XKCD-style passphrase
312- # e.g, 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster
313- # Can take a numerical argument for generating extra passwords
314-
315- if (( ! $+commands[shuf] )); then
316- echo >&2 "$0: \`shuf\` command not found. Install coreutils (\`brew install coreutils\` on macOS)."
317- return 1
318- fi
319-
320- if [[ ! -e /usr/share/dict/words ]]; then
321- echo >&2 "$0: no wordlist found in \`/usr/share/dict/words\`. Install one first."
322- return 1
323- fi
324-
325- local -i i num
326-
327- [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1
328-
329- # Get all alphabetic words of at most 6 characters in length
330- local dict=$(LC_ALL=C grep -E '^[a-zA-Z]{1,6}$' /usr/share/dict/words)
331-
332- # Calculate the base-2 entropy of each word in $dict
333- # Entropy is e = L * log2(C), where L is the length of the password (here,
334- # in words) and C the size of the character set (here, words in $dict).
335- # Solve for e = 128 bits of entropy. Recall: log2(n) = log(n)/log(2).