def calculate_entropy(password: str,
                      method: str = "normal",
                      character_pool: CharacterPool = None):
    """
    Calculate the entropy of a password according to the formula:
    log base 2 (number of possible passwords)

    :param password: the password
    :type: str

    :param method: method to calculate the pool of characters
    :type: str ("strict", "normal" or "lenient")

    :param character_pool: pool of characters to use
    :type: CharacterPool

    :return: Entropy of password
    :type: float
    """
    # set default char pool if user doesn't pass one
    if character_pool is None:
        pool = CharacterPool()
    # else set user char pool, should be init'd or have viable class methods
    else:
        pool = character_pool

    # all chars must be in password char pool
    if not all(i in pool.all for i in password):
        raise UnacceptableCharacters(
            f"You can only use characters from the character pool, "
            f"which are: {pool.all}")
    else:
        number_of_possible_passwords = calculate_number_of_possible_passwords(
            password, method, pool)
        return math.log(number_of_possible_passwords, 2)
Example #2
0
def test_normal_evaluation_triples():
    pool = CharacterPool()
    # triples
    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.lowercase | pool.uppercase | pool.symbols
    )
    assert normal_pool_of_unique_characters(password) == 26 + 26 + 32

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.lowercase | pool.uppercase | pool.numbers
    )
    assert normal_pool_of_unique_characters(password) == len(pool.alphanumeric)
    assert normal_pool_of_unique_characters(password) == 26 + 26 + 10

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.lowercase | pool.symbols | pool.numbers
    )
    assert normal_pool_of_unique_characters(password) == 26 + 32 + 10
    #
    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.uppercase | pool.symbols | pool.numbers
    )
    assert normal_pool_of_unique_characters(password) == 26 + 32 + 10
def random_pool():
    pool = CharacterPool(
        lowercase="123xyz",
        uppercase="ABC!",
        symbols="<@>",
        whitespace="to",
        numbers="cvbnm,",
        other="65;",
    )
    yield pool
Example #4
0
def test_normal_evaluation_quadruples():
    pool = CharacterPool()
    # quadruple
    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.lowercase
        | pool.uppercase
        | pool.symbols
        | pool.numbers
    )
    assert normal_pool_of_unique_characters(password) == 94
Example #5
0
    def __init__(self, password: str, character_pool: CharacterPool = None):
        # set character pool
        if character_pool is None:
            self.pool = CharacterPool()
        else:
            self.pool = character_pool

        assert all(
            i in self.pool.all for i in password
        ), "A password can only use characters from the character_pool provided"

        # set password
        self.password = password

        # set the number of lowercase in the password
        self.lowercase = len([
            character for character in password
            if character in self.pool.lowercase
        ])

        # set the uppercase of lowercase in the password
        self.uppercase = len([
            character for character in password
            if character in self.pool.uppercase
        ])

        # set the symbols of lowercase in the password
        self.symbols = len([
            character for character in password
            if character in self.pool.symbols
        ])

        # set the numbers of lowercase in the password
        self.numbers = len([
            character for character in password
            if character in self.pool.numbers
        ])

        # set the numbers of whitespace in the password
        self.whitespace = len([
            character for character in password
            if character in self.pool.whitespace
        ])

        # set the other of lowercase in the password
        self.other = len([
            character for character in password if character in self.pool.other
        ])

        # set the length of the password
        self.length = len(password)

        # set entropy
        self.entropy = calculate_entropy(password, character_pool=self.pool)
