aboutsummaryrefslogtreecommitdiffstats
path: root/makepass.zsh
diff options
context:
space:
mode:
authorDennis Eriksen <d@ennis.no>2023-09-06 15:52:54 +0200
committerDennis Eriksen <d@ennis.no>2023-09-06 15:52:54 +0200
commit10f86a919c10812dccf0db434b9358138fd94303 (patch)
tree0e7f2d0a273d59fd57507299b919df8daba79d45 /makepass.zsh
parentWIP: Cleanup and standardization 2 (diff)
downloadmakepass-10f86a919c10812dccf0db434b9358138fd94303.tar.gz
WIP: Cleanup and standardization - Done?
I think I'm getting there with this round. `makepass.zsh` might not look pretty, but it is beginning to look like something that might be standardized on. Next step is to rewrite some of the other scripts to match the zsh one.
Diffstat (limited to 'makepass.zsh')
-rwxr-xr-xmakepass.zsh236
1 files changed, 148 insertions, 88 deletions
diff --git a/makepass.zsh b/makepass.zsh
index caa8dca..c513086 100755
--- a/makepass.zsh
+++ b/makepass.zsh
@@ -1,20 +1,56 @@
#!/usr/bin/env zsh
-emulate zsh
-# Filename: ~/bin/makepass.zsh
+# Filename: makepass.zsh
# Purpose: Creating random passwords.
# Authors: Dennis Eriksen <d@ennis.no>
# 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 file generates random passwords
################################################################################
# Copyright (c) 2018-2023 Dennis Eriksen • d@ennis.no
+#
+# Globals
+#
+
+# typeset
+# -a = array
+# -i = integer
+# -g = global
+# -r = readonly
+
+typeset -gri MAX_LENGTH=255 # max length of passwords
+typeset -gri RANGE_MAX=42 # max length when using random length
+typeset -gri RANGE_MIN=8 # min length when using random length
+typeset -gri PASS_WORDS=8 # number of words in passphrases
+
+typeset -gr LOWER='abcdefghijklmnopqrstuvwxyz'
+typeset -gr UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+typeset -gr DIGIT='0123456789'
+typeset -gr OTHER='!#$%&/()=?+-_,.;:<>[]{}|@*'
+typeset -gr ALPHA=${LOWER}${UPPER}
+typeset -gr ALNUM=${ALPHA}${DIGIT}
+typeset -gr EVERY=${ALNUM}${OTHER}
+
+typeset -gi LENGTH=${MAKEPASS_LENGTH:-0} # length of passwords. 0 means "random number between RANGE_MIN and RANGE_MAX"
+typeset -gi NUMBER=${MAKEPASS_NUMBER:-10} # number of passwords
+typeset -gi PRINTLEN=${MAKEPASS_PRINTLEN:-0} # print length of passwords
+typeset -g NORMAL=${MAKEPASS_NORMAL:-$ALNUM'-_'}
+typeset -g SPECIAL=${MAKEPASS_SPECIAL:-$EVERY}
+typeset -g WORDLIST=${MAKEPASS_WORDLIST:-/usr/share/dict/words}
+
+typeset -ga WORDS # Array of words from WORDLIST
+typeset -gi COL_WIDTH # Width of columns we will be printing
+typeset -gi COL_NUM # Number of columns to print
+
+
+#
+# Functions
+#
# Help-function
function _makepass_help() {
- local string='NAME
+ print -- $'NAME
makepass - create several random passwords
SYNOPSIS
@@ -24,17 +60,19 @@ SYNOPSIS
By default `makepass` will output passwords from the three following classes:
- - Normal passwords - 10 random strings with letters (both lower and upper
+ - Normal passwords - random strings with letters (both lower and upper
case), numbers, and dashes and underscores.
- - Passwords with special characters - six random strings generated from
- lower and upper case letters, numbers, and the following characters:
- !#$%&/()=?+-_,.;:<>[]{}|\@*
+ - Passwords with special characters - 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
+ - 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 and last letter will always be a letter.
+
DESCRIPTION
makepass has the following options:
@@ -44,6 +82,8 @@ DESCRIPTION
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.
@@ -51,17 +91,22 @@ ENVIRONMENT
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 NUM overrides that again. So
- `MAKEPASS_LENGTH=10 makepass -l 12 14` will give passwords that are 14
- characters long.
+ 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 from each group to output. This formula is
- used:
- 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.
+ 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.
@@ -72,7 +117,7 @@ ENVIRONMENT
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
@@ -83,24 +128,23 @@ 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
+ a random 32bit integer generated from /dev/random. This should provide
enough randomnes to generate sufficiently secure passwords.
AUTHOR
Dennis Eriksen <https://dnns.no>'
- print -- $string
return 0
}
# Create random passphrase
function passphrase() {
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))]'-'
+ repeat $PASS_WORDS prestring+=$WORDS[$((RANDOM % $#WORDS + 1))]'-'
prestring=$prestring[1,-2] # remove trailing dash
# This while-loop removes any characters NOT in '[^0-9a-zA-Z_-]'
@@ -119,22 +163,41 @@ function passphrase() {
# Function to create random strings
function randstring() {
- # Default is a number in the range <RANGE_MIN-RANGE_MAX>
- local -i len=$(( $1 ? $1 : RANDOM % (RANGE_MAX - RANGE_MIN + 1) + RANGE_MIN ))
- local chars=${2:-$ALNUM}
- local flc=${3:-} # first-last-character
+ local chars=${1:-$NORMAL}
local string
+ local -i len=$(( LENGTH \
+ ? LENGTH \
+ : RANDOM % (RANGE_MAX - RANGE_MIN + 1) + RANGE_MIN ))
+
repeat $len string+=$chars[$((RANDOM % $#chars + 1))]
- # If a third value is provided, it is used as the first and last character in
- # the string
- # TODO: Maybe this needs to be redone? Do it like in the perl-version?
- if [[ -n $flc ]]; then
- string=$flc[$((RANDOM % $#flc + 1))]$string[2,-2]
- (( len >= 2 )) && string+=$flc[$((RANDOM % $#flc + 1))]
+ if ((len >= 2)); then
+ # add new character to start of line and remove 2 from old string
+ string=$ALPHA[$((RANDOM % $#ALPHA + 1))]$string[2,-2]
+ # add new character to end of string
+ string+=$ALPHA[$((RANDOM % $#ALPHA + 1))]
fi
- printf '%s\n' "$string"; return
+ print -- $string; return
+}
+
+# Function to print passwords in neat columns
+function print_columns() {
+ local title=$1
+ local num=$2
+ local chars=$3
+ local -i i=0
+
+ local strings=($(repeat $num { randstring $chars; : $RANDOM }))
+
+ print -- "$title"
+
+ for s in $strings; do
+ let i++
+ (( PRINTLEN )) && s="$#s $s"
+ printf "%-${COL_WIDTH}s" $s;
+ ((i % COL_NUM == 0 || (i == num && i % COL_NUM > 0))) && print
+ done
}
# Function to die
@@ -148,43 +211,19 @@ function die() {
function main() {
setopt localoptions
- integer MAX_LENGTH=255 # max length of passwords
- integer RANGE_MAX=42 # max length when using random length
- integer RANGE_MIN=8 # min length when using random length
- integer PASS_WORDS=8 # number of words in passphrases
- readonly MAX_LENGTH RANGE_MAX RANGE_MIN PASS_WORDS
-
- readonly LOWER='abcdefghijklmnopqrstuvwxyz'
- readonly UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- readonly DIGIT='0123456789'
- readonly OTHER='!#$%&/()=?+-_,.;:<>[]{}|\@*'
- readonly ALPHA=${LOWER}${UPPER}
- readonly ALNUM=${ALPHA}${DIGIT}
- readonly EVERY=${ALNUM}${OTHER}
-
- integer length=${MAKEPASS_LENGTH:-0} # length of passwords
- integer number=${MAKEPASS_NUMBER:-10} # number of passwords
- 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 ))
-
# Getopts
- while getopts 'hl:n:' opt; do
+ while getopts 'hl:n:p' opt; do
case $opt in
h)
_makepass_help && return 0;;
l)
- [[ ! $OPTARG = <0-255> ]] && die "-l takes a number between 0 and 255"
- length=$OPTARG;;
+ [[ $OPTARG = <0-255> ]] || die "-l takes a number between 0 and 255"
+ LENGTH=$OPTARG;;
n)
- [[ ! $OPTARG = <1-255> ]] && die "-n takes a number between 1 and 255"
- number=$OPTARG;;
+ [[ $OPTARG = <1-255> ]] || die "-n takes a number between 1 and 255"
+ NUMBER=$OPTARG;;
+ p)
+ PRINTLEN=1;;
*)
die "Unknown argument";;
esac
@@ -199,56 +238,77 @@ function main() {
# We only take one argument in addition to the optargs
(( ARGC > 1 )) && die "only one argument"
- # if there is an argument, set it to $length
- [[ -n $1 ]] && length=$1
+ # if there is an argument, set it to $LENGTH
+ [[ -n $1 ]] && LENGTH=$1
- # Check $length and $number
- [[ $length = <0-255> ]] || die "length must be a number between 0 and 255"
- [[ $number = <1-255> ]] || die "number-argument must be between 1 and 255"
+ # Check $LENGTH and $NUMBER
+ [[ $LENGTH = <0-255> ]] || die "length must be a number between 0 and 255"
+ [[ $NUMBER = <1-255> ]] || die "number-argument must be between 1 and 255"
+
+
+ #
+ # Some other work
+ #
+
+ # Seed $RANDOM with a random 32bit integer from /dev/random
+ local r4 # will be filled with 4 random bytes from /dev/random
+ IFS= read -rk4 -u0 r4 < /dev/random || return
+ local b1=$r4[1] b2=$r4[2] b3=$r4[3] b4=$r4[4]
+ RANDOM=$(( #b1 << 24 | #b2 << 16 | #b3 << 8 | #b4 ))
+
+ # We zero-pad printlength, so we need three extra chars if LENGTH < 100 (four
+ # if LENGTH >= 100). Two for printlength, one for the space.
+ (( PRINTLEN )) && { ((LENGTH < 100)) && PRINTLEN=3 || PRINTLEN=4 }
+
+ # Calculate width of columns and number of columns to use
+ # add two for spacing between columns
+ COL_WIDTH=$(( ( LENGTH ? LENGTH : RANGE_MAX ) + PRINTLEN + 2 ))
+ # $COLUMNS is a builtin variable for width of terminal
+ COL_NUM=$(( COLUMNS / COL_WIDTH ))
+ # Just in case COL_NUM=0 because of a small terminal or something
+ (( COL_NUM )) || COL_NUM=1
#
# Print!
#
- # Normal passwords
- print "Normal passwords:"
- print -c -- $(repeat $number { randstring $length $normal; : $RANDOM })
- : $RANDOM
- print
+ print_columns "Passwords with special characters" $NUMBER $NORMAL
+
+ print; : $RANDOM # Just to make sure we get a fresh number
- # Passowrds with special characters
- print "Passwords with special characters:"
- print -c -- $(repeat $((number/3*2+1)) { randstring $length $special $ALPHA; : $RANDOM })
- : $RANDOM
+ print_columns "Passwords with special characters" $((NUMBER/3*2+1)) $SPECIAL
# Passphrases - but only if a wordlist is available
- if [[ -r $wordlist ]] && ((number / 2 > 0)); then
- print
+ if [[ -r $WORDLIST ]] && ((NUMBER / 2 > 0)); then
+ print; : $RANDOM
print "Passphrases:"
- local -a words=(${(f)"$(<$wordlist)"})
- repeat $((number / 2)) passphrase
+ # Read wordlist into array
+ WORDS=(${(f)"$(<$WORDLIST)"})
+ # Run passphrase-function to output passphrases
+ repeat $((NUMBER / 2)) passphrase
fi
}
+# Run main function! GOGOGO!
main "${@:-}"
# Last time I redid this script I benchmarked some ways to generate random
# strings. Here's the results:
# subshell with tr | fold | head
-# % time (repeat 10000 { string=''; string=$(tr -cd '[:alpha:]' </dev/urandom | fold -w 20 | head -n1 ) } )
+# % time (repeat 10000 { string=''; string=$(tr -cd '[:alpha:]' </dev/random | 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 ) } )
+# % time (repeat 10000 { string=''; string=$(head -n100 /dev/random | 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]} } )
+# % time (repeat 10000 { string=''; string=${${:-"$(sysread -i 1 </dev/random | 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
+# string-addition with randint32()-function to get random 32bit integer from /dev/random
# % time (repeat 10000 { string='';repeat 20 string+=$ALPHA[$((randint32() % $#ALPHA + 1))] })
# 8.17s user 3.28s system 99% cpu 11.475 total
#