2
\$\begingroup\$

Problem

Write a program to return True, if a string is a strong password.


A password is considered strong if below conditions are all met:

  • It should have at least 6 characters and at most 20 characters.
  • It must contain at least one lowercase, one uppercase and one digit.
  • It must NOT contain three repeating characters in a row ("...aaa..." is weak, but "...aa...a..." is fine, assuming other conditions are met).

Code

I've solved a similar problem to LeetCode Strong Password Checker. If you would like to review the code or add other methods or provide any change/improvement recommendations please do so! Thank you!

def strong_password_match(s: str) -> bool:
    """Returns a boolean if a password is correct"""
    import re
    if re.match(
            r'^(?!.*([a-z])\1\1)(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[A-Za-z0-9]{6,20}$', s) is not None:
        return True
    return False


def strong_password_search(s: str) -> bool:
    """Returns a boolean if a password is correct"""
    import re
    if re.search(
            r'^(?!.*([a-z])\1\1)(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[A-Za-z0-9]{6,20}$', s) is not None:
        return True
    return False


def strong_password_findall(s: str) -> bool:
    """Returns a boolean if a password is correct"""
    import re
    if re.findall(
            r'^(?!.*([a-z])\1\1)(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[A-Za-z0-9]{6,20}$', s) != []:
        return True

    return False


if __name__ == '__main__':
    # ---------------------------- TEST ---------------------------
    import timeit
    import cProfile

    DIVIDER_DASH_LINE = '-' * 50
    GREEN_APPLE = '\U0001F34F'
    RED_APPLE = '\U0001F34E'

    test_methods = (
        ("re.match", strong_password_match),
        ("re.search", strong_password_search),
        ("re.findall", strong_password_findall),
    )

    test_inputs = ('abcAb1', 'abcAb1abcAb1abcAb100',
                   'abcAb1abcAb1abcAb10111', 'aaaAb1abcAb1abcAb100')

    # --------------------------------- PROFILING AND BANCHMARK SETTINGS --------------------------------------
    NUMBER_OF_RUNS = 1
    CPROFILING_ON = False
    BENCHMARK_ON = True

    for description, method in test_methods:
        print((GREEN_APPLE + RED_APPLE) * 5)

        for test_input in test_inputs:
            if CPROFILING_ON:
                print(f'{description} cProfiling: ', cProfile.run("method(test_input)"))
            if BENCHMARK_ON:
                print(f'{description} Benchmark: ', timeit.Timer(
                    f'for i in range({NUMBER_OF_RUNS}): {method(test_input)}', 'gc.enable()').timeit())

            if method(test_input):
                print(f'{GREEN_APPLE} {description}: "{test_input}" is a strong password.')
            else:
                print(f'{RED_APPLE} {description}: "{test_input}" is not a strong password.')

Output

🍏🍎🍏🍎🍏🍎🍏🍎🍏🍎
re.match Benchmark:  1.8437564770000001
🍏 re.match: "abcAb1" is a strong password.
re.match Benchmark:  1.9900512190000006
🍏 re.match: "abcAb1abcAb1abcAb100" is a strong password.
re.match Benchmark:  1.782858269
🍎 re.match: "abcAb1abcAb1abcAb10111" is not a strong password.
re.match Benchmark:  1.8016775740000002
🍎 re.match: "aaaAb1abcAb1abcAb100" is not a strong password.
🍏🍎🍏🍎🍏🍎🍏🍎🍏🍎
re.search Benchmark:  1.8374795240000008
🍏 re.search: "abcAb1" is a strong password.
re.search Benchmark:  1.8352807049999988
🍏 re.search: "abcAb1abcAb1abcAb100" is a strong password.
re.search Benchmark:  1.8023251919999996
🍎 re.search: "abcAb1abcAb1abcAb10111" is not a strong password.
re.search Benchmark:  1.9213070860000006
🍎 re.search: "aaaAb1abcAb1abcAb100" is not a strong password.
🍏🍎🍏🍎🍏🍎🍏🍎🍏🍎
re.findall Benchmark:  1.7806425130000019
🍏 re.findall: "abcAb1" is a strong password.
re.findall Benchmark:  1.836686480000001
🍏 re.findall: "abcAb1abcAb1abcAb100" is a strong password.
re.findall Benchmark:  1.8053996060000017
🍎 re.findall: "abcAb1abcAb1abcAb10111" is not a strong password.
re.findall Benchmark:  1.8203372360000003
🍎 re.findall: "aaaAb1abcAb1abcAb100" is not a strong password.

If you wish to simplify/modify/explore the expression, it's been explained on the top right panel of regex101.com. If you'd like, you can also watch in this link, how it would match against some sample inputs.


RegEx Circuit

jex.im visualizes regular expressions:

enter image description here

Source

Strong Password Checker

\$\endgroup\$
1
  • 2
    \$\begingroup\$ ugh... I vehemently object to the LeetCode challenge for having a really crappy definition of "strong password". These are good guidelines.. Rules #1 and #2 actually make your passwords worse and less safe. \$\endgroup\$ – Gloweye Oct 25 '19 at 8:00
4
\$\begingroup\$

Imports

I notice you import re three times, once for each password check function. Why? Just import the library once at the top of the file, and you can use it in all of your functions.

Returning Booleans

Instead of returning True or False based on the result of the expression, simply return the expression. It results to a boolean anyway, so returning True and False is unnecessary; just return the expression.

DRY (Don't Repeat Yourself)

You have three functions that essentially perform the same task, just with very slightly different function calls. You can compress these regex checks into one method. Then you can pass what type of check you want to perform on the password. See below.

import re

def strong_password_check(password: str, type_of_check: str) -> bool:
    """
    Accepts a password and the type of check to perform on the password

    :param password: Password to check
    :param type_of_check: How to check the password. Has to be "match", "search", or "findall"

    """
    regex = r'^(?!.*([a-z])\1\1)(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[A-Za-z0-9]{6,20}$'
    if type_of_check == "match":
        return re.match(regex, password) is not None
    if type_of_check == "search":
        return re.search(regex, password) is not None
    if type_of_check == "findall":
        return re.findall(regex, password) != []

Test Cases

Not really sure the usefulness of red and green apples. Again, importing cProfile and timeit should go at the top of the program.

I see you're checking for performance, but not for the actual strength of the password. Would your program agree that Password123 is a good password? Any security professional worth his salt wouldn't agree.

I suggest that you use the Kolmgorov Complexity to check the strength of the password.

\$\endgroup\$

Your Answer

By clicking β€œPost Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.