aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md111
-rwxr-xr-xmakepass.zsh236
2 files changed, 238 insertions, 109 deletions
diff --git a/README.md b/README.md
index 6258c88..8f9ee2b 100644
--- a/README.md
+++ b/README.md
@@ -22,27 +22,96 @@ Recently I decided I wanted to try to recreate makepass in other languages, and
maybe see if I can make it even faster.
## makepass specifications
-Here's an attempt to specify how `makepass` should work. Might be useful for future versions.
-
-`./makepass` is a symlink that points to whichever of the scripts I think is the best at any given time.
-
-`makepass` should, by default, output:
-- 10 "normal" passwords
- - random characters from the "normal" character set (see below)
-- 6 passwords with special characters
- - first and last random character: alphanumerical
- - the rest are random characters: from the "special" character set
-- 6 passphrases put together from a wordlist the user can define (using
- `/usr/share/dict/words` by default), separated by hyphens
-
-If an (one) argument is supplied on the command line, it should be a number
-between (including) 0 and 255, else an error should be shown. This argument
-defines the length of the random passwords. If no argument is provided, every
-password (not passphrase) should be of a random length between (including) 8
-and 42 character
-
-The passwords should *preferably* be output in columns. The number of columns
-should be dynamic, and dependant on the width of the screen.
+Here's the synopsis from `makepass.zsh`, which explains how makepass should work:
+
+```
+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 and last letter will always be a letter.
+
+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 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/random. This should provide
+ enough randomnes to generate sufficiently secure passwords.
+
+AUTHOR
+ Dennis Eriksen <https://dnns.no>'
+```
+
+Also, the passwords should *preferably* be output in columns. The number of
+columns should be dynamic, and dependant on the width of the screen.
### Character sets
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
#