6

I wrote this script but [:alnum:] doesnt work. Anyone help?

echo -n "Enter a password : "
read password

LEN=$(echo ${#password})

if [ $LEN -lt 10 ]; then

       echo "$password is smaller than 10 characters"
else
     if ! [ -z `echo $password | tr -d "[:alnum:]"` ]; then
        echo "$password is a weak password"
     else
        echo "$password is a strong password"
     fi
fi
echo 
10
  • @derHugo it doest work like that Nov 10, 2017 at 10:36
  • 1
    IMO see the link I gave you to enforce strong passwords. Use john the ripper , or similar, to test for weak passwords.
    – Panther
    Nov 10, 2017 at 16:18
  • 1
    Available from the repos via package john, see github.com/magnumripper/JohnTheRipper for instructions and the latest version.
    – dessert
    Nov 10, 2017 at 17:01
  • 2
    Passwords don't have strength. Password-generation methods have strength, and what you're trying to do is make an after-the-fact guess at the strength of the method used by looking at characteristics of the password it produced. This only provides a benefit if your guess matches an attacker's guess.
    – Mark
    Nov 10, 2017 at 22:27
  • 1
    All of these answers treat this as an XY problem. Nov 11, 2017 at 1:04

7 Answers 7

16

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
4
  • 2
    cracklib-check doesn't seem to check (extensive) dictionaries of languages other than English: subsidiarydictionary word, but German equivalent subsidiärOK.
    – dessert
    Nov 10, 2017 at 14:30
  • 2
    The here string notation is very useful in this case: cracklib-check <<< 1234
    – dessert
    Nov 10, 2017 at 14:40
  • @dessert probably depends on what dictionaries I have installed
    – muru
    Nov 10, 2017 at 20:52
  • 5
    If cracklib-check counts !foobared123 as “OK” but foobared123 as “simplistic”, it isn't trying hard enough. Adding one punctuation character is child's play for a password cracker. All cracklib-check really does is weed out some of the extremely stupid passwords, it doesn't really tell whether a password is strong. Nov 10, 2017 at 21:12
14

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.)

2
  • Thanks a lot for a good explanation, particularly why the XKCD pattern — pick multiple “words” at random from some dictionary — is good. :-)
    – sudodus
    Nov 11, 2017 at 4:24
  • The Diceware web page seems moved to world.std.com/~reinhold/diceware.html
    – sudodus
    Nov 14, 2017 at 12:27
8

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.

3
  • 2
    This is by far the best answer. See also askubuntu.com/questions/244115/…
    – Panther
    Nov 10, 2017 at 16:38
  • yeah, my old bank considers a 12 character limit and not allowing special characters "strong", but I suspect most experts disagree. using tools that get updated allows you to stay up with standards.
    – Sam
    Nov 10, 2017 at 18:47
  • 3
    No application can determine the strength of a password, because there's no such thing. There is only the strength of a password generation method, and you can't tell the strength from one password. Nov 10, 2017 at 21:13
1
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

4
  • I added syntax highlighting. Makes scripts easier to read.
    – muru
    Nov 10, 2017 at 11:44
  • Oh didn't see that. Just copy pasted my answer in again ;) changed it again.. Though usually this is done automatically isn't it?
    – derHugo
    Nov 10, 2017 at 11:45
  • depending on tags. If the question were tagged bash, for example, everything would be highlighted as a bash script... But the question isn't really about bash.
    – muru
    Nov 10, 2017 at 11:50
  • You've overcomplicated things even more with the use of wc, you've also changed the logic since for example spaces no longer count as making a password “strong”, and you've kept many bugs from the original such as the treatment of a * b as input. Nov 10, 2017 at 21:17
1

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 $ 
13
  • i run it and it always says strong password Nov 10, 2017 at 10:48
  • I'll check and modify it ...
    – sudodus
    Nov 10, 2017 at 10:49
  • im trying too! waiting for your response! Nov 10, 2017 at 10:50
  • @sudodus password shall be strong, if it does NOT ONLY contain alnum. Right now you only look if it has at least one alnum.
    – derHugo
    Nov 10, 2017 at 10:55
  • I think it is better now. We'll see if you find some error.
    – sudodus
    Nov 10, 2017 at 11:41
-3

I found the correct script and it works:

echo -n "Enter a password : "
read password

LEN=$(echo ${#password})

if [ $LEN -lt 10 ]; then

       echo "$password is smaller than 10 characters"
else
     if  [ -z `echo $password | tr -d '[:alpha:]' | tr -d '[:upper:]' | tr -d '[:lower:]' ` ]; then
        echo "$password is a weak password"
     else
        echo "$password is a strong password"
     fi
fi
echo 
1
  • 2
    This does still not work for me -> returns allways "strong password"
    – derHugo
    Nov 10, 2017 at 11:10
-3

Finally, the following script that i wrote, solved my problem:

echo -n "Enter a password : "
read password

LEN=$(echo ${#password})

if [ $LEN -lt 10 ]; then

   echo "$password is smaller than 10 characters"

else

 if  [ -z `echo $password | tr -d '[:alpha:]' | tr -d '[:upper:]' | tr -d '[:lower:]' ` ] || [ -z `echo $password | tr -d '[:upper:]' | tr -d '[:digit:]' `] || [ -z `echo $password | tr -d '[:lower:]' | tr -d '[:digit:]' `] ; then

    echo "$password is a weak password"

 else

    echo "$password is a strong password"

 fi

fi

echo 

Thanks for your responses guys!

1
  • 2
    I suggest you read Gilles's answer. Your script still has massive issues.
    – Kevin
    Nov 10, 2017 at 23:24

Not the answer you're looking for? Browse other questions tagged .