9d6b3984f92cf7f4411b40dfb5a0897b260ae368

Author
Aaron Toponce <aaron.toponce@gmail.com>
Committer
GitHub <noreply@github.com>
Date

Message

feat(plugins): add genpass plugin with 3 distinct password generators (#9502)

Diff

  1diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
  2index 8e175d5495a2bb799ed14e0f23d29077fb4cf7b2..b091f5d89fa8dec22bc41fc1a79226c123acbc6d 100644
  3--- a/.github/CODEOWNERS
  4+++ b/.github/CODEOWNERS
  5@@ -1,5 +1,6 @@
  6 # Plugin owners
  7 plugins/aws/                        @maksyms
  8+plugins/genpass/                    @atoponce
  9 plugins/git-lfs/                    @vietduc01100001
 10 plugins/gitfast/                    @felipec
 11 plugins/sdk/                        @rgoldberg
 12diff --git a/plugins/genpass/README.md b/plugins/genpass/README.md
 13new file mode 100644
 14index 0000000000000000000000000000000000000000..e6e7a513825f6c0802024a1de5a819928e6df073
 15--- /dev/null
 16+++ b/plugins/genpass/README.md
 17@@ -0,0 +1,65 @@
 18+# genpass
 19+
 20+This plugin provides three unique password generators for ZSH. Each generator
 21+has at least a 128-bit security margin and generates passwords from the
 22+cryptographically secure `/dev/urandom`. Each generator can also take an
 23+optional numeric argument to generate multiple passwords.
 24+
 25+Requirements:
 26+
 27+* `grep(1)`
 28+* GNU coreutils (or appropriate for your system)
 29+* Word list providing `/usr/share/dict/words`
 30+
 31+To use it, add `genpass` to the plugins array in your zshrc file:
 32+
 33+    plugins=(... genpass)
 34+
 35+## genpass-apple
 36+
 37+Generates a pronounceable pseudoword passphrase of the "cvccvc" consonant/vowel
 38+syntax, inspired by [Apple's iCloud Keychain password generator][1]. Each
 39+pseudoword has exactly 1 digit placed at the edge of a "word" and exactly 1
 40+capital letter to satisfy most password security requirements.
 41+
 42+    % genpass-apple
 43+    gelcyv-foqtam-fotqoh-viMleb-lexduv-6ixfuk
 44+
 45+    % genpass-apple 3
 46+    japvyz-qyjti4-kajrod-nubxaW-hukkan-dijcaf
 47+    vydpig-fucnul-3ukpog-voggom-zygNad-jepgad
 48+    zocmez-byznis-hegTaj-jecdyq-qiqmiq-5enwom
 49+
 50+[1]: https://developer.apple.com/password-rules/
 51+
 52+## genpass-monkey
 53+
 54+Generates visually unambiguous random meaningless strings using [Crockford's
 55+base32][2].
 56+
 57+    % genpass-monkey
 58+    xt7gn976e7jj3fstgpy27330x3
 59+
 60+    % genpass-monkey 3
 61+    n1qqwtzgejwgqve9yzf2gxvx4m
 62+    r2n3f5s6vbqs2yx7xjnmahqewy
 63+    296w9y9rts3p5r9yay0raek8e5
 64+
 65+[2]: https://www.crockford.com/base32.html
 66+
 67+## genpass-xkcd
 68+
 69+Generates passphrases from `/usr/share/dict/words` inspired by the [famous (and
 70+slightly misleading) XKCD comic][3]. Each passphrase is prepended with a digit
 71+showing the number of words in the passphrase to adhere to password security
 72+requirements that require digits. Each word is 6 characters or less.
 73+
 74+    % genpass-xkcd
 75+    9-eaten-Slav-rife-aired-hill-cordon-splits-welsh-napes
 76+
 77+    % genpass-xkcd 3
 78+    9-worker-Vlad-horde-shrubs-smite-thwart-paw-alters-prawns
 79+    9-tutors-stink-rhythm-junk-snappy-hooray-barbs-mewl-clomp
 80+    9-vital-escape-Angkor-Huff-wet-Mayra-abbés-putts-guzzle
 81+
 82+[3]: https://xkcd.com/936/
 83diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh
 84new file mode 100644
 85index 0000000000000000000000000000000000000000..15bfebda8f078e0801ee26ab82fadcdecace4355
 86--- /dev/null
 87+++ b/plugins/genpass/genpass.plugin.zsh
 88@@ -0,0 +1,95 @@
 89+autoload -U regexp-replace
 90+zmodload zsh/mathfunc
 91+
 92+genpass-apple() {
 93+  # Generates a 128-bit password of 6 pseudowords of 6 characters each
 94+  # EG, xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp
 95+  # Can take a numerical argument for generating extra passwords
 96+  local -i i j num
 97+
 98+  [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1
 99+
100+  local consonants="$(LC_ALL=C tr -cd b-df-hj-np-tv-xz < /dev/urandom \
101+    | head -c $((24*$num)))"
102+  local vowels="$(LC_ALL=C tr -cd aeiouy < /dev/urandom | head -c $((12*$num)))"
103+  local digits="$(LC_ALL=C tr -cd 0-9 < /dev/urandom | head -c $num)"
104+
105+  # The digit is placed on a pseudoword edge using $base36. IE, Dvccvc or cvccvD
106+  local position="$(LC_ALL=C tr -cd 056bchinotuz < /dev/urandom | head -c $num)"
107+  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 \
108+    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 \
109+    t 29 u 30 v 31 w 32 x 33 y 34 z 35)
110+
111+  for i in {1..$num}; do
112+    local pseudo=""
113+
114+    for j in {1..12}; do
115+      # Uniformly iterate through $consonants and $vowels for each $i and $j
116+      # Creates cvccvccvccvccvccvccvccvccvccvccvccvc for each $num
117+      pseudo="${pseudo}${consonants:$((24*$i+2*${j}-26)):1}"
118+      pseudo="${pseudo}${vowels:$((12*$i+${j}-13)):1}"
119+      pseudo="${pseudo}${consonants:$((24*$i+2*${j}-25)):1}"
120+    done
121+
122+    local -i digit_pos=${base36[${position[$i]}]}
123+    local -i char_pos=$digit_pos
124+
125+    # The digit and uppercase character must be in different locations
126+    while [[ $digit_pos == $char_pos ]]; do
127+      char_pos=$base36[$(LC_ALL=C tr -cd 0-9a-z < /dev/urandom | head -c 1)]
128+    done
129+
130+    # Places the digit on a pseudoword edge
131+    regexp-replace pseudo "^(.{$digit_pos}).(.*)$" \
132+      '${match[1]}${digits[$i]}${match[2]}'
133+
134+    # Uppercase a random character (that is not a digit)
135+    regexp-replace pseudo "^(.{$char_pos})(.)(.*)$" \
136+      '${match[1]}${(U)match[2]}${match[3]}'
137+
138+    # Hyphenate each 6-character pseudoword
139+    regexp-replace pseudo '^(.{6})(.{6})(.{6})(.{6})(.{6})(.{6})$' \
140+      '${match[1]}-${match[2]}-${match[3]}-${match[4]}-${match[5]}-${match[6]}'
141+
142+    printf "${pseudo}\n"
143+  done
144+}
145+
146+genpass-monkey() {
147+  # Generates a 128-bit base32 password as if monkeys banged the keyboard
148+  # EG, nz5ej2kypkvcw0rn5cvhs6qxtm
149+  # Can take a numerical argument for generating extra passwords
150+  local -i i num
151+
152+  [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1
153+
154+  local pass=$(LC_ALL=C tr -cd '0-9a-hjkmnp-tv-z' < /dev/urandom \
155+    | head -c $((26*$num)))
156+
157+  for i in {1..$num}; do
158+    printf "${pass:$((26*($i-1))):26}\n"
159+  done
160+}
161+
162+genpass-xkcd() {
163+  # Generates a 128-bit XKCD-style passphrase
164+  # EG, 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster
165+  # Can take a numerical argument for generating extra passwords
166+  local -i i num
167+
168+  [[ $1 =~ '^[0-9]+$' ]] && num=$1 || num=1
169+
170+  # Get all alphabetic words of at most 6 characters in length
171+  local dict=$(grep -E '^[a-zA-Z]{,6}$' /usr/share/dict/words)
172+
173+  # Calculate the base-2 entropy of each word in $dict
174+  # Entropy is e = L * log2(C), where L is the length of the password (here,
175+  # in words) and C the size of the character set (here, words in $dict).
176+  # Solve for e = 128 bits of entropy. Recall: log2(n) = log(n)/log(2).
177+  local -i n=$((int(ceil(128*log(2)/log(${(w)#dict})))))
178+
179+  for i in {1..$num}; do
180+    printf "$n-"
181+    printf "$dict" | shuf -n "$n" | paste -sd '-'
182+  done
183+}