Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Minimal password checking: a summary

by bronto (Priest)
on Jul 29, 2003 at 12:24 UTC ( #278798=perlquestion: print w/ replies, xml ) Need Help??
bronto has asked for the wisdom of the Perl Monks concerning the following question:

I'd like to sum up what has been discussed on my question about basic checks on password; What follows could be further discussed and improved and then, if you like, go to code snippets or categorized q/a.

The question

First of all, the question was:

What I need is to do just a minimal check, in particular:

  • password is at least 5 characters long;
  • password contains alphabetic characters and digits
  • password isn't a repetition of a few characters or patterns (e.g.: "pippo12": too many p's; "cacca11": only c's and a's, and widely repeated; "32ratatata": the pattern "ta" appears 3 times and covers 60% of the password...)

While the first two points are really easy to code:

. . .

I'm not sure how to manage the third point in a clear and efficient manner . . .

What's basic, anyway?

An old ugly programming language, one could say :-)

After reading monks' comments I went back to my Essential System Administration book (2nd edition, unfortunately :-). At page 210, speaking about Password Triviality Checks, it broadly says that the passwd command (she's speaking about UNIX passwords) may reject passwords that are:

  • all lowercase or all alphabetic
  • same as the account username or derived from the GECOS field
  • words that could be found on an online dictionary
  • passwords previously used by the user
  • simple keyboard patterns

Speaking about the SCO(TM) UNIX'goodpw program, she then shows some examples of passwords that would be rejected for the user chavez:

  • the username itself (obviously)
  • cha12vez, because you get the username after eliminating all non-alphas
  • z12chave: same as before, with one left rotation
  • ChavChav: not rotationally unique
  • C1C1C1C1: same as before, four times

There have been interesting considerations about repeated characters or sequences. I share the opinion expressed by an Anonymous Monk, even if agree that 'ppppp' isn't more difficult to crack than 'abcde' or 'qwerty'.

What do you mean for basic, then?

Now, the requirement in the original question was to have something clear and efficient; in particular, waiting for more that a second to have the password check is not acceptable.

I ran john the ripper against my password, with a dictionary of nearly 187000 words it took 44 seconds: too much; we have to drop some requirements to have a real basic check:

  • checking dictionary words mixed with punctuation, symbols and digits is too slow: dropped
  • running perl -ne 'chomp ; print "The password is $_\n" if crypt($_,"Ab") eq "AbCdEfGhIjKlM"' wordlist.lst on Linux with a PIV processor (wordlist contains 187000 words) took me from 9 to 12 seconds: too slow: dropped
  • checking keyboard sequences depends on the keyboard you are using (hey! not everybody out there uses a QWERTY keyboard :-): dropped

Therefore, here is what we'll consider "a minimal check for passwords":

  • the password length falls into a predefined range;
  • repeated characters don't cover more than an half of the entire password;
  • stripped away all non-alphas, password doesn't match the username, nor it does after all the possible left rotations of the string;
  • stripped away all non-alphas, password doesn't match personal data of the user (name, surname, city...);
  • none of n-1 left-rotations of an n-long password matches it;
  • password contains at least alphas, digits and non-alpha-digits

Ok. I would you code that?

Personally? This is my first attempt:

