Last active
September 11, 2024 00:52
-
-
Save atoponce/23fe6fc056c6e95b9e624a6a93f1218e to your computer and use it in GitHub Desktop.
Password generators written in ZSH
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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. | |
# | |
# Released to the public domain. No license. | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zsh | |
# | |
# Usage: genpass-csv [NUM] | |
# | |
# Generate a password made of a comma-separated password of two 11-base64 characters | |
# with the security margin of at least 128 bits. | |
# | |
# Example password: R6bnlXCl7Bd,96+JZ6lxU6a | |
# > "Add commas to your passwords to mess with the CSV file they will be dumped into after being | |
# > breached. Until next time!" ~ Skeletor | |
# | |
# Released to the public domain. No license. | |
emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var | |
# Test if argument is numeric, or return unsuccessfully | |
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 c | |
local chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
local length=$(( ceil(128/log2($#chars)) )) | |
repeat ${1-1}; do | |
local pw="" | |
repeat $length; do | |
sysread -s1 c || return | |
pw+=$chars[#c%$#chars+1] # Uniform as $#chars divides 128 evenly. | |
done | |
print -r -- \""$pw[1,$#pw/2]\",\"$pw[$#pw/2+1,$#pw]\"" | |
done | |
} < /dev/urandom |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zsh | |
# | |
# Usage: genpass-monkey [NUM] | |
# | |
# Generate a password made of 26 alphanumeric characters | |
# with the security margin of at least 128 bits. | |
# | |
# Example password: nz5ej2kypkvcw0rn5cvhs6qxtm | |
# | |
# If given a numerical argument, generate that many passwords. | |
# | |
# Released to the public domain. No license. | |
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 || return | |
{ | |
local -r chars=abcdefghjkmnpqrstvwxyz0123456789 | |
local c | |
repeat ${1-1}; do | |
repeat 26; do | |
sysread -s1 c || return | |
# This is uniform because $#chars evenly divides 128. | |
print -rn -- $chars[#c%$#chars+1] | |
done | |
done | |
} </dev/urandom |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zsh | |
# | |
# Usage: genpass-whitespace [NUM] | |
# | |
# Generate a purely whitespace password with 128 bits of symmetric security. | |
# | |
# Characters are strictly non-control, non-graphical, horizontal spaces/blanks. | |
# Both nonzero- and zero-width characters are used. Two characters are | |
# technically vertical characters, but aren't interpreted as such in the shell. | |
# They are "\u2028" and "\u2029". You might need a font with good Unicode | |
# support to prevent some of these characters creating tofu. | |
# | |
# The password is wrapped in braille pattern blanks for correctly handling | |
# zero-width characters at the edges, to prevent whitespace stripping by the | |
# auth form, and to guarantee a copy-able width should only zero-width | |
# characters be generated. | |
# | |
# Example password: "⠀ ᅠ ᅠ ᅠ ⠀" | |
# | |
# Released to the public domain. No license. | |
emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var | |
# Test if argument is numeric, or return unsuccessfully | |
if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then | |
print -ru2 -- "usage: $0 [NUM]" | |
return 1 | |
fi | |
zmodload zsh/system zsh/mathfunc || return | |
tabs -1 # set tab width to 1 space | |
{ | |
local c | |
local chars=( | |
$'\u0009' # Character tabulation | |
$'\u0020' # Space | |
$'\u00A0' # No-break space | |
$'\u00AD' # Soft hyphen | |
$'\u034F' # Combining grapheme joiner | |
$'\u115F' # Hangul Choseong filler | |
$'\u1160' # Hangul Jungseong filler | |
$'\u180E' # Mongolian vowel separator | |
$'\u2000' # En quad | |
$'\u2001' # Em quad | |
$'\u2002' # En space | |
$'\u2003' # Em space | |
$'\u2004' # Three-per-em space | |
$'\u2005' # Four-per-em space | |
$'\u2006' # Six-per-em space | |
$'\u2007' # Figure space | |
$'\u2008' # Punctuation space | |
$'\u2009' # Thin space | |
$'\u200A' # Hair space | |
$'\u200B' # Zero width space | |
$'\u200C' # Zero width non-joiner | |
$'\u200D' # Zero width joiner | |
$'\u2028' # Line separator | |
$'\u2029' # Paragraph separator | |
$'\u202F' # Narrow no-break space | |
$'\u205F' # Medium mathematical space | |
$'\u2060' # Word joiner | |
$'\u2800' # Braille pattern blank | |
$'\u3000' # Ideographic space | |
$'\u3164' # Hangul filler | |
$'\uFEFF' # Zero width non-breaking space | |
$'\uFFA0' # Halfwdith hangul filler | |
) | |
local length=$(( ceil(128/log2($#chars)) )) | |
repeat ${1-1}; do | |
print -rn -- $'"\u2800' | |
repeat $length; do | |
sysread -s1 c || return | |
print -rn -- $chars[#c%$#chars+1] # Uniform as $#chars divides 128 evenly. | |
done | |
print -r $'\u2800"' | |
done | |
} < /dev/urandom | |
tabs -8 # restore tab width |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zsh | |
# | |
# Usage: genpass-xkcd [NUM] | |
# | |
# Generate a password made of words from /usr/share/dict/words | |
# with the security margin of at least 128 bits. | |
# | |
# Example password: 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster | |
# | |
# If given a numerical argument, generate that many passwords. | |
# | |
# The name of this utility is a reference to https://xkcd.com/936/. | |
# | |
# Released to the public domain. No license. | |
emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var -o extended_glob | |
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 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. / (log($#words) / log(2))))) | |
{ | |
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 | |
done | |
} </dev/urandom |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment