3 # Usage: genpass-apple [NUM]
5 # Generate a password made of 6 pseudowords of 6 characters each
6 # with the security margin of at least 128 bits.
8 # Example password: xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp
10 # If given a numerical argument, generate that many passwords.
12 emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var
14 if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then
15 print -ru2 -- "usage: $0 [NUM]"
19 zmodload zsh/system zsh/mathfunc || return
22 local -r vowels=aeiouy
23 local -r consonants=bcdfghjklmnpqrstvwxz
24 local -r digits=0123456789
26 # Sets REPLY to a uniformly distributed random number in [1, $1].
27 # Requires: $1 <= 256.
31 sysread -s1 c || return
32 # Avoid bias towards smaller numbers.
33 (( #c < 256 / $1 * $1 )) && break
35 typeset -g REPLY=$((#c % $1 + 1))
41 # Generate 6 pseudowords of the form cvccvc where c and v
42 # denote random consonants and vowels respectively.
47 for chars in $consonants $vowels $consonants; do
48 -$0-rand $#chars || return
49 words[-1]+=$chars[REPLY]
54 local pwd=${(j:-:)words}
56 # Replace either the first or the last character in one of
57 # the words with a random digit.
58 -$0-rand $#digits || return
59 local digit=$digits[REPLY]
60 -$0-rand $((2 * $#words)) || return
61 pwd[REPLY/2*7+2*(REPLY%2)-1]=$digit
63 # Convert one lower-case character to upper case.
65 -$0-rand $#pwd || return
66 [[ $vowels$consonants == *$pwd[REPLY]* ]] && break
68 # NOTE: We aren't using ${(U)c} here because its results are
69 # locale-dependent. For example, when upper-casing 'i' in Turkish
70 # locale we would get 'İ', a.k.a. latin capital letter i with dot
71 # above. We could set LC_CTYPE=C locally but then we would run afoul
72 # of this zsh bug: https://www.zsh.org/mla/workers/2020/msg00588.html.
74 printf -v c '%o' $((#c - 32))
75 printf "%s\\$c%s\\n" "$pwd[1,REPLY-1]" "$pwd[REPLY+1,-1]" || return
78 unfunction -m -- "-${(b)0}-*"