Security by Post-It

16

1

As you may know, hackers are everywhere and they want to hack everything. You were asked to make password requirements which will stop any hacker. The problem is, that your boss has heard that paying for LOC is evil and he pays you $1800 - $0.03 * characters inserted per month to make you write simplest thing which possibly could work. So, you have to use small number of characters (hopefully very small), or otherwise forget about cash. Also, your boss doesn't care what language you will use.

The requirements for good passwords are similar to those in mentioned article, except the dictionary based requirements were removed to avoid making solution depend on external files, you don't have to check for letters being rearranged (Hard to understand what it actually means), last rule is removed (What is 3/4?) and it doesn't check old passwords.

The exact requirements after removing certain requirements from linked article are:

  • have at least 8 character(s)!
  • not be longer than 12 characters!
  • have upper and lower case characters!
  • have no more than 8 upper-case letter(s)!
  • have no more than 8 lower-case letter(s)!
  • have at least 2 letter(s)!
  • have a leading letter!
  • have at least 1 digit(s)!
  • not be your username!
  • not be your username backwards!
  • not contain your username!
  • not contain your username backwards!
  • have no more than 1 pair(s) of repeating characters!
  • not have 3 occurences of the same character!
  • not contain carat (^)
  • not contain space
  • not contain =
  • not conatain &
  • not contain #
  • not contain ,
  • not conatain ;
  • not contain "
  • not contain >
  • not contain <
  • not contain [
  • not contain |
  • not contain )

All misspellings on this list were left as is.

$ ./checkpass
Username: John
Password: L!]E2m69
OK.

$ ./checkpass
Username: John
Password: JohnnhoJ12
Nope.

$ ./checkpass
Username: JOE.smith
Password: JOE!smith123
OK.

The shortest code wins money (sent as JPG file). It has to show "Username: " and "Password: " prompts and reply with exact message.

Konrad Borowski

Posted 2012-06-04T12:18:19.597

Reputation: 11 185

1Nice, seeing a code golf challenge in a Daily WTF article, +1 ;-) – ChristopheD – 2012-06-04T14:19:21.423

1The first example should fail ("have upper and lower case characters!"), shouldn't it? – Howard – 2012-06-04T15:46:43.587

@Howard: It means that both uppercase and lowercase letters are needed in password. Notice lack of word "not". – Konrad Borowski – 2012-06-04T15:51:37.247

It's not very obvious in some fonts that the l in that first password is a lower-case ell and not the number one, so I'm editing to replace it with an unambiguous lower-case letter. – Peter Taylor – 2012-06-04T16:46:33.793

@PeterTaylor Ah, thank you. Indeed I read it as 1 (digit one) instead of ell. – Howard – 2012-06-04T16:58:40.987

Hmm, looks like this one's exceptionally good for regex (I was trying to build a non-regex solution but currently at double the character count for a decently golfed solution). – ChristopheD – 2012-06-04T18:50:35.007

Answers

8

Perl, 203 194 189 193 chars

Here's my Perl take on the problem:

print"Username: ";chop($u=<>);$n=reverse$u;print"Password: ";$_=<>;
say/^\pL.{7,11}$/*/\d/*/[A-Z]/*9>y/A-Z//&y/a-z//<9*/[a-z]/*
!/[" #,;->^&[|)]|(.)(.*\1.*\1|\1.*(.)\3)|\Q$u\E|\Q$n/?"OK.":"Nope."

The regexes check, in order, that the password:

  • starts with a letter, has eight to twelve characters

  • contains a digit

  • contains an uppercase letter

  • has eight or fewer uppercase letters

  • has eight or fewer lowercase letters

  • contains a lowercase letter

  • does not contain any of the forbidden punctuation marks, three occurrences of any character, more than one occurrence of a doubled character, the username, or the username reversed.

(Thanks to Peter Taylor for pointing out a bug in the 189-char version.)

breadbox

Posted 2012-06-04T12:18:19.597

Reputation: 6 893

Figured out how to run this on ideone with use v5.10; and it fails my "are regexes escaped properly" test case. See http://ideone.com/QKFnZ

– Peter Taylor – 2012-06-05T12:09:01.510

@PeterTaylor: I don't know about Ruby, but in Perl fix would be \Q$u\E|\Q$n (the last \E can be skipped, if this part would be moved to end). – Konrad Borowski – 2012-06-05T12:13:10.133

OTOH I think one character can be saved by merging the repeats as (.)(.*\1.*\1|\1.*(.)\3) (not tested - I'm not going to try scripting a full test battery with ideone). – Peter Taylor – 2012-06-05T12:21:38.267

5

Ruby, 270 characters

$><<"Username: ";u=gets.chop
$><<"Password: ";gets
puts ('^.{8,12}$+\p{Lower}+\p{Upper}+^(\p{Alpha}.*){2}+\d+(\p{Lower}.*){9}+(\p{Upper}.*){9}+(.)\1.*(.)\2+(.).*\1.*\1+[ ^=&#,;"<>\[|)]+'+u+?++u.reverse).split(?+).map{|r|/#{r}/=~$_??A:?B}*""=="AAAAABBBBBBB"?"OK.":"Nope."

A ruby implementation build on twelve regular expressions. Each expression is either a positive match (first five) or a negative one (latter seven). As a restriction the username may only contain letters or digits.

Positive regular expression matches:

  • /^.{8,12}$/: have at least 8 character(s)!, not be longer than 12 characters!
  • /\p{Lower}/ and /\p{Upper}/: have upper and lower case characters!
  • /^(\p{Alpha}.*){2}/: have at least 2 letter(s)!, have a leading letter!
  • /\d/: have at least 1 digit(s)!

Negative regular expression matches:

  • /(\p{Lower}.*){9}/: have no more than 8 lower-case letter(s)!
  • /(\p{Upper}.*){9}/: have no more than 8 upper-case letter(s)!
  • /(.)\1.*(.)\2/: have no more than 1 pair(s) of repeating characters!
  • /(.).*\1.*\1/: not have 3 occurences of the same character!
  • /[ ^=&#,;"<>\[|)]/: not contain caret, space, =, &, #, ,, ;, ", >, <, [, |, )
  • /#{u}/: not be your username!, not contain your username!
  • /#{u.reverse}/: not be your username backwards!, not contain your username backwards!

Howard

Posted 2012-06-04T12:18:19.597

Reputation: 23 109

This doesn't escape the username, so a perfectly valid password can be rejected. Test case at http://ideone.com/bPpeo

– Peter Taylor – 2012-06-05T12:04:41.750

@PeterTaylor That's why I noted the restriction for user names in my answer. – Howard – 2012-06-05T14:44:08.497

1

Python 3, 291 bytes/characters

from re import*
n,p=map(input,["Username: ","Password: "])
c,U,L=lambda x:len(split("[%s]"%x,p)),"A-Z","a-z"
print(["OK.","Nope."][any([8>len(p)>12,2>c(U)>9,2>c(L)>9,3>c(U+L),match(U+L,p),2>c("0-9"),n in p,n[::-1]in p,any(c(x)>3 for x in p),len(findall("(.)\\1",p))>1,c(' ^=&#,;"><[|)')>1])])

More nicely formatted and commented:

# import all elements from the regular expression module
from re import *

# Get the two lines of user input (username `n`, password `p`):
n, p = map(input, ["Username: ","Password: "])

# Assign some internally useful shortcuts (uppercase letters `U`, lowercase letters `L`):
# `c(x)` counts the occurrences of pattern `x` in the password `p` plus 1
c, U, L = lambda x: len(split("[%s]" % x, p)), "A-Z", "a-z"

# Print the test result: `"OK."` if the `any(...)` function returned `False`, else `"Nope."`.
# The `any(...)` combines the result of all enclosed checks and returns `True` if at least
# one of the checks failed (returned `True`).
print(["OK.", "Nope."][any([                                # vvv--- CHECKS: ---vvv
                             8>len(p)>12,                   # password length 8-12
                             2>c(U)>9,                      # 1-8 uppercase letters
                             2>c(L)>9,                      # 1-8 lowercase letters
                             3>c(U+L),                      # at least 2 letters
                             match(U+L,p),                  # starts with a letter
                             2>c("0-9"),                    # at least 1 digit
                             n in p,                        # username is not (in) the pw.
                             n[::-1]in p,                   # reversed name not (in) the pw.
                             any(c(x)>3 for x in p),        # at most 3 same characters
                             len(findall("(.)\\1",p))>1,    # at most 1 pair (e.g. "AA")
                             c(' ^=&#,;"><[|)')>1])         # does not contain special char.
                           ])

You can find this solution on ideone.com, but the output looks a bit ugly because it does not show the predefined input or even line breaks there. Also, the username-password combination "JOE.smith"-"JOE!smith123" is currently entered as fixed input data.
I added a breakdown of all the checks as debug output though.

Byte Commander

Posted 2012-06-04T12:18:19.597

Reputation: 394