summaryrefslogtreecommitdiff
path: root/plugins/genpass/genpass-xkcd
diff options
context:
space:
mode:
authorTuowen Zhao <ztuowen@gmail.com>2020-12-16 22:13:45 -0700
committerTuowen Zhao <ztuowen@gmail.com>2020-12-16 22:13:45 -0700
commitfb45741fc1dbd40dd2be72bc35a28c6ee8f3f7a5 (patch)
treedd7746c9910755dfeb5bf28bda68e28b47d5771f /plugins/genpass/genpass-xkcd
parent3aaa0bc62ece494dd2b6e47a191de79e562156f9 (diff)
parentb28665aebb4c1b07a57890eb59551bc51d0acf37 (diff)
downloadzsh-fb45741fc1dbd40dd2be72bc35a28c6ee8f3f7a5.tar.gz
zsh-fb45741fc1dbd40dd2be72bc35a28c6ee8f3f7a5.tar.bz2
zsh-fb45741fc1dbd40dd2be72bc35a28c6ee8f3f7a5.zip
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'plugins/genpass/genpass-xkcd')
-rwxr-xr-xplugins/genpass/genpass-xkcd68
1 files changed, 68 insertions, 0 deletions
diff --git a/plugins/genpass/genpass-xkcd b/plugins/genpass/genpass-xkcd
new file mode 100755
index 000000000..a486ccb40
--- /dev/null
+++ b/plugins/genpass/genpass-xkcd
@@ -0,0 +1,68 @@
+#!/usr/bin/env zsh
+#
+# Usage: genpass-xkcd [NUM]
+#
+# Generate a password made of words from /usr/share/dict/words
+# with the security margin of at least 128 bits.
+#
+# Example password: 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster
+#
+# If given a numerical argument, generate that many passwords.
+#
+# The name of this utility is a reference to https://xkcd.com/936/.
+
+emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var -o extended_glob
+
+if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then
+ print -ru2 -- "usage: $0 [NUM]"
+ return 1
+fi
+
+zmodload zsh/system zsh/mathfunc || return
+
+local -r dict=/usr/share/dict/words
+
+if [[ ! -e $dict ]]; then
+ print -ru2 -- "$0: file not found: $dict"
+ return 1
+fi
+
+# Read all dictionary words and leave only those made of 1-6 characters.
+local -a words
+words=(${(M)${(f)"$(<$dict)"}:#[a-zA-Z](#c1,6)}) || return
+
+if (( $#words < 2 )); then
+ print -ru2 -- "$0: not enough suitable words in $dict"
+ return 1
+fi
+
+if (( $#words > 16#7FFFFFFF )); then
+ print -ru2 -- "$0: too many words in $dict"
+ return 1
+fi
+
+# Figure out how many words we need for 128 bits of security margin.
+# Each word adds log2($#words) bits.
+local -i n=$((ceil(128. / log2($#words))))
+
+{
+ local c
+ repeat ${1-1}; do
+ print -rn -- $n
+ repeat $n; do
+ while true; do
+ # Generate a random number in [0, 2**31).
+ local -i rnd=0
+ repeat 4; do
+ sysread -s1 c || return
+ (( rnd = (~(1 << 23) & rnd) << 8 | #c ))
+ done
+ # Avoid bias towards words in the beginning of the list.
+ (( rnd < 16#7FFFFFFF / $#words * $#words )) || continue
+ print -rn -- -$words[rnd%$#words+1]
+ break
+ done
+ done
+ print
+ done
+} </dev/urandom