diff options
Diffstat (limited to 'makepass.zsh')
-rwxr-xr-x | makepass.zsh | 212 |
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 ################################################################# |