# Security by Post-It

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)!
• 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
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. • Nice, seeing a code golf challenge in a Daily WTF article, +1 ;-) – ChristopheD Jun 4 '12 at 14:19 • The first example should fail ("have upper and lower case characters!"), shouldn't it? – Howard Jun 4 '12 at 15:46 • @Howard: It means that both uppercase and lowercase letters are needed in password. Notice lack of word "not". – Konrad Borowski Jun 4 '12 at 15:51 • 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 Jun 4 '12 at 16:46 • @PeterTaylor Ah, thank you. Indeed I read it as 1 (digit one) instead of ell. – Howard Jun 4 '12 at 16:58 ## 3 Answers ## Perl, 203194189 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.)

• Figured out how to run this on ideone with use v5.10; and it fails my "are regexes escaped properly" test case. See ideone.com/QKFnZ – Peter Taylor Jun 5 '12 at 12:09
• @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 Jun 5 '12 at 12:13
• 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 Jun 5 '12 at 12:21

### 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!
• This doesn't escape the username, so a perfectly valid password can be rejected. Test case at ideone.com/bPpeo – Peter Taylor Jun 5 '12 at 12:04
• @PeterTaylor That's why I noted the restriction for user names in my answer. – Howard Jun 5 '12 at 14:44

## Python 3, 291 bytes/characters

from re import*
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.