#!/usr/bin/perl # # call this file passcheck.pl in order to test it with the # test script use strict ; use warnings ; sub passcheck { my ($username,$password,$name,$surname,$city) = @_ ; my ($minlen,$maxlen,$maxfreq) = (5,8,.5) ; my $plen = length $password ; my $pclean = lc $password ; $pclean =~ s/[^a-z]//g ; my %prots = map {$_,qr/$_/} ($pclean,leftrotations($pclean)) ; # Check length { return "password is too short" if $plen < $minlen ; return "password is too long" if $plen > $maxlen ; } # Check repetitions { my @chars = split //,$password ; my %unique ; foreach my $char (@chars) { $unique{$char}++ } ; while (my ($char,$count) = each %unique) { return "Too many repetions of char $char" if $count/$plen > $maxfreq ; } } # Check password against username, name, surname and city # All but username could be composed, like "Alan Louis", or "Di Cioc +cio" # or "Los Angeles", so we have to treat each chunk separately. { my %chunks = map { ($_,qr/$_/) } split(/\s+/,lc(join(" ",$name,$surname,$city))) ; # Add username to %chunks # You can compact the code below in one line, but why? :-) { my $lcuser = lc $username ; $chunks{$lcuser} = qr/$lcuser/ ; } foreach my $chunk (keys %chunks) { foreach my $rot (keys %prots) { return "password matches personal data after some left rotatio +n" if $rot =~ $chunks{$chunk} or $chunk =~ $prots{$rot} ; } } } # Left rotations of the password don't match it { foreach my $rot (leftrotations($password)) { return "Password matches itself after some left rotation" if $rot eq $password ; } } # Password contains alphas, digits and non-alpha-digits { local $_ = $password ; return "Password must contain alphanumeric characters, digits and +symbols" unless /[a-z]/i and /\d/ and /[^a-z0-9]/i ; } return "password ok" ; } sub leftrotations { my $string = shift ; my $n = length $string ; my @result ; # note: $i < $n, since the n-th permutation is the password again for (my $i = 1 ; $i < $n ; $i++) { $string =~ s/^(.)(.*)$/$2$1/ ; push @result,$string ; } return @result ; } 1 ;

I tested it with this small test script:

#!/usr/bin/perl use Test::More qw(no_plan) ; my ($username,@userinfo) = qw(bronto Marco Marongiu Capoterra) ; my $good = 'c0m&c@z%' ; my @passwords = qw(shrt waytoolong manyyyyy nto12bro% comar1$ marmaron poterr@1 t1c&t1c& pitbull) ; my $ok = "password ok" ; require './passcheck.pl' ; is($ok,passcheck($username,$good,@userinfo),"$good is good") ; isnt($ok,passcheck($username,$_,@userinfo),"$_ is bad") foreach @passw +ords ;

Would you like to improve those subs with me?

Ciao!
--bronto


The very nature of Perl to be like natural language--inconsistant and full of dwim and special cases--makes it impossible to know it all without simply memorizing the documentation (which is not complete or totally correct anyway).
--John M. Dlugosz

edited: Tue Jul 29 14:01:03 2003 by jeffa - readmore tag

