aboutsummaryrefslogtreecommitdiffstats
path: root/makepass.zsh
diff options
context:
space:
mode:
Diffstat (limited to 'makepass.zsh')
-rwxr-xr-xmakepass.zsh212
1 files changed, 212 insertions, 0 deletions
diff --git a/makepass.zsh b/makepass.zsh
new file mode 100755
index 0000000..319a200
--- /dev/null
+++ b/makepass.zsh
@@ -0,0 +1,212 @@
+#!/usr/bin/env zsh
+emulate zsh
+# Filename: ~/bin/makepass.zsh
+# Purpose: Creating random passwords.
+# Authors: Dennis Eriksen <d@ennis.no>
+# Bug-Reports: Email <idgatt@dnns.no>
+# License: This file is licensed under the GPL v2.
+################################################################################
+# This file takes randomness from /dev/urandom and turns it into random
+# passwords.
+################################################################################
+# Copyright (c) 2018-2022 Dennis Eriksen • d@ennis.no
+
+
+# Help-function
+function _makepass_help() {
+ local string='NAME
+ makepass - create several random passwords
+
+SYNOPSIS
+ makepass [OPTIONS] [NUM]
+
+ If a NUM is provided, the we will output passwords of that length.
+
+ By default `makepass` will output passwords from the three following classes:
+
+ - Normal passwords - 10 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:
+ !#$%&/()=?+-_,.;:<>[]{}|\@*
+
+ - 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
+ changed, but you do not have to use all of them. Use as mane as you want.
+
+DESCRIPTION
+ makepass has the following options:
+
+ -h
+ output this help-text
+
+ENVIRONMENT
+ makepass examines the following environmental variables.
+
+ MAKEPASS_DEFAULT_LENGTH
+ Specifies the default length of passwords. Valid values are "random"
+ (default), or a number. If "random", a random value between 8 and 42
+ will be used for each password. If `makepass` is run with a NUM as an
+ argument, that NUM overrides this variable.
+
+ 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/urandom. This should provide
+ enough randomnes to generate sufficiently secure passwords.
+
+AUTHOR
+ Dennis Eriksen <https://dnns.no>'
+
+ print -- $string
+ 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() {
+ 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 ))
+
+
+ # Some error-checking
+ # We only take one argument
+ (( ARGC > 1 )) && print -u2 -- "only one argument\nmaybe try running \`makepass -h\` for help" && return 1
+ # if $1 == -h
+ [[ $1 == '-h' ]] && _makepass_help && return 0
+ # if $1 is not empty and is not a number
+ [[ -n $1 && ! $1 = <1-> ]] && print -u2 -- "not a number above 0\nmaybe try running \`makepass -h\` for help" && return 1
+
+ # length of passwords
+ local -i len=$(( length > 0 && ! ${1:-0} > 0 ? length : ${1:-0} ))
+
+ # Normal passwords
+ print "Normal passwords:"
+ print -c -- $(repeat 10 { randstring $len $normal; : $RANDOM })
+ : $RANDOM
+ print
+
+ # Passowrds with special characters
+ print "Passwords with special characters:"
+ print -c -- $(repeat 6 { randstring $len $special $alpha; : $RANDOM })
+ : $RANDOM
+
+ # Passphrases - but only if a wordlist is available
+ if [[ -r $wordlist ]]; then
+ print
+ print "Passphrases:"
+ local -a words=(${(f)"$(<"$wordlist")"})
+ repeat 6 randphrase
+ fi
+}
+
+makepass "${@:-}"
+
+# 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 ) } )
+# 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
+
+
+## END OF FILE #################################################################