How to check password strength [duplicate]
Solution 1:
If you really want to check password strengths, you should use cracklib-check
from the cracklib-runtime
package (should be installed by default):
$ echo foo | cracklib-check
foo: it is WAY too short
$ echo foobar | cracklib-check
foobar: it is based on a dictionary word
$ echo foobared | cracklib-check
foobared: it is based on a dictionary word
$ echo foobared123 | cracklib-check
foobared123: it is too simplistic/systematic
$ echo '!'foobared123 | cracklib-check
!foobared123: OK
Solution 2:
Superficial problems
There are several problems with your script. It breaks if the password contains a number of special characters. Try entering input such as:
a space
two spaces
a * star ← try this one in different directories
bbbbbbbbbb ← try this one in a directory containing a file called a
endswithabackslash\
Read Why does my shell script choke on whitespace or other special characters?. All of it. Do not write any shell scripts that are remotely related to security until you understand all of it.
Oh, and [:alnum:]
works perfectly. You probably intended to write if [ -z …
or if ! [ -n …
instead of if ! [ -z …
.
There's no such thing as the strength of a password
The idea of “password strength” is a myth. It's a myth that's spread by a lot of websites, but it's still a myth. There is no such thing as the strength of a password, there is only the strength of a password generation process.
Having special characters in a password does not make it stronger. A password is a compromise between ease of memorizing and ease of cracking, and special characters make passwords significantly harder to memorize but not significantly harder to crack, as analyzed in this thread on Security Stack Exchange (the short story, the math, some complements — exercise: in this wrong answer, which parts completely ignore the facts?). The idea that special characters make a password stronger is based on the assumption that people who write password crackers are idiots. Guess what: they aren't. There's money to be made by cracking passwords, so you can bet there are people who invest in doing it well.
So how should I choose passwords then?
Randomly. If your method to choose a password doesn't include a source of randomness (with a computer, or rolling a dice if you like it old-school), it's no good.
Diceware is a popular choice, but any method that follows the XKCD pattern — pick multiple “words” at random from some dictionary — is good.
A correct script
#!/bin/sh
echo -n "Enter a password : "
IFS= read -r password
LEN=${#password}
if [ "$LEN" -lt 10 ]; then
printf "%s is smaller than 10 characters\n" "$password"
fi
if [ -z "$(printf %s "$password" | tr -d "[:alnum:]")" ]; then
printf "%s only contains ASCII letters and digits\n" "$password"
else
printf "%s contains characters other than ASCII letters and digits\n" "$password"
fi
Using tr
in this way overcomplicates things. The shell is perfectly able to check whether a string contains characters among a certain set.
#!/bin/sh
echo -n "Enter a password : "
IFS= read -r password
LEN=${#password}
if [ "$LEN" -lt 10 ]; then
printf "%s is smaller than 10 characters\n" "$password"
fi
case "$password" in
*[![:alnum:]]*)
printf "%s contains characters other than ASCII letters and digits\n" "$password";;
*)
printf "%s only contains ASCII letters and digits\n" "$password";;
esac
(Note that the statement about ASCII letters and digits is true for Ubuntu's /bin/sh
, but in bash [:alnum:]
includes all letters and digits in the current locale, not just the ASCII ones.)
Solution 3:
Smells awfully like an XY problem to me. Never, ever, write your own tools for working with passwords. At least you aren't dealing with password storage here, but the first thing that comes to mind with your code is that I could enter an all-numeric password and have it considered "strong" (when in reality it would be a lot weaker than an all-alphabetic password). If you continue with this approach to security/password-handling, you're going to fall down sooner or later and it won't be pretty when it happens.
The correct solution is to use an external library or helper application to determine password strength (and perform other password-related tasks). Most Linux systems these days have PAM that can perform all authentication-related tasks for you in a secure manner (as a bonus you get support for other authentication methods besides passwords, depending on how the user's system is configured) and muru has already suggested a helper application that determines password strength.
Solution 4:
td -d "[:alnum:]"
works for me:
$> echo '123§45!67M89(0' | tr -d "[:alnum:]"
§!(
You can use it like
#!/bin/bash
echo -n "Enter a password : "
read password
LEN=$(echo ${#password})
if [ $LEN -lt 10 ]; then
echo "$password is smaller than 10 characters"
else
if [ $(echo $password | tr -d "[:alnum:]" | wc -w) -eq 0 ]; then
echo "$password is a weak password"
else
echo "$password is a strong password"
fi
fi echo
wc -w
counts the words.
So if you remove all
alnum
and the word count is0
=> true => weak passwordif the word count is
1
(or more if spaces are used) => false => strong password
Solution 5:
If you want a good method to create passwords
See
Password generator combining actual words
"Strong Passwords" or "Strength of a password generation process"
Your question: How to check password strength?
How do I enforce a password complexity policy?
You can only check certain aspects of the password or use a specific tool for each method to create the password or a general brute force method (if the attacker does not know the method).
Shellscript pwdcheck
using cracklib-check
#!/bin/bash
# setting variables
usage="Use 4 words chosen randomly, see this link:
https://security.stackexchange.com/questions/6095/xkcd-936-short-complex-password-or-long-dictionary-passphrase"
minlen=20 # can be modified here
short="is shorter than $minlen characters"
goodmix="is long enough"
badmix="is too short
$usage"
separator="-------"
# checking parameter
if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ $# -gt 1 ]; then
echo "${0##*/} uses 'cracklib-check'"
echo "----------------------------------------------------------------"
echo "Usage: $0 CandidateContaining4DifferentWords"
echo "Example: $0 At-least-$minlen-char"
echo " $0 'Should.be.selected.via.*random*.process'"
echo " $0 'Single-quote-for-1-special-character!'"
echo " $0 'FindPatternByDigitalTest123'"
echo " $0 'Provoke1pattern2search3by4separating5words'"
echo "$usage"
exit
elif [ $# -eq 0 ]; then
echo "$usage"
echo "----------------------------------------------------------------"
read -p "Enter a password : " password
elif [ $# -eq 1 ]; then
password="$1"
fi
# checking and installing if necessary
which cracklib-check > /dev/null
if [ $? -eq 1 ]; then
read -p "Do you want to install 'cracklib-runtime' to get 'cracklib-check'? (y/N) " ans
if [ "$ans" == "y" ]; then
sudo apt-get update && sudo apt-get install cracklib-runtime
fi
fi
if [ ${#password} -lt $minlen ]; then
result="$short"
else
result="$goodmix"
case "$password" in
*[![:alnum:]]*)
alnm="'$password' contains characters other than ASCII letters and digits";;
# result="$badmix";;
*)
alnm="$password contains only ASCII letters and digits";;
esac
fi
echo "Test 1 - size&mix: '$password' $result"
test ${#password} -lt $minlen || echo "$alnm"
if [ "$result" == "$badmix" ] || [ "$result" == "$short" ]; then
total="is bad"
else
total='is good'
fi
echo "$separator"
echo "Test 2 - lexicon: '$password'"
sed -e 's/[0-9]/123\n/g' -e 's/$//' -e 's/[§!@£$€#¤%/()=?*,;.:_-~ ]/123\n/g' -e 's/$/123/g' \
<<< "$password" | LANG=C cracklib-check |sed 's/123: /: /'| \
grep 'it is based on a dictionary word'
if [ $? -ne 0 ]; then
echo 'no comment'
fi
echo "$separator"
echo "Test 3 - digital: '$password'"
sed -e 's/[[:alpha:]]//g' -e 's/[§!@£$€#¤%/()=?*,;.:_-~ ]//g' -e 's/$/xyz/' \
<<< "$password" | LANG=C cracklib-check |sed 's/xyz: /: /'| \
grep 'it is too simplistic/systematic'
if [ $? -eq 0 ]; then
total='is bad'
else
echo 'is good'
fi
echo "$separator"
echo "Test 4 - cracklib-check: '$password'"
LANG=C cracklib-check <<< "$password" | tee /dev/stderr | grep ': OK' > /dev/null
if [ $? -eq 0 ]; then
echo='is good'
else
total='is bad'
fi
if [ "$total" == "is good" ]; then
echo "$separator"
ans=
while [ "$ans" != "g" ] && [ "$ans" != "b" ]
do
read -p "Test 5 - manual: Is '$password' a good or bad password? (g/b) " ans
if [ "$ans" == "g" ]; then
echo 'is good'
elif [ "$ans" == "b" ]; then
total='is bad'
echo "$total"
fi
done
fi
echo "$separator"
if [ "$total" == "is good" ]; then
echo "Every test result for '$password' $total: No weakness found :-)"
else
echo "Some test result for '$password' $total: Some weakness found :-("
fi
Help text
Running in the current directory, a test directory. where you have the shellscript file,
$ ./pwdcheck -h
pwdcheck uses 'cracklib-check'
----------------------------------------------------------------
Usage: ./pwdcheck CandidateContaining4DifferentWords
Example: ./pwdcheck At-least-20-char
./pwdcheck 'Should.be.selected.via.*random*.process'
./pwdcheck 'Single-quote-for-1-special-character!'
./pwdcheck 'FindPatternByDigitalTest123'
./pwdcheck 'Provoke1pattern2search3by4separating5words'
Use 4 words chosen randomly, see this link:
https://security.stackexchange.com/questions/6095/xkcd-936-short-complex-password-or-long-dictionary-passphrase
The cracklib program package
$ apt-cache policy cracklib-runtime
cracklib-runtime:
Installerad: 2.9.2-1ubuntu1
Kandidat: 2.9.2-1ubuntu1
Versionstabell:
*** 2.9.2-1ubuntu1 500
500 http://se.archive.ubuntu.com/ubuntu xenial-updates/main i386 Packages
100 /var/lib/dpkg/status
2.9.2-1build2 500
500 http://se.archive.ubuntu.com/ubuntu xenial/main i386 Packages
Testing the different examples from the help text
Manual test important but use wisely
Your manual inspection and 'test' may be important to avoid really bad passwords, but if you used an automatic random method with a good reputation, you should rely on it and avoid tampering with the result, because you will probably make the password easier to crack.
$ ./pwdcheck CandidateContaining4DifferentWords
Test 1 - size&mix: 'CandidateContaining4DifferentWords' is long enough
CandidateContaining4DifferentWords contains only ASCII letters and digits
-------
Test 2 - lexicon: 'CandidateContaining4DifferentWords'
no comment
-------
Test 3 - digital: 'CandidateContaining4DifferentWords'
is good
-------
Test 4 - cracklib-check: 'CandidateContaining4DifferentWords'
CandidateContaining4DifferentWords: OK
-------
Test 5 - manual: Is 'CandidateContaining4DifferentWords' a good or bad password? (g/b) b
is bad
-------
Some test result for 'CandidateContaining4DifferentWords' is bad: Some weakness found :-(
# comment: This password is published here!
##### Short password #####
$ ./pwdcheck At-least-20-char
Test 1 - size&mix: 'At-least-20-char' is shorter than 20 characters
-------
Test 2 - lexicon: 'At-least-20-char'
least: it is based on a dictionary word
char: it is based on a dictionary word
-------
Test 3 - digital: 'At-least-20-char'
is good
-------
Test 4 - cracklib-check: 'At-least-20-char'
At-least-20-char: OK
-------
Some test result for 'At-least-20-char' is bad: Some weakness found :-(
##### Reminder about random process #####
$ ./pwdcheck 'Should.be.selected.via.*random*.process'
Test 1 - size&mix: 'Should.be.selected.via.*random*.process' is long enough
'Should.be.selected.via.*random*.process' contains characters other than ASCII letters and digits
-------
Test 2 - lexicon: 'Should.be.selected.via.*random*.process'
Should: it is based on a dictionary word
selected: it is based on a dictionary word
via: it is based on a dictionary word
random: it is based on a dictionary word
process: it is based on a dictionary word
-------
Test 3 - digital: 'Should.be.selected.via.*random*.process'
is good
-------
Test 4 - cracklib-check: 'Should.be.selected.via.*random*.process'
Should.be.selected.via.*random*.process: OK
-------
Test 5 - manual: Is 'Should.be.selected.via.*random*.process' a good or bad password? (g/b) g
is good
-------
Every test result for 'Should.be.selected.via.*random*.process' is good: No weakness found :-)
# comment: Do not use the password literally ;-)
##### Single quote the password, if you intend to use special characters #####
##### Words are found by lexicon test (using cracklib-check), and accepted #####
$ ./pwdcheck 'Single-quote-for-1-special-character!'
Test 1 - size&mix: 'Single-quote-for-1-special-character!' is long enough
'Single-quote-for-1-special-character!' contains characters other than ASCII letters and digits
-------
Test 2 - lexicon: 'Single-quote-for-1-special-character!'
Single: it is based on a dictionary word
quote: it is based on a dictionary word
for: it is based on a dictionary word
special: it is based on a dictionary word
character: it is based on a dictionary word
-------
Test 3 - digital: 'Single-quote-for-1-special-character!'
is good
-------
Test 4 - cracklib-check: 'Single-quote-for-1-special-character!'
Single-quote-for-1-special-character!: OK
-------
Test 5 - manual: Is 'Single-quote-for-1-special-character!' a good or bad password? (g/b) b
is bad
-------
Some test result for 'Single-quote-for-1-special-character!' is bad: Some weakness found :-(
##### Showing how the digital test works (it uses cracklib-check) #####
$ ./pwdcheck 'FindPatternByDigitalTest123'
Test 1 - size&mix: 'FindPatternByDigitalTest123' is long enough
FindPatternByDigitalTest123 contains only ASCII letters and digits
-------
Test 2 - lexicon: 'FindPatternByDigitalTest123'
no comment
-------
Test 3 - digital: 'FindPatternByDigitalTest123'
123: it is too simplistic/systematic
-------
Test 4 - cracklib-check: 'FindPatternByDigitalTest123'
FindPatternByDigitalTest123: OK
-------
Some test result for 'FindPatternByDigitalTest123' is bad: Some weakness found :-(
##### Showing the lexicon test and the digital test #####
$ ./pwdcheck 'Provoke1pattern2search3by4separating5words'
Test 1 - size&mix: 'Provoke1pattern2search3by4separating5words' is long enough
Provoke1pattern2search3by4separating5words contains only ASCII letters and digits
-------
Test 2 - lexicon: 'Provoke1pattern2search3by4separating5words'
Provoke: it is based on a dictionary word
pattern: it is based on a dictionary word
search: it is based on a dictionary word
separating: it is based on a dictionary word
words: it is based on a dictionary word
-------
Test 3 - digital: 'Provoke1pattern2search3by4separating5words'
12345: it is too simplistic/systematic
-------
Test 4 - cracklib-check: 'Provoke1pattern2search3by4separating5words'
Provoke1pattern2search3by4separating5words: OK
-------
Some test result for 'Provoke1pattern2search3by4separating5words' is bad: Some weakness found :-(
##### Run interactively without any parameter #####
$ ./pwdcheck
Use 4 words chosen randomly, see this link:
https://security.stackexchange.com/questions/6095/xkcd-936-short-complex-password-or-long-dictionary-passphrase
----------------------------------------------------------------
Enter a password : CandidateContaining4DifferentWords
Test 1 - size&mix: 'CandidateContaining4DifferentWords' is long enough
CandidateContaining4DifferentWords contains only ASCII letters and digits
-------
Test 2 - lexicon: 'CandidateContaining4DifferentWords'
no comment
-------
Test 3 - digital: 'CandidateContaining4DifferentWords'
is good
-------
Test 4 - cracklib-check: 'CandidateContaining4DifferentWords'
CandidateContaining4DifferentWords: OK
-------
Test 5 - manual: Is 'CandidateContaining4DifferentWords' a good or bad password? (g/b) g
is good
-------
Every test result for 'CandidateContaining4DifferentWords' is good: No weakness found :-)
sudodus@xenial32 /media/multimed-2/test/test0/pwdstrength $