Metadata-Version: 2.1
Name: Password-Validation
Version: 0.0.1
Summary: UNKNOWN
Home-page: https://github.com/jackwardell/PasswordValidation
Author: Jack Wardell
License: UNKNOWN
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.6
Description-Content-Type: text/markdown

# Password Validation

## Aim
This library aims to allow the programmer to simply validate passwords according to their desired policy. 

## Flow
How does it work:
* You create password policy
* You test passwords against that policy
* The passwords that abide by the policy are valid, those that don't abide are invalid

Simple.


## Password Guidelines
I recommend reading the 2017 NIST guidelines:
* Verifiers should not impose composition rules e.g., requiring mixtures of different character types or prohibiting consecutively repeated characters
* Verifiers should not require passwords to be changed arbitrarily or regularly e.g. the previous 90 day rule
* Passwords must be at least 8 characters in length
* Password systems should permit subscriber-chosen passwords at least 64 characters in length.
* All printing ASCII characters, the space character, and Unicode characters should be acceptable in passwords
* When establishing or changing passwords, the verifier shall advise the subscriber that they need to select a different password if they have chosen a weak or compromised password
* Verifiers should offer guidance such as a password-strength meter, to assist the user in choosing a strong password
* Verifiers shall store passwords in a form that is resistant to offline attacks. Passwords shall be salted and hashed using a suitable one-way key derivation function. Key derivation functions take a password, a salt, and a cost factor as inputs then generate a password hash. Their purpose is to make each password guessing trial by an attacker who has obtained a password hash file expensive and therefore the cost of a guessing attack high or prohibitive.

Personal headline points: 
* Don't enforce bizarre convention e.g. 1 lowercase, 1 uppercase, 1 number, 1 symbol, etc
* Don't make make users change them regularly
* Make it at least 12 characters long
* AND SALT AND HASH THEM WHEN PERSISTING

The XKCD comic puts it best: https://xkcd.com/936/


## How to use

### Install
Install it:
```
pip install password_validation
```


### How 
Example:
```
>>> from password_validation import PasswordPolicy
>>> policy = PasswordPolicy()
>>> policy.validate("hello-this-is-quite-a-good-password")
True

>>> policy.validate("password")
False

>>> test = policy.test_password("hello")
>>> test
[<RequirementUnfulfilled('the minimum password length', statement=(5 >= 12))>,
 <RequirementUnfulfilled('entropy', statement=(23.50219859070546 >= 32))>]
```
In the above example "password" is not valid, because it's not more than 12 characters (which is a default requirement). And "hello" isn't valid either because it's too short and too low in entropy.

You can, when using `test_password` get back a list of unfulfilled requirements.

Or you can see all the requirements:
```
>>> from password_validation import PasswordPolicy
>>> policy = PasswordPolicy()
>>> policy.test_password("goodbye", failure_only=False)
[<RequirementFulfilled('the minimum number of lowercase characters', statement=(7 >= 0))>,
 <RequirementFulfilled('the minimum number of uppercase characters', statement=(0 >= 0))>,
 <RequirementFulfilled('the minimum number of number characters', statement=(0 >= 0))>,
 <RequirementFulfilled('the minimum number of symbol characters', statement=(0 >= 0))>,
 <RequirementFulfilled('the minimum number of whitespace characters', statement=(0 >= 0))>,
 <RequirementFulfilled('the minimum number of other characters', statement=(0 >= 0))>,
 <RequirementUnfulfilled('the minimum password length', statement=(7 >= 12))>,
 <RequirementFulfilled('the maximum password length', statement=(7 <= 128))>,
 <RequirementFulfilled('entropy', statement=(32.90307802698764 >= 32))>,
 <RequirementFulfilled('forbidden words', statement=("goodbye" not in []))>]
```

The `__init__` looks something like this:
```
class PasswordPolicy:
    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,
        entropy: typing.Union[int, float] = 32,
        forbidden_words: list = None
    )
```
Features in your policy can include:
* number of lowercase characters (default 0) `PasswordPolicy(lowercase=1)`
* number of uppercase characters (default 0) `PasswordPolicy(uppercase=1)` 
* number of symbols characters (default 0) `PasswordPolicy(symbols=1)` 
* number of number characters (default 0) `PasswordPolicy(numbers=1)` 
* number of whitespace characters (default 0) `PasswordPolicy(whitespace=1)`
* number of other characters (default 0) `PasswordPolicy(other=1)`
* minimum password length (default 12) `PasswordPolicy(min_length=1)` 
* maximum password length (default 128)`PasswordPolicy(max_length=1)` 
* minimum password entropy (default 32) `PasswordPolicy(entropy=1)` 
* a list of forbidden words `PasswordPolicy(forbidden_words=['password'])` 

FYI other characters is if you wanted to add non-ascii characters

#### Flask example
```
from password_validation import PasswordPolicy

policy = PasswordPolicy()

@app.route("/register")
def register():
    password = request.form.get("password")
    if policy.validate(password):
        # create user
    else:
        for requirement in policy.test_password(password):
            alert = f"{requirement.name} not satisfied: expected: {requirement.requirement}, got: {requirement.actual}"
            flash(alert)
    return render_template("register.html")    
```


You can also get your fields using `policy.to_dict()`


#### Character Pool

If you don't like the default characters (ascii) you can make your own `CharacterPool`:

```
hex_pool = CharacterPool(
    lowercase="",
    uppercase="ABCDEF",
    numbers="0123456789",
    symbols="",
    whitespace="",
    other="",
)
```
or 
```
random_pool = CharacterPool(
    lowercase="åéîøü",
    uppercase="XYZ",
    numbers="123",
    symbols=".",
    whitespace="",
    other="",
)
```
You can then pass to the policy:
```
policy = PasswordPolicy(character_pool=hex_pool)
```
and the same logic will apply but for that pool



