From 9d6b3984f92cf7f4411b40dfb5a0897b260ae368 Mon Sep 17 00:00:00 2001 From: Aaron Toponce Date: Sat, 12 Dec 2020 04:50:45 -0700 Subject: feat(plugins): add genpass plugin with 3 distinct password generators (#9502) --- plugins/genpass/README.md | 65 ++++++++++++++++++++++++++ plugins/genpass/genpass.plugin.zsh | 95 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 plugins/genpass/README.md create mode 100644 plugins/genpass/genpass.plugin.zsh (limited to 'plugins/genpass') diff --git a/plugins/genpass/README.md b/plugins/genpass/README.md new file mode 100644 index 000000000..e6e7a5138 --- /dev/null +++ b/plugins/genpass/README.md @@ -0,0 +1,65 @@ +# genpass + +This plugin provides three unique password generators for ZSH. Each generator +has at least a 128-bit security margin and generates passwords from the +cryptographically secure `/dev/urandom`. Each generator can also take an +optional numeric argument to generate multiple passwords. + +Requirements: + +* `grep(1)` +* GNU coreutils (or appropriate for your system) +* Word list providing `/usr/share/dict/words` + +To use it, add `genpass` to the plugins array in your zshrc file: + + plugins=(... genpass) + +## genpass-apple + +Generates a pronounceable pseudoword passphrase of the "cvccvc" consonant/vowel +syntax, inspired by [Apple's iCloud Keychain password generator][1]. Each +pseudoword has exactly 1 digit placed at the edge of a "word" and exactly 1 +capital letter to satisfy most password security requirements. + + % genpass-apple + gelcyv-foqtam-fotqoh-viMleb-lexduv-6ixfuk + + % genpass-apple 3 + japvyz-qyjti4-kajrod-nubxaW-hukkan-dijcaf + vydpig-fucnul-3ukpog-voggom-zygNad-jepgad + zocmez-byznis-hegTaj-jecdyq-qiqmiq-5enwom + +[1]: https://developer.apple.com/password-rules/ + +## genpass-monkey + +Generates visually unambiguous random meaningless strings using [Crockford's +base32][2]. + + % genpass-monkey + xt7gn976e7jj3fstgpy27330x3 + + % genpass-monkey 3 + n1qqwtzgejwgqve9yzf2gxvx4m + r2n3f5s6vbqs2yx7xjnmahqewy + 296w9y9rts3p5r9yay0raek8e5 + +[2]: https://www.crockford.com/base32.html + +## genpass-xkcd + +Generates passphrases from `/usr/share/dict/words` inspired by the [famous (and +slightly misleading) XKCD comic][3]. Each passphrase is prepended with a digit +showing the number of words in the passphrase to adhere to password security +requirements that require digits. Each word is 6 characters or less. + + % genpass-xkcd + 9-eaten-Slav-rife-aired-hill-cordon-splits-welsh-napes + + % genpass-xkcd 3 + 9-worker-Vlad-horde-shrubs-smite-thwart-paw-alters-prawns + 9-tutors-stink-rhythm-junk-snappy-hooray-barbs-mewl-clomp + 9-vital-escape-Angkor-Huff-wet-Mayra-abbés-putts-guzzle + +[3]: https://xkcd.com/936/ diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh new file mode 100644 index 000000000..15bfebda8 --- /dev/null +++ b/plugins/genpass/genpass.plugin.zsh @@ -0,0 +1,95 @@ +autoload -U regexp-replace +zmodload zsh/mathfunc + +genpass-apple() { + # Generates a 128-bit password of 6 pseudowords of 6 characters each + # EG, xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp + # Can take a numerical argument for generating extra passwords + local -i i j num + + [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 + + local consonants="$(LC_ALL=C tr -cd b-df-hj-np-tv-xz < /dev/urandom \ + | head -c $((24*$num)))" + local vowels="$(LC_ALL=C tr -cd aeiouy < /dev/urandom | head -c $((12*$num)))" + local digits="$(LC_ALL=C tr -cd 0-9 < /dev/urandom | head -c $num)" + + # The digit is placed on a pseudoword edge using $base36. IE, Dvccvc or cvccvD + local position="$(LC_ALL=C tr -cd 056bchinotuz < /dev/urandom | head -c $num)" + 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 \ + 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 \ + t 29 u 30 v 31 w 32 x 33 y 34 z 35) + + for i in {1..$num}; do + local pseudo="" + + for j in {1..12}; do + # Uniformly iterate through $consonants and $vowels for each $i and $j + # Creates cvccvccvccvccvccvccvccvccvccvccvccvc for each $num + pseudo="${pseudo}${consonants:$((24*$i+2*${j}-26)):1}" + pseudo="${pseudo}${vowels:$((12*$i+${j}-13)):1}" + pseudo="${pseudo}${consonants:$((24*$i+2*${j}-25)):1}" + done + + local -i digit_pos=${base36[${position[$i]}]} + local -i char_pos=$digit_pos + + # The digit and uppercase character must be in different locations + while [[ $digit_pos == $char_pos ]]; do + char_pos=$base36[$(LC_ALL=C tr -cd 0-9a-z < /dev/urandom | head -c 1)] + done + + # Places the digit on a pseudoword edge + regexp-replace pseudo "^(.{$digit_pos}).(.*)$" \ + '${match[1]}${digits[$i]}${match[2]}' + + # Uppercase a random character (that is not a digit) + regexp-replace pseudo "^(.{$char_pos})(.)(.*)$" \ + '${match[1]}${(U)match[2]}${match[3]}' + + # Hyphenate each 6-character pseudoword + regexp-replace pseudo '^(.{6})(.{6})(.{6})(.{6})(.{6})(.{6})$' \ + '${match[1]}-${match[2]}-${match[3]}-${match[4]}-${match[5]}-${match[6]}' + + printf "${pseudo}\n" + done +} + +genpass-monkey() { + # Generates a 128-bit base32 password as if monkeys banged the keyboard + # EG, nz5ej2kypkvcw0rn5cvhs6qxtm + # Can take a numerical argument for generating extra passwords + local -i i num + + [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 + + local pass=$(LC_ALL=C tr -cd '0-9a-hjkmnp-tv-z' < /dev/urandom \ + | head -c $((26*$num))) + + for i in {1..$num}; do + printf "${pass:$((26*($i-1))):26}\n" + done +} + +genpass-xkcd() { + # Generates a 128-bit XKCD-style passphrase + # EG, 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster + # Can take a numerical argument for generating extra passwords + local -i i num + + [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 + + # Get all alphabetic words of at most 6 characters in length + local dict=$(grep -E '^[a-zA-Z]{,6}$' /usr/share/dict/words) + + # Calculate the base-2 entropy of each word in $dict + # Entropy is e = L * log2(C), where L is the length of the password (here, + # in words) and C the size of the character set (here, words in $dict). + # Solve for e = 128 bits of entropy. Recall: log2(n) = log(n)/log(2). + local -i n=$((int(ceil(128*log(2)/log(${(w)#dict}))))) + + for i in {1..$num}; do + printf "$n-" + printf "$dict" | shuf -n "$n" | paste -sd '-' + done +} -- cgit v1.2.3-70-g09d2 From f8e9339c87ab6b17ee34d34a72ddbe89333e9fa1 Mon Sep 17 00:00:00 2001 From: Aaron Toponce Date: Sat, 12 Dec 2020 17:20:41 -0700 Subject: fix(genpass): only use words with ASCII characters in `genpass-xkcd` (#9508) --- plugins/genpass/genpass.plugin.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/genpass') diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh index 15bfebda8..65bbd2e48 100644 --- a/plugins/genpass/genpass.plugin.zsh +++ b/plugins/genpass/genpass.plugin.zsh @@ -80,7 +80,7 @@ genpass-xkcd() { [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 # Get all alphabetic words of at most 6 characters in length - local dict=$(grep -E '^[a-zA-Z]{,6}$' /usr/share/dict/words) + local dict=$(LC_ALL=C grep -E '^[a-zA-Z]{,6}$' /usr/share/dict/words) # Calculate the base-2 entropy of each word in $dict # Entropy is e = L * log2(C), where L is the length of the password (here, -- cgit v1.2.3-70-g09d2 From 2a0ae3315db98d137de547e2cb9adfbc38263e6c Mon Sep 17 00:00:00 2001 From: Matt Lewin Date: Sun, 13 Dec 2020 16:28:22 -0500 Subject: fix(genpass): fix grep regex in `genpass-xkcd` for FreeBSD version (#9514) --- plugins/genpass/genpass.plugin.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/genpass') diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh index 65bbd2e48..e7f86bf7a 100644 --- a/plugins/genpass/genpass.plugin.zsh +++ b/plugins/genpass/genpass.plugin.zsh @@ -80,7 +80,7 @@ genpass-xkcd() { [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 # Get all alphabetic words of at most 6 characters in length - local dict=$(LC_ALL=C grep -E '^[a-zA-Z]{,6}$' /usr/share/dict/words) + local dict=$(LC_ALL=C grep -E '^[a-zA-Z]{1,6}$' /usr/share/dict/words) # Calculate the base-2 entropy of each word in $dict # Entropy is e = L * log2(C), where L is the length of the password (here, -- cgit v1.2.3-70-g09d2 From 619097cc2ad31c1b5086870293739d41dd4129c3 Mon Sep 17 00:00:00 2001 From: Patrick Harrison Date: Mon, 14 Dec 2020 09:52:02 +0700 Subject: fix(genpass): check for presence of shuf command. "shuf" is not a standard command on MacOS and requires installation of the brew coreutils package --- plugins/genpass/genpass.plugin.zsh | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'plugins/genpass') diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh index e7f86bf7a..f1ad80bba 100644 --- a/plugins/genpass/genpass.plugin.zsh +++ b/plugins/genpass/genpass.plugin.zsh @@ -75,6 +75,12 @@ genpass-xkcd() { # Generates a 128-bit XKCD-style passphrase # EG, 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster # Can take a numerical argument for generating extra passwords + + if (( ! $+commands[shuf] )); then + echo >&2 "$0: \`shuf\` command not found. Install coreutils (\`brew install coreutils\` on macOS)." + return 1 + fi + local -i i num [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 -- cgit v1.2.3-70-g09d2 From 076f7f1eb19914877e49eb186eb076fc3e493b36 Mon Sep 17 00:00:00 2001 From: Marc Cornellà Date: Mon, 14 Dec 2020 15:42:10 +0100 Subject: fix(genpass): warn if no wordlist is found --- plugins/genpass/genpass.plugin.zsh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'plugins/genpass') diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh index f1ad80bba..1353ef456 100644 --- a/plugins/genpass/genpass.plugin.zsh +++ b/plugins/genpass/genpass.plugin.zsh @@ -73,7 +73,7 @@ genpass-monkey() { genpass-xkcd() { # Generates a 128-bit XKCD-style passphrase - # EG, 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster + # e.g, 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster # Can take a numerical argument for generating extra passwords if (( ! $+commands[shuf] )); then @@ -81,6 +81,11 @@ genpass-xkcd() { return 1 fi + if [[ ! -e /usr/share/dict/words ]]; then + echo >&2 "$0: no wordlist found in \`/usr/share/dict/words\`. Install one first." + return 1 + fi + local -i i num [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 -- cgit v1.2.3-70-g09d2 From 2db42c6ce745ed37262bed6c97683a00a430f076 Mon Sep 17 00:00:00 2001 From: Patrick Harrison Date: Mon, 14 Dec 2020 10:11:12 +0700 Subject: fix(genpass): add compatibility for macOS paste command "paste" on macOS requires a '-' to signify that the standard input is used. Without the '-' character, the command errors out. --- plugins/genpass/genpass.plugin.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/genpass') diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh index 1353ef456..e6a1cef34 100644 --- a/plugins/genpass/genpass.plugin.zsh +++ b/plugins/genpass/genpass.plugin.zsh @@ -101,6 +101,6 @@ genpass-xkcd() { for i in {1..$num}; do printf "$n-" - printf "$dict" | shuf -n "$n" | paste -sd '-' + printf "$dict" | shuf -n "$n" | paste -sd '-' - done } -- cgit v1.2.3-70-g09d2 From b28665aebb4c1b07a57890eb59551bc51d0acf37 Mon Sep 17 00:00:00 2001 From: Roman Perepelitsa Date: Wed, 16 Dec 2020 16:57:59 +0100 Subject: fix(genpass): improve performance and usability and fix bugs (#9520) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *Bugs* The following bugs have been fixed: - All generators ignored errors from external commands. For example, if `/usr/share/dict/words` was unreadable, `genpass-xkcd` would print "0-" as a password and return success. - All generators silently ignored the argument if it wasn't a number. For example, `genpass-apple -2` was generating one password and not printing any errors. - All generators silently ignored extra arguments. For example, `genpass-apple -n 2` was generating one password and not printing any errors. - `genpass-xkcd` was generating passwords with less than 128 bits of security margin in contradiction to documentation. The smaller the dictionary size, the weaker the passwords it was generating. For a dictionary with 27 words, `genpass-xkcd` was generating passwords with 93 bits of security margin (`log2(27!)`). - The source of random data used by `genpass-xkcd` was not cryptographically secure in contradiction to documentation. See: https://www.gnu.org/software/coreutils/manual/html_node/Random-sources.html - `genpass-apple` could generate a password with non-ascii characters depending on user locale. For example, passwords could contain 'İ' for users with Turkish locale. - `genpass-apple` didn't work with `ksh_arrays` shell option. - `genpass-xkcd` was printing spurious errors with `ksh_arrays` shell option. - `genpass-xkcd` was producing too short (weak) or too strong (long) and/or printing errors when `IFS` was set to non-default value. - All generators were printing fewer passwords than requested and returning success when passed a very large number as an argument. *Usability* Generators are now implemented as self-contained executable files. They can be invoked from scripts with no additional setup. Generators no longer depend on external commands. The only dependencies are `/dev/urandom` and, for `genpass-xkcd`, `/usr/share/dict/words`. All generators used to silently ignore all arguments after the first and the first argument if it wasn't a number. For example, both `genpass-apple -2` and `genpass-apple -n 2` were generating one password and not printing any errors. Now these print an error and fail. *Performance* The time it takes to load the plugin has been greatly reduced. This translates into faster zsh startup when the plugin is enabled. Incidentally, two generators out of three have been sped up to a large degree while one generator (`genpass-xkcd`) has gotten slower. This is unlikely to matter one way or another unless generating a very large number of passwords. In the latter case `genpass-xkcd` is now also faster than it used to be. The following table shows benchmark results from Linux x86-64 on i9-7900X. The numbers in the second and third columns show how many times a given command could be executed per second. Higher numbers are better. command | before (Hz) | after (Hz) | speedup | ----------------------------|------------:|-----------:|--------:| `source genpass.plugin.zsh` | 4810 | 68700 | +1326% | `genpass-apple` | 30.3 | 893 | +2846% | `genpass-monkey` | 203 | 5290 | +2504% | `genpass-xkcd` | 34.4 | 14.5 | -58% | `genpass-xkcd 1000` | 0.145 | 0.804 | +454% | --- plugins/genpass/README.md | 15 +++--- plugins/genpass/genpass-apple | 79 +++++++++++++++++++++++++++ plugins/genpass/genpass-monkey | 32 +++++++++++ plugins/genpass/genpass-xkcd | 68 +++++++++++++++++++++++ plugins/genpass/genpass.plugin.zsh | 107 +------------------------------------ 5 files changed, 188 insertions(+), 113 deletions(-) create mode 100755 plugins/genpass/genpass-apple create mode 100755 plugins/genpass/genpass-monkey create mode 100755 plugins/genpass/genpass-xkcd (limited to 'plugins/genpass') diff --git a/plugins/genpass/README.md b/plugins/genpass/README.md index e6e7a5138..a5ff4a876 100644 --- a/plugins/genpass/README.md +++ b/plugins/genpass/README.md @@ -5,21 +5,22 @@ has at least a 128-bit security margin and generates passwords from the cryptographically secure `/dev/urandom`. Each generator can also take an optional numeric argument to generate multiple passwords. -Requirements: +To use it from an interactive ZSH, add `genpass` to the plugins array in your +zshrc file: -* `grep(1)` -* GNU coreutils (or appropriate for your system) -* Word list providing `/usr/share/dict/words` + plugins=(... genpass) -To use it, add `genpass` to the plugins array in your zshrc file: +You can also invoke password generators directly (they are implemented as +standalone executable files), which can be handy when you need to generate +passwords in a script: - plugins=(... genpass) + ~/.oh-my-zsh/plugins/genpass/genpass-apple 3 ## genpass-apple Generates a pronounceable pseudoword passphrase of the "cvccvc" consonant/vowel syntax, inspired by [Apple's iCloud Keychain password generator][1]. Each -pseudoword has exactly 1 digit placed at the edge of a "word" and exactly 1 +password has exactly 1 digit placed at the edge of a "word" and exactly 1 capital letter to satisfy most password security requirements. % genpass-apple diff --git a/plugins/genpass/genpass-apple b/plugins/genpass/genpass-apple new file mode 100755 index 000000000..963ab6447 --- /dev/null +++ b/plugins/genpass/genpass-apple @@ -0,0 +1,79 @@ +#!/usr/bin/env zsh +# +# Usage: genpass-apple [NUM] +# +# Generate a password made of 6 pseudowords of 6 characters each +# with the security margin of at least 128 bits. +# +# Example password: xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp +# +# If given a numerical argument, generate that many passwords. + +emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var + +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 vowels=aeiouy + local -r consonants=bcdfghjklmnpqrstvwxz + local -r digits=0123456789 + + # Sets REPLY to a uniformly distributed random number in [1, $1]. + # Requires: $1 <= 256. + function -$0-rand() { + local c + while true; do + sysread -s1 c || return + # Avoid bias towards smaller numbers. + (( #c < 256 / $1 * $1 )) && break + done + typeset -g REPLY=$((#c % $1 + 1)) + } + + local REPLY chars + + repeat ${1-1}; do + # Generate 6 pseudowords of the form cvccvc where c and v + # denote random consonants and vowels respectively. + local words=() + repeat 6; do + words+=('') + repeat 2; do + for chars in $consonants $vowels $consonants; do + -$0-rand $#chars || return + words[-1]+=$chars[REPLY] + done + done + done + + local pwd=${(j:-:)words} + + # Replace either the first or the last character in one of + # the words with a random digit. + -$0-rand $#digits || return + local digit=$digits[REPLY] + -$0-rand $((2 * $#words)) || return + pwd[REPLY/2*7+2*(REPLY%2)-1]=$digit + + # Convert one lower-case character to upper case. + while true; do + -$0-rand $#pwd || return + [[ $vowels$consonants == *$pwd[REPLY]* ]] && break + done + # NOTE: We aren't using ${(U)c} here because its results are + # locale-dependent. For example, when upper-casing 'i' in Turkish + # locale we would get 'İ', a.k.a. latin capital letter i with dot + # above. We could set LC_CTYPE=C locally but then we would run afoul + # of this zsh bug: https://www.zsh.org/mla/workers/2020/msg00588.html. + local c=$pwd[REPLY] + printf -v c '%o' $((#c - 32)) + printf "%s\\$c%s\\n" "$pwd[1,REPLY-1]" "$pwd[REPLY+1,-1]" || return + done +} always { + unfunction -m -- "-${(b)0}-*" +} } ]]; then + print -ru2 -- "usage: $0 [NUM]" + return 1 +fi + +zmodload zsh/system || return + +{ + local -r chars=abcdefghjkmnpqrstvwxyz0123456789 + local c + repeat ${1-1}; do + repeat 26; do + sysread -s1 c || return + # There is uniform because $#chars divides 256. + print -rn -- $chars[#c%$#chars+1] + done + print + done +} } ]]; 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 +} &2 "$0: \`shuf\` command not found. Install coreutils (\`brew install coreutils\` on macOS)." - return 1 - fi - - if [[ ! -e /usr/share/dict/words ]]; then - echo >&2 "$0: no wordlist found in \`/usr/share/dict/words\`. Install one first." - return 1 - fi - - local -i i num - - [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1 - - # Get all alphabetic words of at most 6 characters in length - local dict=$(LC_ALL=C grep -E '^[a-zA-Z]{1,6}$' /usr/share/dict/words) - - # Calculate the base-2 entropy of each word in $dict - # Entropy is e = L * log2(C), where L is the length of the password (here, - # in words) and C the size of the character set (here, words in $dict). - # Solve for e = 128 bits of entropy. Recall: log2(n) = log(n)/log(2). - local -i n=$((int(ceil(128*log(2)/log(${(w)#dict}))))) - - for i in {1..$num}; do - printf "$n-" - printf "$dict" | shuf -n "$n" | paste -sd '-' - - done -} +autoload -Uz genpass-apple genpass-monkey genpass-xkcd -- cgit v1.2.3-70-g09d2