Reputation: 51
I am making a simple script in Python that evaluates the strength of a password on a score system that gives and takes points based on whether or not it contains upper or lowercase letters, numbers and symbols for school.
One of the requirements is that it checks for 3 letters or numbers that are consecutive from left to right on a UK QWERTY keyboard and takes away 5 points for each instance. For example the password 'qwer123' would lose 15 points for 'qwe', 'wer' and '123'. How could this be accomplished? My current code below.
def check():
user_password_score=0
password_capitals=False
password_lowers=False
password_numbers=False
password_symbols=False
password_explanation_check=False
ascii_codes=[]
password_explanation=[]
print("The only characters allowed in the passwords are upper and lower case letters, numbers and these symbols; !, $, %, ^, &, *, (, ), _, -, = and +.\n")
user_password=str(input("Enter the password you would like to get checked: "))
print("")
if len(user_password)>24 or len(user_password)<8:
print("That password is not between 8 and 24 characters and so the Password Checker can't evaluate it.")
menu()
for i in user_password:
ascii_code=ord(i)
#print(ascii_code)
ascii_codes.append(ascii_code)
#print(ascii_codes)
for i in range(len(ascii_codes)):
if ascii_codes[i]>64 and ascii_codes[i]<90:
password_capitals=True
elif ascii_codes[i]>96 and ascii_codes[i]<123:
password_lowers=True
elif ascii_codes[i]>47 and ascii_codes[i]<58:
password_numbers=True
elif ascii_codes[i] in (33,36,37,94,38,42,40,41,45,95,61,43):
password_symbols=True
else:
print("Your password contains characters that aren't allowed.\n")
menu()
if password_capitals==True:
user_password_score+=5
if password_lowers==True:
user_password_score+=5
if password_numbers==True:
user_password_score+=5
if password_symbols==True:
user_password_score+=5
if password_capitals==True and password_lowers==True and password_numbers==True and password_symbols==True:
user_password_score+=10
if password_numbers==False and password_symbols==False:
user_password_score-=5
if password_capitals==False and password_lowers==False and password_symbols==False:
user_password_score-=5
if password_capitals==False and password_lowers==False and password_numbers==False:
user_password_score-=5
#print(user_password_score)
if user_password_score>20:
print("Your password is strong.\n")
else:
print("That password is weak.\n")
#don't forget you still need to add the thing that checks for 'qwe' and other stuff.
menu()
Upvotes: 4
Views: 182
Reputation: 912
If regular expressions are allowed, you can do this in one line:
import re
user_password_score = 42
pwd = 'qwer123'
user_password_score += (lambda z : -5 * len([match.group(1) for match in re.compile('(?=({0}))'.format('|'.join(["({0})".format(w) for w in [x for y in [[s[i:i+3] for i in range(0,len(s)-2)] for s in ["qwertyuiopasdfghjklzxcvbnm", "azertyuiopqsdfghjklmwxcvbn", "abcdefghijklmnopqrstuvwxyz", "01234567890"]] for x in y]]))).finditer(z) ]))(pwd)
This code is equivalent:
import re
user_password_score = 42
pwd = 'qwer123'
seqs = ["qwertyuiopasdfghjklzxcvbnm", "azertyuiopqsdfghjklmwxcvbn", "abcdefghijklmnopqrstuvwxyz", "01234567890"]
pattern = re.compile('(?=({0}))'.format('|'.join(["({0})".format(w) for w in [x for y in [[s[i:i+3] for i in range(0,len(s)-2)] for s in seqs] for x in y]])))
penalty = -5 * len([match.group(1) for match in pattern.finditer(pwd) ])
user_password_score += penalty
And the following code is also equivalent ( and hopefully, also human-readable ). We'll print it step by step to see better what it's doing.
import re
def build_pattern(sequences):
all_triplets = []
triplets = []
for seq in sequences:
for i in range(0, len(seq) - 2):
triplets.append(seq[i:i+3])
all_triplets.append(triplets)
triplets = []
expanded_triplets = [ x for y in all_triplets for x in y ]
print("Plain list of triplets: " + str(expanded_triplets))
string_pattern = '|'.join( [ "({0})".format(x) for x in expanded_triplets ] )
lookahead_pattern = '(?=({0}))'.format(string_pattern)
print("Regex expression: " + lookahead_pattern)
return re.compile(lookahead_pattern)
password = 'qwer123'
user_password_score = 42
print("User password score: " + str(user_password_score))
sequences = ["qwertyuiopasdfghjklzxcvbnm",
"azertyuiopqsdfghjklmwxcvbn",
"abcdefghijklmnopqrstuvwxyz",
"01234567890"]
pattern = build_pattern(sequences)
matches = [ match.group(1) for match in pattern.finditer(password) ]
print("Matches : " + str(matches))
matches_count = len(matches)
penalty = -5 * matches_count
print("Penalty: " + str(penalty))
user_password_score += penalty
print("Final score: " + str(user_password_score))
This is the output:
User password score: 42
Plain list of triplets: ['qwe', 'wer', 'ert', 'rty', 'tyu', 'yui', 'uio', 'iop', 'opa', 'pas', 'asd', 'sdf', 'dfg', 'fgh', 'ghj', 'hjk', 'jkl', 'klz', 'lzx', 'zxc', 'xcv', 'cvb', 'vbn', 'bnm', 'aze', 'zer', 'ert', 'rty', 'tyu', 'yui', 'uio', 'iop', 'opq', 'pqs', 'qsd', 'sdf', 'dfg', 'fgh', 'ghj', 'hjk', 'jkl', 'klm', 'lmw', 'mwx', 'wxc', 'xcv', 'cvb', 'vbn', 'abc', 'bcd', 'cde', 'def', 'efg', 'fgh', 'ghi', 'hij', 'ijk', 'jkl', 'klm', 'lmn', 'mno', 'nop', 'opq', 'pqr', 'qrs', 'rst', 'stu', 'tuv', 'uvw', 'vwx', 'wxy', 'xyz', '012', '123', '234', '345', '456', '567', '678', '789', '890']
Regex expression: (?=((qwe)|(wer)|(ert)|(rty)|(tyu)|(yui)|(uio)|(iop)|(opa)|(pas)|(asd)|(sdf)|(dfg)|(fgh)|(ghj)|(hjk)|(jkl)|(klz)|(lzx)|(zxc)|(xcv)|(cvb)|(vbn)|(bnm)|(aze)|(zer)|(ert)|(rty)|(tyu)|(yui)|(uio)|(iop)|(opq)|(pqs)|(qsd)|(sdf)|(dfg)|(fgh)|(ghj)|(hjk)|(jkl)|(klm)|(lmw)|(mwx)|(wxc)|(xcv)|(cvb)|(vbn)|(abc)|(bcd)|(cde)|(def)|(efg)|(fgh)|(ghi)|(hij)|(ijk)|(jkl)|(klm)|(lmn)|(mno)|(nop)|(opq)|(pqr)|(qrs)|(rst)|(stu)|(tuv)|(uvw)|(vwx)|(wxy)|(xyz)|(012)|(123)|(234)|(345)|(456)|(567)|(678)|(789)|(890)))
Matches : ['qwe', 'wer', '123']
Penalty: -15
Final score: 27
Inside the build_pattern
function, [ x for y in all_triplets for x in y ]
is a trick to expand a list of lists into a plain list. A regex pattern like (lmw)|(mwx)|(wxc)
used inside a finditer()
tells we want to find all matches for lmw, mwx and wxc. And when we wrap this pattern inside a lookahead ( (?=())
) we're telling re
that it should also include overlapping matches on the result.
Upvotes: 0
Reputation: 4855
I would create a list of sequences of adjacent keys, as others mentioned above. Then I would create a sliding window function to generate all sequences of length 3, and match each of them against the password:
from itertools import islice
keyboard_rows = ['1234567890', 'qwertyuiop', 'asdfghjkl', 'zxcvbnm']
def window(seq, n=3):
it = iter(seq)
result = tuple(islice(it, n))
if len(result) == n:
yield result
for elem in it:
result = result[1:] + (elem,)
yield result
for row in keyboard_rows:
for seq in window(row, n=3):
if "".join(seq) in password:
user_password_score -= 15
# scan other direction <--
for seq in window(row[::-1], n=3):
if "".join(seq) in password:
user_password_score -= 15
Upvotes: 0
Reputation: 3265
You could store forbidden sequences in a set of strings, and decrement the score every time someone uses that sequence.
password = "qwert123"
score = 42 # initial score
sequences = { # all in lowercase because of the `lower()` in the loop
"qwertyuiopasdfghjklzxcvbnm",
"azertyuiopqsdfghjklmwxcvbn",
"abcdefghijklmnopqrstuvwxyz",
"01234567890"
}
match_length = 3 # length threshold for the sanction
sequences.update({s[::-1] for s in sequences}) # do we allow reverse ?
for c in range(len(password)-match_length+1):
for seq in sequences:
if password[c:c+match_length].lower() in seq:
score-=5
print(f"'{password[c:c+match_length]}' => -5 !")
break # Don't flag the same letters more than once
print(score) # 22 (42-4*5)
Upvotes: 1
Reputation: 531
The simplest way is to brute force through all the possible sequences.
Create 4 string: "1234567890"
, "qwertyuiop"
, "asdfghjkl"
, "zxcvbnm"
and loop through each with 3 characters from the user_password
.
You can initialize this list at the beginning of the check
function:
sequences = ["1234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm"]
and then inside the for i in range(len(ascii_codes))
loop add:
if(i<len(ascii_codes)-2): # since we will be checking for characters up to i+2 in our loop
flag = False # initialize a flag to signal finding a match
for s in sequences: # loop through each of the 4 keyboard sequences
if(s.find(user_password[i: i+3].lower()) != -1):
user_password_score -= 5
flag = True
break
if(flag): break
Upvotes: 0