1
0
mirror of https://github.com/ohmyzsh/ohmyzsh.git synced 2024-12-11 23:53:43 +08:00
ohmyzsh/plugins/genpass/genpass-apple

80 lines
2.3 KiB
Plaintext
Raw Normal View History

fix(genpass): improve performance and usability and fix bugs (#9520) *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% |
2020-12-16 23:57:59 +08:00
#!/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}-*"
} </dev/urandom