diff options
-rwxr-xr-x | bin/bin/makepass.sh | 2 | ||||
-rwxr-xr-x | bin/bin/makepass.zsh | 185 |
2 files changed, 119 insertions, 68 deletions
diff --git a/bin/bin/makepass.sh b/bin/bin/makepass.sh index 78e3b75..7db96fd 100755 --- a/bin/bin/makepass.sh +++ b/bin/bin/makepass.sh @@ -105,6 +105,6 @@ _random() ( return 0 ) -makepass "${@:-}" && return 0 +makepass "${@:-}" ## END OF FILE ################################################################# diff --git a/bin/bin/makepass.zsh b/bin/bin/makepass.zsh index 0d56c6a..319a200 100755 --- a/bin/bin/makepass.zsh +++ b/bin/bin/makepass.zsh @@ -9,17 +9,10 @@ emulate zsh # This file takes randomness from /dev/urandom and turns it into random # passwords. ################################################################################ -# -# This script relies on the following external commands, in addition to zsh: -# `head`, `tail`, and `tr`. All three are used in a way which is defined in -# POSIX.1-2017. This script should therefore work on *most* systems with zsh. -# -# head - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/head.html -# tail - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/tail.html -# tr - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/tr.html -# # Copyright (c) 2018-2022 Dennis Eriksen • d@ennis.no + +# Help-function function _makepass_help() { local string='NAME makepass - create several random passwords @@ -34,8 +27,9 @@ SYNOPSIS - Normal passwords - 10 random strings with letters (both lower and upper case), numbers, and dashes and underscores. - - Passwords with special characters - six random strings from the following - range: !#$%&/()=?+-_,.;:<>[]{}|\@*^A-Z-a-z-0-9 + - Passwords with special characters - six random strings generated from + lower and upper case letters, numbers, and the following characters: + !#$%&/()=?+-_,.;:<>[]{}|\@* - Passphrases - if we find a dictionary, five series of eight random words from the dictionary, separated by dashes. The number of words can not be @@ -57,23 +51,27 @@ ENVIRONMENT argument, that NUM overrides this variable. MAKEPASS_NORMAL - Specifies the array of characters from which to generate "normal" - passwords. See below for valid values. Defaults to: _A-Z-a-z-0-9 + String of characters from which to generate "normal" passwords. + Defaults to: + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ MAKEPASS_SPECIAL - Specifies the array of characters from which to generate passwords with - special characters. See below for valid values. Defaults to: - !#$%&/()=?+-_,.;:<>[]{}|\@*^A-Z-a-z-0-9 + String of characters from which to generate passwords with special + characters. Defaults to the same characters as in MAKEPASS_NORMAL, plus + these: + !#$%&/()=?+-_,.;:<>[]{}|\@* MAKEPASS_WORDLIST Specifies the dictionary we find words for passphrases in. If this is unset or empty, we try "/usr/share/dict/words". If that file does not exist, no passphrases will be provided. -ABOUT CHARACTER ARRAYS - makepass uses `tr` to translate and/or delete characters. Therefore, values - that would be valid for `tr` is valid for makepass. This includes - character-classes like [:alnum:] and [:print:]. +NOTES + This scripts makes use of $RANDOM - a builtin in zsh which produces a + pseudo-random integer between 0 and 32767, newly generated each time the + parameter is referenced. We initially seed the random number generator with + a random 32bit integer generated from /dev/urandom. This should provide + enough randomnes to generate sufficiently secure passwords. AUTHOR Dennis Eriksen <https://dnns.no>' @@ -82,15 +80,77 @@ AUTHOR return 0 } + + +# Create random passphrase +function randphrase() { + setopt localoptions rematch_pcre + local -i len=${1:-8} + local prestring string + + # Put together $len random words, separated by '-' + repeat $len prestring+=$words[$((RANDOM % $#words + 1))]'-' + prestring=$prestring[1,-2] # remove trailing dash + + # This while-loop removes any characters NOT in '[^0-9a-zA-Z_-]' + while [[ -n $prestring ]]; do + if [[ $prestring =~ '[^0-9a-zA-Z_-]' ]]; then + string+=${prestring[1,MBEGIN-1]} + prestring=${prestring[MEND+1,-1]} + else + break + fi + done + string+=$prestring # append the rest of $prestring + + printf '%s\n' $string; return +} + + + +# Function to create random strings +function randstring() { + # Default is a number between 8 and 44 + local -i len=$(( ${1:-0} > 0 ? ${1:-0} : RANDOM % (44 - 8 + 1) + 8)) + local chars=${2:-$alnum} + local flc=${3:-} # first-last-character + local string + repeat $len string+=$chars[$((RANDOM % $#chars + 1))] + + # If a third value is provided, it is used as the forst and last character in + # the string + if [[ -n $flc ]]; then + string=$flc[$((RANDOM % $#flc + 1))]$string[2,-2] + (( len >= 2 )) && string+=$flc[$((RANDOM % $#flc + 1))] + fi + + printf '%s\n' "$string"; return +} + + + # makepass-function. This is where the magic happens function makepass() { - MAKEPASS_DEFAULT_LENGTH=${MAKEPASS_DEFAULT_LENGTH:-random} - MAKEPASS_NORMAL=${MAKEPASS_NORMAL:-'_A-Z-a-z-0-9'} - MAKEPASS_SPECIAL=${MAKEPASS_SPECIAL:-'!#$%&/()=?+-_,.;:<>[]{}|\@*^A-Z-a-z-0-9'} - MAKEPASS_WORDLIST=${MAKEPASS_WORDLIST:-/usr/share/dict/words} - RANDOM=$(head -n 10 /dev/urandom | tr -cd '[:digit:]' | tail -c 10) # seed RANDOM + setopt localoptions + local lower='abcdefghijklmnopqrstuvwxyz' + local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ' + local digit='0123456789' + local other='!#$%&/()=?+-_,.;:<>[]{}|\@*' + local alpha=${lower}${upper} + local alnum=${alpha}${digit} + local every=${alnum}${other} + + local length=${MAKEPASS_DEFAULT_LENGTH:-random} + local normal=${MAKEPASS_NORMAL:-$alnum'-_'} + local special=${MAKEPASS_SPECIAL:-$every} + local wordlist=${MAKEPASS_WORDLIST:-/usr/share/dict/words} + + # Seed $RANDOM with a random 32bit integer from /dev/urandom + local r4 # will be filled with 4 random bytes from /dev/urandom + IFS= read -rk4 -u0 r4 < /dev/urandom || return + local b1=$r4[1] b2=$r4[2] b3=$r4[3] b4=$r4[4] + RANDOM=$(( #b1 << 24 | #b2 << 16 | #b3 << 8 | #b4 )) - local -i len # Some error-checking # We only take one argument @@ -98,64 +158,55 @@ function makepass() { # if $1 == -h [[ $1 == '-h' ]] && _makepass_help && return 0 # if $1 is not empty and is not a number - [[ ! -z "$1" && ! $1 = <1-> ]] && print -u2 -- "not a number above 0\nmaybe try running \`makepass -h\` for help" && return 1 + [[ -n $1 && ! $1 = <1-> ]] && print -u2 -- "not a number above 0\nmaybe try running \`makepass -h\` for help" && return 1 - [[ $MAKEPASS_DEFAULT_LENGTH == <0-> && -z $1 ]] && len=$MAKEPASS_DEFAULT_LENGTH || len=${1:-0} + # length of passwords + local -i len=$(( length > 0 && ! ${1:-0} > 0 ? length : ${1:-0} )) # Normal passwords print "Normal passwords:" - print -c -- $(repeat 10 _random $len $MAKEPASS_NORMAL) + print -c -- $(repeat 10 { randstring $len $normal; : $RANDOM }) + : $RANDOM print # Passowrds with special characters print "Passwords with special characters:" - print -c -- $(repeat 6 _random $len $MAKEPASS_SPECIAL true) + print -c -- $(repeat 6 { randstring $len $special $alpha; : $RANDOM }) + : $RANDOM # Passphrases - but only if a wordlist is available - if [[ -r "$MAKEPASS_WORDLIST" ]]; then + if [[ -r $wordlist ]]; then print print "Passphrases:" - local -a words=(${(f)"$(<"$MAKEPASS_WORDLIST")"}) - - for i in {1..5}; do - print "$RANDOM" > /dev/null # The values of RANDOM form an intentionally-repeatable pseudo-random sequence; subshells that reference RANDOM will result in identical pseudo-random values unless the value of RANDOM is referenced or seeded in the parent shell in between subshell invocations. - print $(repeat 8 print "$words[RANDOM % $#words +1]") | tr ' ' '-' | tr -dc '_A-Z-a-z-0-9'; print - done + local -a words=(${(f)"$(<"$wordlist")"}) + repeat 6 randphrase fi } -# Function to create random stuff -function _random() { - setopt local_options - RANDOM=$(head -n 10 /dev/urandom | tr -cd "[:digit:]" | tail -c 8) # seed RANDOM - - # Default is a number between 8 and 44 - local -i len=$1 - (( len == 0 )) && len=$((RANDOM % (44 - 8 + 1) + 8)) - - # Default to [:alnum:] if no chars are specified - local chars=${2:-'[:alnum:]'} +makepass "${@:-}" - # First-Last-Alpha - if you want the first and the last letter to be from [:alpha:] - local fla=${3:-'false'} +# Last time I redid this script I benchmarked some ways to generate random +# strings. Here's the results: - local string - - if [[ $fla == true ]]; then - if (( len <= 2 )); then - string="$(head -n 10 /dev/urandom | tr -cd '[:alpha:]' | tail -c $len)" - else - string="$( head -n 10 /dev/urandom | tr -cd '[:alpha:]' | tail -c 1)" - string+="$(head -n 100 /dev/urandom | tr -cd "$chars" | tail -c $((len-2)))" - string+="$(head -n 10 /dev/urandom | tr -cd '[:alpha:]' | tail -c 1)" - fi - else - string="$(head -n 100 /dev/urandom | tr -cd "$chars" | tail -c $len)" - fi - - printf '%s\n' "$string"; return -} +# subshell with tr | fold | head +# % time (repeat 10000 { string=''; string=$(tr -cd '[:alpha:]' </dev/urandom | fold -w 20 | head -n1 ) } ) +# 45.32s user 22.38s system 201% cpu 33.598 total +# +# subshell with head | tr | tail +# % time (repeat 10000 { string=''; string=$(head -n100 /dev/urandom | tr -cd '[:alpha:]' | tail -c20 ) } ) +# 40.88s user 17.87s system 187% cpu 31.249 total +# +# subshell sysread | tr in brace-expansion instead of head/tail +# % time (repeat 10000 { string=''; string=${${:-"$(sysread -i 1 </dev/urandom | tr -cd '[:alpha:]')"}[1,20]} } ) +# 24.13s user 11.38s system 120% cpu 29.442 total +# +# string-addition with randint32()-function to get random 32bit integer from /dev/urandom +# % time (repeat 10000 { string='';repeat 20 string+=$alpha[$((randint32() % $#alpha + 1))] }) +# 8.17s user 3.28s system 99% cpu 11.475 total +# +# string-addition and $RANDOM +# % time (repeat 10000 { string='';repeat 20 string+=$alpha[$((RANDOM % $#alpha + 1))] } ) +# 0.74s user 0.00s system 99% cpu 0.741 total -makepass "${@:-}" ## END OF FILE ################################################################# |