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 is 0 => true => weak password

  • if 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 $