From 9d6b3984f92cf7f4411b40dfb5a0897b260ae368 Mon Sep 17 00:00:00 2001 From: Aaron Toponce Date: Sat, 12 Dec 2020 04:50:45 -0700 Subject: [PATCH] feat(plugins): add genpass plugin with 3 distinct password generators (#9502) --- .github/CODEOWNERS | 1 + plugins/genpass/README.md | 65 ++++++++++++++++++++ plugins/genpass/genpass.plugin.zsh | 95 ++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 plugins/genpass/README.md create mode 100644 plugins/genpass/genpass.plugin.zsh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8e175d549..b091f5d89 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,6 @@ # Plugin owners plugins/aws/ @maksyms +plugins/genpass/ @atoponce plugins/git-lfs/ @vietduc01100001 plugins/gitfast/ @felipec plugins/sdk/ @rgoldberg 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 +}