This code snippet shows an implementation of a password policy check. - The method composition uses regex to verify that the password meets composition criteria (at least one number, one special character, one capital letter and length between 8 and 64) - The method check_strength() uses the library zxcvbn to check a password's strength
Initial setup:
pip install zxcvbn
Code:
import re
from zxcvbn import zxcvbn
def composition(password):
number = "(?=.*\d)"
upper_lower = "(?=.*[a-z])(?=.*[A-Z])"
special = "[^A-Za-z0-9]"
length = ".{8,64}$"
test = []
test.append(True if re.search(re.compile(number), password) else False)
test.append(True if re.search(re.compile(upper_lower), password) else False)
test.append(True if re.search(re.compile(special), password) else False)
test.append(True if re.search(re.compile(length), password) else False)
return test
def check_strength(password):
result = zxcvbn(password, user_inputs=['John', 'Smith'])
strength = {
0: "Worst",
1: "Bad",
2: "Weak",
3: "Good",
4: "Strong"
}
return [result['score'], strength[result['score']], result['feedback']]
def main():
password = 'Abcdefghi1.'
x = composition(password)
print("composition: ", x)
y = check_strength(password)
print("check_strength: ", y)
return None
if __name__ == '__main__':
main()
1- Do you think the implementation of this code snippet is up-to-date and secure?
2- Would you make any other changes to this code snippet?
3- Did you use any additional resources while checking the code? If yes, please provide a link or description of your resource.
1 Answer 1
Elevate the data that drives the code to the status of named constants. Among other benefits, this simple change will support any efforts you might make to subject the code to automated testing (because your test assertions can use the constants too).
The regular expressions are overly complex and maybe naive. Too complex because they contain unnecessary non-capturing groups (I got rid of them, in addition to separating the tests for upper and lower). Maybe too naive, depending on your goals, because they assume that the universe of letters is restricted to ASCII (I made no changes in that regard).
import re
import sys
from zxcvbn import zxcvbn
from dataclasses import dataclass
from typing import Tuple
STRENGTHS = {
0: 'Worst',
1: 'Bad',
2: 'Weak',
3: 'Good',
4: 'Strong'
}
PATTERNS = dict(
number = re.compile('\d'),
lower = re.compile('[a-z]'),
upper = re.compile('[A-Z]'),
special = re.compile('[^A-Za-z0-9]'),
length = re.compile('.{8,64}$'),
)
Return more declarative data. Both composition()
and check_strength()
return fairly cryptic data bundles: a boolean list and a hodgepodge list. More
useful would be declarative return values, such as a dict or dataclass. Each
is illustrated here:
@dataclass(frozen = True)
class PasswordStrength:
score: int
label: str
warning: str
suggestions: Tuple[str]
def composition(password):
return {
label : bool(rgx.search(password))
for label, rgx in PATTERNS.items()
}
def check_strength(password):
z = zxcvbn(password, user_inputs=['John', 'Smith'])
return PasswordStrength(
z['score'],
STRENGTHS[z['score']],
z['feedback']['warning'],
tuple(z['feedback']['suggestions']),
)
Consider unifying the checks. I don't know your larger goals,
but in the abstract I would be tempted to unify the logic
of both functions and return a single frozen data bundle
containing all of the checks: everything currently in PasswordStrength
, plus some attributes like has_number
,
has_upper
, and so forth. The class could also include
one or more summary @property
methods to return a yes/no verdict
based on the results from the individual checks.
Get in the habit of parameterizing the main function. This is a small issue
in the grand scheme, but while working on the code, I was annoyed that I had to
edit the code each time I wanted to experiment with a different password. You
can avoid such hassles by setting up the main()
function to
accept command-line arguments. For example:
def main(args):
password = args[0] if args else 'Abcdefghi1.'
...
if __name__ == '__main__':
main(sys.argv[1:])
Explore related questions
See similar questions with these tags.