1
\$\begingroup\$

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.

asked Jun 8, 2022 at 17:38
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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:])
answered Jun 9, 2022 at 0:14
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.