Improve your password generation script
I created a small password generation script. I'm wondering what improvements could be made to it other than handling input errors, usage information, etc. This is the main functionality that I am interested in improving.
This is what it does (and what I like):
- Be sure to keep the lowercase characters (L), uppercase characters (U), numbers (N), and characters (S) used in passwords.
- I would like me to find a new password for legnth 10 for me in no more than two seconds.
- The variable length of the password string must be used as an argument.
- Only a password containing at least one L, U, N and S should be accepted.
Here is the code:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
S="\-/\\)?=+.%#"
until [ $(echo $password | grep [$L] | grep [$U] | grep [$N] | grep -c [$S] ) == 1 ]; do
password=$(cat $RNDSOURCE | tr -cd "$L$U$N$S" | head -c $PASSWORDLENGTH)
echo In progress: $password # It simply for debug purposes, ignore it
done
echo Final password: $password
My questions:
- Is there a better way to check if a password is acceptable than the way I do it?
- How about generating a password?
- Any improvements to the coding style? (Short variable names are temporary. Although I use uppercase names for "constant" [I know not formally] and lowercase variables for variables. Do you like that?)
Vote for the most improved version. :-)
This was just an exercise for me, mostly for fun and as a learning experience, although I will start using it instead of the KeepassX generation I am using now. It will be interesting to see what improvements and suggestions the more experienced Bashistas bring (I made that word).
I created a little basic script to measure performance: (If anyone finds this funny)
#!/bin/bash
SAMPLES=100
SCALE=3
echo -e "PL\tMax\tMin\tAvg"
for p in $(seq 4 50); do
bcstr=""; max=-98765; min=98765
for s in $(seq 1 $SAMPLES); do
gt=$(\time -f %e ./genpassw.sh $p 2>&1 1>/dev/null)
bcstr="$gt + $bcstr"
max=$(echo "if($max < $gt ) $gt else $max" | bc)
min=$(echo "if($min > $gt ) $gt else $min" | bc)
done
bcstr="scale=$SCALE;($bcstr 0)/$SAMPLES"
avg=$(echo $bcstr | bc)
echo -e "$p\t$max\t$min\t$avg"
done
You are throwing away a bunch of randomness in your input stream. Save these bytes and convert them to your character set. Replace the password = ... operator in your loop as follows:
ALL="$L$U$N$S"
password=$(tr "\000-\377" "$ALL$ALL$ALL$ALL$ALL" < $RNDSOURCE | head -c $PASSWORDLENGTH)
Repeating $ ALL means the "map to" setting is> = 255 characters.
I also removed the gratuitous use of cat.
(Edited to clarify that what is shown above is not intended to replace the complete script, just the inner loop.)
Edit: Here's a much faster strategy that isn't called by external programs:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
# (Use this with tr.)
#S='\-/\\)?=+.%#'
# (Use this for bash.)
S='-/\)?=+.%#'
ALL="$L$U$N$S"
# This function echoes a random index into it argument.
function rndindex() { echo $(($RANDOM % ${#1})); }
# Make sure the password contains at least one of each class.
password="${L:$(rndindex $L):1}${U:$(rndindex $U):1}${N:$(rndindex $N):1}${S:$(rndindex $S):1}"
# Add random other characters to the password until it is the desired length.
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
password=$password${ALL:$(rndindex $ALL):1}
done
# Now shuffle it.
chars=$password
password=""
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
n=$(rndindex $chars)
ch=${chars:$n:1}
password="$password$ch"
if [[ $n == $(( ${#chars} - 1 )) ]]; then
chars="${chars:0:$n}"
elif [[ $n == 0 ]]; then
chars="${chars:1}"
else
chars="${chars:0:$n}${chars:$((n+1))}"
fi
done
echo $password
Timing tests show this is 5 to 20 times faster than the original script and the timing is more predictable from one run to the next.
source to share
secpwgen
very good (it can also generate easier-to-remember passwords diceware
), but has almost disappeared from the net. I managed to find a copy 1.3 source
and put it on github .
Currently part of Alpine Linux .
source to share