#!/usr/bin/env zsh emulate zsh # Filename: ~/bin/makepass.zsh # Purpose: Creating random passwords. # Authors: Dennis Eriksen # Bug-Reports: Email # 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 ' 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:]'