Example #6
0
def test_character_pool():
    pool = CharacterPool()

    assert pool.lowercase == set("abcdefghijklmnopqrstuvwxyz")
    assert pool.uppercase == set("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    assert pool.numbers == set("0123456789")
    assert pool.symbols == set(r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~""")
    assert pool.whitespace == set(" ")
    assert pool.other == set()
    assert pool.letters == pool.lowercase | pool.uppercase
    assert pool.alphanumeric == pool.letters | pool.numbers
    assert pool.all == pool.alphanumeric | pool.symbols | pool.whitespace | pool.other
Example #7
0
def test_character_pool_init_with_kwargs():
    pool = CharacterPool(
        lowercase="abc",
        uppercase="XYZ",
        numbers="123",
        symbols="()",
        whitespace=" ",
        other="é",
    )

    assert pool.lowercase == set("abc")
    assert pool.uppercase == set("XYZ")
    assert pool.numbers == set("123")
    assert pool.symbols == set("()")
    assert pool.whitespace == set(" ")
    assert pool.other == set("é")
    assert pool.letters == pool.lowercase | pool.uppercase
    assert pool.alphanumeric == pool.letters | pool.numbers
    assert pool.all == pool.alphanumeric | pool.symbols | pool.whitespace | pool.other
Example #8
0
def test_strict_evaluation():
    pool = CharacterPool()

    password = "******"
    assert strict_pool_of_unique_characters(password) == len(password)
    assert strict_pool_of_unique_characters(password) == 10

    password = "******"
    assert strict_pool_of_unique_characters(password) == len(set(password))
    assert strict_pool_of_unique_characters(password) == 4

    password = "******"
    assert strict_pool_of_unique_characters(password) == 6

    password = "******"
    assert strict_pool_of_unique_characters(password) == 10

    password = "******"
    assert strict_pool_of_unique_characters(password) == 12

    password = "******"
    assert strict_pool_of_unique_characters(password) == 11

    password = pool.lowercase
    assert strict_pool_of_unique_characters(password) == len(pool.lowercase)
    assert strict_pool_of_unique_characters(password) == 26

    password = pool.uppercase
    assert strict_pool_of_unique_characters(password) == len(pool.uppercase)
    assert strict_pool_of_unique_characters(password) == 26

    password = pool.numbers
    assert strict_pool_of_unique_characters(password) == len(pool.numbers)
    assert strict_pool_of_unique_characters(password) == 10

    password = pool.whitespace
    assert strict_pool_of_unique_characters(password) == len(pool.whitespace)
    assert strict_pool_of_unique_characters(password) == 1

    password = pool.symbols
    assert strict_pool_of_unique_characters(password) == len(pool.symbols)
    assert strict_pool_of_unique_characters(password) == 32
def calculate_number_of_possible_passwords(
        password: str,
        method: str = "normal",
        character_pool: CharacterPool = None):
    """
    Calculate the number of possible passwords according to the formula:
    size of pool of characters (int) ^ number of characters in password (int)

    :param password: the password
    :type: str

    :param method: method to calculate the pool of characters
    :type: str ("strict", "normal" or "lenient")

    :param character_pool: pool of characters to use
    :type: CharacterPool

    :return: Number Of Possible Passwords
    :type: int
    """
    # set default char pool if user doesn't pass one
    if character_pool is None:
        pool = CharacterPool()
    # else set user char pool, should be init'd or have viable class methods
    else:
        pool = character_pool

    # user can pick between strict, normal and lenient
    if method == "strict":
        pool_of_characters = strict_pool_of_unique_characters(password)
    elif method == "normal":
        pool_of_characters = normal_pool_of_unique_characters(
            password, character_pool=pool)
    elif method == "lenient":
        pool_of_characters = lenient_pool_of_unique_characters(
            character_pool=pool)
    else:
        raise ValueError(
            'method must be either "strict", "normal" or "lenient"')

    # return pool of chars ^ len password
    return pool_of_characters**len(password)
Example #10
0
def test_normal_evaluation_doubles():
    pool = CharacterPool()
    # doubles
    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.lowercase | pool.symbols
    )
    assert normal_pool_of_unique_characters(password) == 26 + 32

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.lowercase | pool.uppercase
    )
    assert normal_pool_of_unique_characters(password) == len(pool.letters)
    assert normal_pool_of_unique_characters(password) == 26 + 26

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.lowercase | pool.numbers
    )
    assert normal_pool_of_unique_characters(password) == 26 + 10

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.uppercase | pool.symbols
    )
    assert normal_pool_of_unique_characters(password) == 26 + 32

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.uppercase | pool.numbers
    )
    assert normal_pool_of_unique_characters(password) == 26 + 10

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(
        pool.uppercase | pool.numbers
    )
    assert normal_pool_of_unique_characters(password) == 26 + 10
Example #11
0
def test_normal_evaluation_singles():
    pool = CharacterPool()
    # singles
    password = "******"
    assert normal_pool_of_unique_characters(password) == len(pool.lowercase)
    assert normal_pool_of_unique_characters(password) == 26

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(pool.uppercase)
    assert normal_pool_of_unique_characters(password) == 26

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(pool.symbols)
    assert normal_pool_of_unique_characters(password) == 32

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(pool.numbers)
    assert normal_pool_of_unique_characters(password) == 10

    password = "******"
    assert normal_pool_of_unique_characters(password) == len(pool.whitespace)
    assert normal_pool_of_unique_characters(password) == 1
Example #12
0
def test_password_policy():
    # check object
    policy = PasswordPolicy()
    assert policy
    assert isinstance(policy, PasswordPolicy)

    # check attrs
    assert policy.lowercase == 0
    assert policy.uppercase == 0
    assert policy.symbols == 0
    assert policy.numbers == 0
    assert policy.whitespace == 0
    assert policy.other == 0
    assert policy.min_length == 12
    assert policy.max_length == 128
    assert policy.min_entropy == 32
    assert policy.classification == "Weak"
    assert policy.forbidden_words == []
    assert policy.pool

    assert policy.to_dict()
    assert isinstance(policy.to_dict(), dict)
    assert policy.to_dict() == dict(
        lowercase=0,
        uppercase=0,
        symbols=0,
        numbers=0,
        whitespace=0,
        other=0,
        min_length=12,
        max_length=128,
        entropy=32,
        classification="Weak",
        forbidden_words=[],
        character_pool=CharacterPool().to_dict(),
    )
Example #13
0
def test_lenient_evaluation():
    pool = CharacterPool()
    assert lenient_pool_of_unique_characters() == len(pool.all)
    assert lenient_pool_of_unique_characters() == 95
def hex_pool():
    pool = CharacterPool(lowercase="abcdef",
                         uppercase="ABCDEF",
                         symbols="",
                         whitespace="")
    yield pool
Example #15
0
    def __init__(
        self,
        lowercase: int = 0,
        uppercase: int = 0,
        symbols: int = 0,
        numbers: int = 0,
        whitespace: int = 0,
        other: int = 0,
        min_length: int = 12,
        max_length: int = 128,
        min_entropy: typing.Union[int, float] = 32,
        forbidden_words: list = None,
        character_pool: CharacterPool = None,
        requirement_cls: PasswordRequirement = None,
        classifier: Classifier = None,
    ):
        # set character pool if not passed
        if character_pool is None:
            self.pool = CharacterPool()
        else:
            self.pool = character_pool

        # set requirement class if not passed
        if requirement_cls is None:
            self.requirement_cls = PasswordRequirement
        else:
            self.requirement_cls = requirement_cls

        # check lowercase value acceptable
        # first check is int
        assert isinstance(
            lowercase, int
        ), "lowercase (the minimum number of lowercase characters) must be int"
        # then check it is a value between 0 and the number of
        # lowercase characters in the character pool (ascii is 0-26)
        assert 0 <= lowercase <= len(self.pool.lowercase), (
            f"lowercase (the minimum number of lowercase characters) must be "
            f"between 0 and {len(self.pool.lowercase)} inclusive")
        self.lowercase = lowercase
        self.lowercase_requirement = MakePasswordRequirement(
            "the minimum number of lowercase characters",
            self.lowercase,
            cls=requirement_cls,
        )

        # check uppercase value acceptable
        # first check is int
        assert isinstance(
            uppercase, int
        ), "uppercase (the minimum number of uppercase characters) must be int"
        # then check it is a value between 0 and the number of
        # lowercase characters in the character pool (ascii is 0-26)
        assert 0 <= uppercase <= len(self.pool.uppercase), (
            f"uppercase (the minimum number of uppercase characters) must be "
            f"between 0 and {len(self.pool.uppercase)} inclusive")
        self.uppercase = uppercase
        self.uppercase_requirement = MakePasswordRequirement(
            "the minimum number of uppercase characters",
            self.uppercase,
            cls=requirement_cls,
        )

        # check numbers value acceptable
        # first check is int
        assert isinstance(
            numbers, int
        ), "numbers (the minimum number of number characters) must be int"
        # then check it is a value between 0 and the number of
        # number characters in the character pool (ascii is 0-9)
        assert 0 <= numbers <= len(self.pool.numbers), (
            f"numbers (the minimum number of number characters) must be "
            f"between 0 and {len(self.pool.numbers)} inclusive")
        self.numbers = numbers
        self.numbers_requirement = MakePasswordRequirement(
            "the minimum number of number characters",
            self.numbers,
            cls=requirement_cls)

        # check symbols value acceptable
        # first check is int
        assert isinstance(
            symbols, int
        ), "symbols (the minimum number of symbol characters) must be int"
        # then check it is a value between 0 and the number of
        # number characters in the character pool (ascii is 0-32)
        # although for symbols this is debatable, some might not include
        # particular symbol characters like '\' or ';', etc
        # this is overridable, like all pool features depending on use case
        assert 0 <= symbols <= len(self.pool.symbols), (
            f"symbols (the minimum number of symbol characters) must be "
            f"between 0 and {len(self.pool.symbols)} inclusive")
        self.symbols = symbols
        self.symbols_requirement = MakePasswordRequirement(
            "the minimum number of symbol characters",
            self.symbols,
            cls=requirement_cls)

        # check whitespace value acceptable
        # first check is int
        assert isinstance(whitespace, int), (
            "whitespace (the minimum number of whitespace characters) must be "
            "int")
        # then check it is a value between 0 and the number of
        # number characters in the character pool (ascii is 0-5)
        assert 0 <= whitespace <= len(self.pool.whitespace), (
            f"whitespace (the minimum number of whitespace characters) must be "
            f"between 0 and {len(self.pool.whitespace)} inclusive")
        self.whitespace = whitespace
        self.whitespace_requirement = MakePasswordRequirement(
            "the minimum number of whitespace characters",
            self.whitespace,
            cls=requirement_cls,
        )

        # check other value acceptable
        # first check is int
        assert isinstance(
            other,
            int), "other (the minimum number of other characters) must be int"
        # then check it is a value between 0 and the number of
        # other characters in the character pool (ascii is 0)
        # this can be used as a bucket by developers that want to allow
        # other characters
        assert 0 <= other <= len(self.pool.other), (
            f"other (the minimum number of other characters) must be "
            f"between 0 and {len(self.pool.other)} inclusive")
        self.other = other
        self.other_requirement = MakePasswordRequirement(
            "the minimum number of other characters",
            self.other,
            cls=requirement_cls)

        # check min_length value acceptable
        # check is int
        assert isinstance(
            min_length,
            int), "min_length (the minimum password length) must be int"
        # check max_length value acceptable
        # check is int
        assert isinstance(
            max_length,
            int), "max_length (the maximum password length) must be int"
        # then check one is
        assert 0 <= min_length <= max_length, (
            "the min_length (minimum password length) cannot be smaller than "
            "the max_length (maximum password length) and must be larger than "
            "0. However the min_length and max_length can be equal if the user "
            "desires a single length for all passwords")
        self.min_length = min_length
        self.min_length_requirement = MakePasswordRequirement(
            "the minimum password length",
            self.min_length,
            cls=requirement_cls,
        )

        self.max_length = max_length
        self.max_length_requirement = MakePasswordRequirement(
            "the maximum password length",
            self.max_length,
            func=less_than_or_equal_to,
            cls=requirement_cls,
        )

        assert isinstance(min_entropy,
                          (int, float)), "entropy must be an int or float"
        assert 0 < min_entropy, "entropy must be greater than 0"
        self.min_entropy = min_entropy
        self.entropy_requirement = MakePasswordRequirement("entropy",
                                                           self.min_entropy,
                                                           cls=requirement_cls)

        self.forbidden_words = forbidden_words if forbidden_words else []
        assert isinstance(self.forbidden_words,
                          list), "forbidden words must be a list"
        for word in self.forbidden_words:
            assert isinstance(word, str), "all forbidden words must be strings"
        self.forbidden_words_requirements = MakePasswordRequirement(
            "forbidden words",
            self.forbidden_words,
            cls=requirement_cls,
            func=not_in,
        )

        # set a classifier if not passed
        # with default values of:
        # "Very Weak" is entropy between 0 to 28
        # "Weak" is entropy between 28 to 35
        # "Ok" is entropy between 35 to 59
        # "Good" is entropy between 59 to 127
        # "Very Good" is entropy above 127
        if classifier is None:
            self.classifier = Classifier()
        else:
            self.classifier = classifier

        # set a classification level from the entropy value
        self.classification = self.classifier.classify(self.min_entropy)
Example #16
0
class PasswordPolicy:
    """
    The password policy is where one can define what they expect of a password
    when submitted by a user.

    The default policy, is that a user chooses a password of at least 12
    characters, but there is no requirement to use an amount of particular
    character types, e.g. symbols

    :param lowercase (int): the minimum number of lowercase characters in a
                            password
    :param uppercase (int): the minimum number of uppercase characters in a
                            password
    :param symbols (int): the minimum number of symbol characters in a password
    :param numbers (int): the minimum number of number characters in a password
    :param other (int): the minimum number of other characters in a password
    :param whitespace (int): the minimum number of whitespace characters in a
                             password
    :param min_length (int): the minimum length for a password
    :param max_length (int): the maximum length for a password
    :param forbidden_words (list(str)): a list of forbidden words as strings
    :param character_pool (CharacterPool): the pool or characters to pick from
    """
    def __init__(
        self,
        lowercase: int = 0,
        uppercase: int = 0,
        symbols: int = 0,
        numbers: int = 0,
        whitespace: int = 0,
        other: int = 0,
        min_length: int = 12,
        max_length: int = 128,
        min_entropy: typing.Union[int, float] = 32,
        forbidden_words: list = None,
        character_pool: CharacterPool = None,
        requirement_cls: PasswordRequirement = None,
        classifier: Classifier = None,
    ):
        # set character pool if not passed
        if character_pool is None:
            self.pool = CharacterPool()
        else:
            self.pool = character_pool

        # set requirement class if not passed
        if requirement_cls is None:
            self.requirement_cls = PasswordRequirement
        else:
            self.requirement_cls = requirement_cls

        # check lowercase value acceptable
        # first check is int
        assert isinstance(
            lowercase, int
        ), "lowercase (the minimum number of lowercase characters) must be int"
        # then check it is a value between 0 and the number of
        # lowercase characters in the character pool (ascii is 0-26)
        assert 0 <= lowercase <= len(self.pool.lowercase), (
            f"lowercase (the minimum number of lowercase characters) must be "
            f"between 0 and {len(self.pool.lowercase)} inclusive")
        self.lowercase = lowercase
        self.lowercase_requirement = MakePasswordRequirement(
            "the minimum number of lowercase characters",
            self.lowercase,
            cls=requirement_cls,
        )

        # check uppercase value acceptable
        # first check is int
        assert isinstance(
            uppercase, int
        ), "uppercase (the minimum number of uppercase characters) must be int"
        # then check it is a value between 0 and the number of
        # lowercase characters in the character pool (ascii is 0-26)
        assert 0 <= uppercase <= len(self.pool.uppercase), (
            f"uppercase (the minimum number of uppercase characters) must be "
            f"between 0 and {len(self.pool.uppercase)} inclusive")
        self.uppercase = uppercase
        self.uppercase_requirement = MakePasswordRequirement(
            "the minimum number of uppercase characters",
            self.uppercase,
            cls=requirement_cls,
        )

        # check numbers value acceptable
        # first check is int
        assert isinstance(
            numbers, int
        ), "numbers (the minimum number of number characters) must be int"
        # then check it is a value between 0 and the number of
        # number characters in the character pool (ascii is 0-9)
        assert 0 <= numbers <= len(self.pool.numbers), (
            f"numbers (the minimum number of number characters) must be "
            f"between 0 and {len(self.pool.numbers)} inclusive")
        self.numbers = numbers
        self.numbers_requirement = MakePasswordRequirement(
            "the minimum number of number characters",
            self.numbers,
            cls=requirement_cls)

        # check symbols value acceptable
        # first check is int
        assert isinstance(
            symbols, int
        ), "symbols (the minimum number of symbol characters) must be int"
        # then check it is a value between 0 and the number of
        # number characters in the character pool (ascii is 0-32)
        # although for symbols this is debatable, some might not include
        # particular symbol characters like '\' or ';', etc
        # this is overridable, like all pool features depending on use case
        assert 0 <= symbols <= len(self.pool.symbols), (
            f"symbols (the minimum number of symbol characters) must be "
            f"between 0 and {len(self.pool.symbols)} inclusive")
        self.symbols = symbols
        self.symbols_requirement = MakePasswordRequirement(
            "the minimum number of symbol characters",
            self.symbols,
            cls=requirement_cls)

        # check whitespace value acceptable
        # first check is int
        assert isinstance(whitespace, int), (
            "whitespace (the minimum number of whitespace characters) must be "
            "int")
        # then check it is a value between 0 and the number of
        # number characters in the character pool (ascii is 0-5)
        assert 0 <= whitespace <= len(self.pool.whitespace), (
            f"whitespace (the minimum number of whitespace characters) must be "
            f"between 0 and {len(self.pool.whitespace)} inclusive")
        self.whitespace = whitespace
        self.whitespace_requirement = MakePasswordRequirement(
            "the minimum number of whitespace characters",
            self.whitespace,
            cls=requirement_cls,
        )

        # check other value acceptable
        # first check is int
        assert isinstance(
            other,
            int), "other (the minimum number of other characters) must be int"
        # then check it is a value between 0 and the number of
        # other characters in the character pool (ascii is 0)
        # this can be used as a bucket by developers that want to allow
        # other characters
        assert 0 <= other <= len(self.pool.other), (
            f"other (the minimum number of other characters) must be "
            f"between 0 and {len(self.pool.other)} inclusive")
        self.other = other
        self.other_requirement = MakePasswordRequirement(
            "the minimum number of other characters",
            self.other,
            cls=requirement_cls)

        # check min_length value acceptable
        # check is int
        assert isinstance(
            min_length,
            int), "min_length (the minimum password length) must be int"
        # check max_length value acceptable
        # check is int
        assert isinstance(
            max_length,
            int), "max_length (the maximum password length) must be int"
        # then check one is
        assert 0 <= min_length <= max_length, (
            "the min_length (minimum password length) cannot be smaller than "
            "the max_length (maximum password length) and must be larger than "
            "0. However the min_length and max_length can be equal if the user "
            "desires a single length for all passwords")
        self.min_length = min_length
        self.min_length_requirement = MakePasswordRequirement(
            "the minimum password length",
            self.min_length,
            cls=requirement_cls,
        )

        self.max_length = max_length
        self.max_length_requirement = MakePasswordRequirement(
            "the maximum password length",
            self.max_length,
            func=less_than_or_equal_to,
            cls=requirement_cls,
        )

        assert isinstance(min_entropy,
                          (int, float)), "entropy must be an int or float"
        assert 0 < min_entropy, "entropy must be greater than 0"
        self.min_entropy = min_entropy
        self.entropy_requirement = MakePasswordRequirement("entropy",
                                                           self.min_entropy,
                                                           cls=requirement_cls)

        self.forbidden_words = forbidden_words if forbidden_words else []
        assert isinstance(self.forbidden_words,
                          list), "forbidden words must be a list"
        for word in self.forbidden_words:
            assert isinstance(word, str), "all forbidden words must be strings"
        self.forbidden_words_requirements = MakePasswordRequirement(
            "forbidden words",
            self.forbidden_words,
            cls=requirement_cls,
            func=not_in,
        )

        # set a classifier if not passed
        # with default values of:
        # "Very Weak" is entropy between 0 to 28
        # "Weak" is entropy between 28 to 35
        # "Ok" is entropy between 35 to 59
        # "Good" is entropy between 59 to 127
        # "Very Good" is entropy above 127
        if classifier is None:
            self.classifier = Classifier()
        else:
            self.classifier = classifier

        # set a classification level from the entropy value
        self.classification = self.classifier.classify(self.min_entropy)

    def to_dict(self) -> dict:
        rv = {
            "lowercase": self.lowercase,
            "uppercase": self.uppercase,
            "symbols": self.symbols,
            "numbers": self.numbers,
            "whitespace": self.whitespace,
            "other": self.other,
            "min_length": self.min_length,
            "max_length": self.max_length,
            "entropy": self.min_entropy,
            "forbidden_words": self.forbidden_words,
            "classification": self.classification,
            "character_pool": self.pool.to_dict(),
        }
        return rv

    def test_password(self, password: str, failures_only: bool = True):
        password = _make_password(password)
        validity = [
            self.lowercase_requirement(password.lowercase),
            self.uppercase_requirement(password.uppercase),
            self.numbers_requirement(password.numbers),
            self.symbols_requirement(password.symbols),
            self.whitespace_requirement(password.whitespace),
            self.other_requirement(password.other),
            self.min_length_requirement(password.length),
            self.max_length_requirement(password.length),
            self.entropy_requirement(password.entropy),
            self.forbidden_words_requirements(password.password),
        ]
        return [i for i in validity if not i] if failures_only else validity

    def validate(self, password):
        return not bool(self.test_password(password))