diff options
author | Dennis Eriksen <d@ennis.no> | 2023-09-07 22:08:01 +0200 |
---|---|---|
committer | Dennis Eriksen <d@ennis.no> | 2023-09-07 22:08:01 +0200 |
commit | 1db76b659c79e4da1e21f200b76a80f55b7e4777 (patch) | |
tree | 233de96f2fa9dd8bf5686c93fb076b897c73c8c5 | |
parent | Redid the perl-version (diff) | |
download | makepass-1db76b659c79e4da1e21f200b76a80f55b7e4777.tar.gz |
Redid makepass.bash
Without any polish...
But makepass.bash is now about 5.5x faster than previously!
-rw-r--r-- | README.md | 28 | ||||
-rwxr-xr-x | makepass.bash | 274 |
2 files changed, 257 insertions, 45 deletions
@@ -153,31 +153,31 @@ Here are the results so far: ``` % hyperfine --time-unit=millisecond --warmup=1 --shell=none ./makepass.* Benchmark 1: ./makepass.bash - Time (mean ± σ): 422.7 ms ± 3.1 ms [User: 95.0 ms, System: 659.0 ms] - Range (min … max): 417.9 ms … 428.3 ms 10 runs + Time (mean ± σ): 75.5 ms ± 2.1 ms [User: 32.1 ms, System: 38.2 ms] + Range (min … max): 71.9 ms … 79.5 ms 39 runs Benchmark 2: ./makepass.go - Time (mean ± σ): 6.9 ms ± 0.2 ms [User: 1.1 ms, System: 4.7 ms] - Range (min … max): 6.3 ms … 8.1 ms 434 runs + Time (mean ± σ): 6.9 ms ± 0.2 ms [User: 1.5 ms, System: 4.1 ms] + Range (min … max): 6.4 ms … 8.2 ms 432 runs Benchmark 3: ./makepass.pl - Time (mean ± σ): 36.2 ms ± 0.4 ms [User: 17.8 ms, System: 16.2 ms] - Range (min … max): 35.6 ms … 37.9 ms 82 runs + Time (mean ± σ): 38.6 ms ± 0.3 ms [User: 16.8 ms, System: 20.8 ms] + Range (min … max): 38.0 ms … 39.4 ms 77 runs Benchmark 4: ./makepass.sh - Time (mean ± σ): 1107.7 ms ± 15.3 ms [User: 276.0 ms, System: 1790.0 ms] - Range (min … max): 1084.9 ms … 1140.3 ms 10 runs + Time (mean ± σ): 1138.7 ms ± 15.4 ms [User: 268.0 ms, System: 1875.0 ms] + Range (min … max): 1114.3 ms … 1157.5 ms 10 runs Benchmark 5: ./makepass.zsh - Time (mean ± σ): 25.3 ms ± 0.7 ms [User: 12.7 ms, System: 10.8 ms] - Range (min … max): 23.6 ms … 27.7 ms 114 runs + Time (mean ± σ): 26.3 ms ± 0.7 ms [User: 12.1 ms, System: 11.6 ms] + Range (min … max): 24.7 ms … 28.3 ms 112 runs Summary './makepass.go' ran - 3.69 ± 0.15 times faster than './makepass.zsh' - 5.28 ± 0.17 times faster than './makepass.pl' - 61.62 ± 1.86 times faster than './makepass.bash' - 161.48 ± 5.22 times faster than './makepass.sh' + 3.80 ± 0.16 times faster than './makepass.zsh' + 5.57 ± 0.19 times faster than './makepass.pl' + 10.89 ± 0.47 times faster than './makepass.bash' + 164.37 ± 5.94 times faster than './makepass.sh' ``` ## Versions diff --git a/makepass.bash b/makepass.bash index c2edf82..16dffa9 100755 --- a/makepass.bash +++ b/makepass.bash @@ -5,52 +5,264 @@ # Bug-Reports: Email <git@dnns.no> # License: This file is licensed under the BSD 3-Clause license. ################################################################################ -# This file takes randomness from /dev/urandom and turns it into random -# passwords. +# This script generates random passwords. ################################################################################ +# Copyright (c) 2018-2023 Dennis Eriksen • d@ennis.no +MAX=255 # max length of passwords +RANGE_MAX=42 # max length when using random length +RANGE_MIN=8 # min length when using random length +PASS_WORDS=8 # number of words in passphrases -# Copyright (c) 2018-2023 Dennis Eriksen • d@ennis.no +LOWER='abcdefghijklmnopqrstuvwxyz' +UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +DIGIT='0123456789' +OTHER='!#$%&/()=?+-_,.;:<>[]{}|@*' +ALPHA=${LOWER}${UPPER} +ALNUM=${ALPHA}${DIGIT} +EVERY=${ALNUM}${OTHER} + +LENGTH=${MAKEPASS_LENGTH:-0} # length of passwords. 0 means "random number between RANGE_MIN and RANGE_MAX" +NUMBER=${MAKEPASS_NUMBER:-10} # number of passwords +PRINTLEN=${MAKEPASS_PRINTLEN:-0} # print length of passwords +NORMAL=${MAKEPASS_NORMAL:-$ALNUM'-_'} +SPECIAL=${MAKEPASS_SPECIAL:-$EVERY} +WORDLIST=${MAKEPASS_WORDLIST:-/usr/share/dict/words} +XWIDTH=$(tput cols) +WORDS=() + +# +# Functions +# # makepass-function -function makepass { - local l=$1 - local words - local first - local last - MAKEPASS_WORDLIST=${MAKEPASS_WORDLIST:-/usr/share/dict/words} - # if $l is not a number, then exit - [[ ! $l =~ ^[0-9]+$ ]] && [[ ! "$l" == "" ]] && echo "not a number" && return 1 - (( l <= 1 || l > 255 )) && echo "Argument must be between 0 and 255" && return 1 - # if $1 is actually empty, set $l to random value for each output - - echo "Normal passwords:" - for _ in {1..10}; do - [ "$1" = "" ] && l=$(shuf -i 8-44 -n 1) - head -n10 /dev/urandom | tr -dc _A-Z-a-z-0-9 | cut -c-"${1:-$l}"; - done | column +function main { + + # Getopts + while getopts 'hl:n:p' opt; do + case $opt in + h) + help && return 0;; + l) + if ! int_in_range "$OPTARG" 0 "$MAX"; then die "-l takes a number between 0 and $MAX"; fi + LENGTH=$OPTARG;; + n) + if ! int_in_range "$OPTARG" 1 "$MAX"; then die "-n takes a number between 1 and $MAX"; fi + NUMBER=$OPTARG;; + p) + PRINTLEN=1;; + *) + die "Unknown argument";; + esac + done + shift $((OPTIND - 1)) + + + # + # Some error-checking + # + + if [[ $# -gt 1 ]]; then die "only one argument"; fi + + if [[ -n $1 ]]; then LENGTH=$1; fi + + # Check $LENGTH and $NUMBER + if ! int_in_range "$LENGTH" 0 "$MAX"; then die "length must be a number between 0 and $MAX"; fi + if ! int_in_range "$NUMBER" 0 "$MAX"; then die "number-argument must be between and $MAX"; fi + + + # + # Some other work + # + + if (( PRINTLEN )); then PRINTLEN=$((LENGTH < 100 ? 2 : 3)); fi + + COL_WIDTH=$(( ( LENGTH ? LENGTH : RANGE_MAX ) + 2 )) + COL_NUM=$(( XWIDTH / ( COL_WIDTH + ( PRINTLEN ? PRINTLEN + 1 : 0 )) )) + + + + # + # Print! + # + + print_columns "Normal passwords" "$NUMBER" "$NORMAL" + echo "" - echo "Passwords with special characters:" - for _ in {1..6}; do - [ "$1" = "" ] && l=$(shuf -i 16-64 -n 1) - first=$(head -n10 /dev/urandom | tr -dc A-Za-z | cut -c-1) - words=$(head -n10 /dev/urandom | tr -dc '!#$%&/()=?+-_,.;:<>[]{}|\@*^A-Z-a-z-0-9' | cut -c-"${1:-$l}") - last=$(head -n10 /dev/urandom | tr -dc A-Za-z | cut -c-1) - echo "${first}${words}${last}" - done | column + print_columns "Passwords with special characters" "$((NUMBER*2/3))" "$SPECIAL" if [ -r "${MAKEPASS_WORDLIST}" ]; then echo "" echo "Passphrases:" - for _ in {1..5}; do - words=$(shuf -n 8 "${MAKEPASS_WORDLIST}" | tr '\n' '-' | tr -dc '_A-Z-a-z-0-9') - echo "${words:0:-1}" + mapfile -t WORDS < "${WORDLIST}" + + for ((i=0; i < (NUMBER / 2); i++)); do + passphrase done; fi + + +} + +# Function to print passwords in neat columns +function print_columns { + local title=$1 + local num=$2 + local chars=$3 + local i + + local strings=() + for ((i=0; i < num; i++)); do + strings+=("$(randstring "$chars")") + done + + echo "${title}:" + + i=0 + for s in "${strings[@]}"; do + ((i++)) + if (( PRINTLEN )); then printf "%0${PRINTLEN}i " "${#s}"; fi + printf "%-${COL_WIDTH}s" "$s" + if ((i % COL_NUM == 0 || (i == num && i % COL_NUM > 0) )); then echo ""; fi + done +} + +# Function to create random strings +function randstring { + local chars=${1:-$NORMAL} + local string + local len=$(( LENGTH ? LENGTH : RANDOM % (RANGE_MAX - RANGE_MIN + 1) + RANGE_MIN )) + + string+="${ALPHA:$((RANDOM % ${#ALPHA})):1}" + while (( ${#string} <= len - 2 )); do string+="${chars:$((RANDOM % ${#chars})):1}"; done + if (( len >= 2 )); then string+="${ALNUM:$((RANDOM % ${#ALNUM})):1}"; fi + + echo "$string" +} + +# PASSPHRASE!! +function passphrase { + local string + local i + for ((i=0; i < PASS_WORDS; i++)); do + string+="${WORDS[$((RANDOM % ${#WORDS[@]}))]}-" + done + string="${string:0:-1}" + + echo "$string" +} + +# Check if int and if in range +function int_in_range { + local num=$1 + local min=$2 + local max=$3 + + if [[ $num =~ ^[0-9]+$ && $min -le $num && $num -le $max ]]; then + return 0 + fi + return 1 +} + +# Function to die +function die() { + echo -- "$@" + echo -- "Maybe try running \`makepass -h\` for help" + exit 1 +} + +# Help-function +function help { + echo $'NAME + makepass - create several random passwords + +SYNOPSIS + makepass [OPTIONS] [NUM] + + If a NUM is provided, passwords will be NUM characters long. + + By default `makepass` will output passwords from the three following classes: + + - Normal passwords - random strings with letters (both lower and upper + case), numbers, and dashes and underscores. + + - Passwords with special characters - random strings generated from lower + and upper case letters, numbers, and the following characters: + !#$%&/()=?+-_,.;:<>[]{}|@* + + - Passphrases - if we find a dictionary, a series of eight random words + from the dictionary, separated by dashes. The number of words can not be + changed, but you do not have to use all of them. Use as mane as you want. + + The first character will always be alphabetic, and the last will always be + alphanumeric. + +DESCRIPTION + makepass has the following options: + + -h + output this help-text + -l + length of passwords. See MAKEPASS_LENGTH below + -n + number of passwords. See MAKEPASS_NUMBER below + -p + print length of number + +ENVIRONMENT + makepass examines the following environmental variables. + + MAKEPASS_LENGTH + Specifies the length of passwords. Valid values are 0-255. If 0, a + random value between 8 and 42 will be used for each password. -l + overrides this environmental variable, and the argument NUM overrides + that again. So `MAKEPASS_LENGTH=10 makepass -l 12 14` will give + passwords that are 14 characters long, even though both -l and + MAKEPASS_LENGTH also specifies a length. + + MAKEPASS_NUMBER + The number of passwords to generate. This formula is used to determine + how many passwords from each group should be generated: + - (n) normal passwords + - (n / 3 * 2 + 1) special passwords + - (n / 2) passphrases + Where n is 10 by default. Valid values for n are 1-255. Floating-poing + math is not used, so results may vary. + + MAKEPASS_PRINTLEN + If 1, print length of all passwords. If 0, don\'t. + + MAKEPASS_NORMAL + String of characters from which to generate "normal" passwords. + Defaults to: + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ + + MAKEPASS_SPECIAL + 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. + +NOTES + This scripts makes use of $RANDOM - a builtin in bash which produces a + pseudo-random integer, newly generated each time the parameter is + referenced. This should provide enough randomnes to generate sufficiently + secure passwords. + +AUTHOR + Dennis Eriksen <https://dnns.no>' + + return 0 } -makepass "${@:-}" +# RUN IT! +main "${@:-}" ## END OF FILE ################################################################# |