Comment on Minimal password checking: a summary
Select or Download Code
Re: Minimal password checking: a summary
by particle (Vicar) on Jul 29, 2003 at 13:04 UTC

    well, if you right rotate instead, you can use chop.

    sub rot_same { my( $word )= @_; my %seen; for(1..length $word) { ## rotate right $word= chop($word) . $word; return 1 if exists $seen{$word}; $seen{$word}++; } } print $_, ' => ', rot_same($_), $/ for qw(abcdef aaaaaa abcabc); __END__ abcdef => aaaaaa => 1 abcabc => 1

    and if you calculate the maximum number of same characters once, it will speed things up.

    sub too_many_repetitions { my( $word )= @_; my $same_char_percent= 0.5; my $max_same_char= $same_char_percent * length $word; my @chars= split // => $word; my %seen; $seen{$_}++ for @chars; return 1 if $max_same_char < pop @{[sort values %seen]}; } print $_, ' => ', too_many_repetitions($_), $/ for qw(abcdef aaaaaa abcabc); __END__ abcdef => aaaaaa => 1 abcabc =>

    ~Particle *accelerates*

Re: Minimal password checking: a summary
by dbwiz (Curate) on Jul 29, 2003 at 13:18 UTC

    It looks like it will let pass your username with some disguise:

    my $doubt= 'br0n7o1.'; # (also 8r0n7o1.) reads bronto1. is($ok,passcheck($username, $doubt, @userinfo), "$doubt is good");

    Perhaps you should do some minimal conversion before checking for personal info, such as  tr/128075/izbots/.

      Yesterday night I worked on your suggestion: unfortunately there are many symbols that cannot be mapped uniquely; for example the number 1 could well be mapped to 'l' and 'i' and 'I'; the same holds for other symbols that could be mapped to more than one letter. Taking each and every possibile combination could take a lot of time, and you are always leaving something out.

      That's why I chose not to implement it even in a simple form. But if you have some efficient code that takes into account the special cases, you are welcome :-)

      Ciao!
      --bronto


      The very nature of Perl to be like natural language--inconsistant and full of dwim and special cases--makes it impossible to know it all without simply memorizing the documentation (which is not complete or totally correct anyway).
      --John M. Dlugosz

        There is a simple way. Instead of checking if a password looks like a username, you can first translate the username into this sort of "code" and then check if they look the same. Something along the lines of

        my %codes = ( l => 1, L => 1, i => 1, I => 1, z => 2, Z => 2, e => 3, E => 3, h => 4, H => 4, s => 5, S => 5, G => 6, g => 9, t => 7, T => 7, b => 8, B => 8, o => 0, O => 0 ); my $user = 'pileofdung'; my $translated; for (split //, $user) { $translated .= (defined $codes{$_}) ? $codes{$_} : $_; } print $translated,$/; # p1130fdun9 my $password = 'p113.0f.dun9%'; my $match =0; for (split //, $translated) { $match++ if $password =~ /$_/ } print "they match\n" if $match >= length($password) -2; # you can choose how lax you want to be by # setting an appropriate number of characters that # you want to be different between username and password # in this case if all but 2 characters are the same, it # is a bad password

        Of course, you can use any other comparing methods, but just to give you some ideas to play with.

Re: Minimal password checking: a summary
by kutsu (Priest) on Jul 29, 2003 at 14:33 UTC

    Since the title of this node includes the word summary I wanted to add this:

    There is no such thing as a secure password and passwords aren't the end all of security. Even if the perfect program/script were to be written which was unbreakable, the password would still have it's greatest weakness in the user, who could tell someone the password, use it on a different non-secure site, or etc...

    Not saying this directly to anyone, not even bronto, just wanted it added since this had the word summary. Besides that nice looking code and bronto++.

    "Pain is weakness leaving the body, I find myself in pain everyday" -me

      There is no such thing as a secure password and passwords aren't the end all of security. Even if the perfect program/script were to be written which was unbreakable, the password would still have it's greatest weakness in the user, who could tell someone the password, use it on a different non-secure site, or etc...

      I agree with you. Nevertheless, applying security is like building walls around your systems, and doors on the walls; and none of the doors should be too easy to open.

      A password is such a door. And every door should be secured as much as possible, compatibly with the environment around the system itself. Obviously, it doesn't make sense to have an uncatchable password when you leave yourself logged in on a publicly accessible terminal :-)

      Ciao!
      --bronto


      The very nature of Perl to be like natural language--inconsistant and full of dwim and special cases--makes it impossible to know it all without simply memorizing the documentation (which is not complete or totally correct anyway).
      --John M. Dlugosz
Re: Minimal password checking: a summary
by duelafn (Priest) on Jul 29, 2003 at 20:17 UTC
    If you're doing rotations, you should probably also do reversals.
    sub passcheck2 { my ($u, $p) = splice @_, 2; "password ok" eq passcheck( $u, $p, @_ ) and "password ok" eq passcheck( $u, reverse($p), @_ ) }

      I thought about it yesterday and implemented in my nightly programming session :-)

      I am going to post the new code later

      Ciao!
      --bronto

      Update: posted!, with a test script, too.


      The very nature of Perl to be like natural language--inconsistant and full of dwim and special cases--makes it impossible to know it all without simply memorizing the documentation (which is not complete or totally correct anyway).
      --John M. Dlugosz

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://278798]
Approved by particle
Front-paged by valdez
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (6)
As of 2014-09-16 05:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite cookbook is:










    Results (156 votes), past polls