Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,25 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [2.7, 3.6, 3.7, 3.8, 3.9]
python-version:
["3.8.*", "3.9.*", "3.10.*", "3.11.*", "3.12.*", "3.13.*"]
steps:
- uses: actions/checkout@master
- name: set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U setuptools
pip install -r requirements.txt
pip install .
- name: Run mypy
if: "matrix.python-version != '2.7' && matrix.python-version != 'pypy2'"
run: |
- uses: actions/checkout@master
- name: set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U setuptools
pip install -r requirements.txt
pip install .
pip install tox
- name: Run mypy
run: |
pip install -U mypy
mypy -p zxcvbn --ignore-missing-imports
- name: Run tests
run: |
pytest -v
- name: Test Compatibility
run: |
python tests/test_compatibility.py tests/password_expected_value.json
- name: Run tests
run: |
tox
19 changes: 17 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ time.

Features
--------
- **Tested in Python versions 2.7, 3.6-3.9**
- **Tested in Python versions 3.8-3.13**
- Accepts user data to be added to the dictionaries that are tested against (name, birthdate, etc)
- Gives a score to the password, from 0 (terrible) to 4 (great)
- Provides feedback on the password and ways to improve it
Expand Down Expand Up @@ -103,6 +103,18 @@ Output:
}],
}

Another optional argument is ``max_length``, allowing override of the default max password length of 72.
.. code:: python

from zxcvbn import zxcvbn

results = zxcvbn('JohnSmith321', user_inputs=['John', 'Smith'], max_length=88)

.. warning::

We strongly advise against setting ``max_length`` greater than 72,
as it can lead to long processing times and may leave server-side applications open
to denial-of-service scenarios.

Custom Ranked Dictionaries
--------------------------
Expand All @@ -121,18 +133,21 @@ In order to support more languages or just add password dictionaries of your own
These lists will be added to the current ones, but you can also overwrite the current ones if you wish.
The lists you add should be in order of how common the word is used with the most common words appearing first.


CLI
~~~

You an also use zxcvbn from the command line::

echo 'password' | zxcvbn --user-input <user-input> | jq

You can include a ``--max-length`` argument::
echo '<long password>' | zxcvbn --max-length 142

You can also execute the zxcvbn module::

echo 'password' | python -m zxcvbn --user-input <user-input> | jq


Contribute
----------

Expand Down
6 changes: 5 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pytest==3.5.0
# For older Python versions < 3.6 install Pytest 3.5.0
pytest==3.5.0; python_version < "3.6"

# For Python 3.6+, install a more modern Pytest:
pytest==7.4.2; python_version >= "3.6"
12 changes: 12 additions & 0 deletions tests/l33t_exploit_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import pytest
from zxcvbn import zxcvbn

# Test ACsploit-generated password targeting zxcvbn's l33t matching algorithm
# (see https://github.com/GoSimpleLLC/nbvcxz/issues/60)
def test_l33t_exploit():

password = "4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/4@8({[</369&#!1/|0$5+7%2/"

# Function should raise ValueError for input exceeding default max_length of 72 chars
with pytest.raises(ValueError, match="Password exceeds max length of 72 characters"):
zxcvbn(password, user_inputs=[None])
3 changes: 2 additions & 1 deletion tests/zxcvbn_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import pytest
from zxcvbn import zxcvbn


Expand All @@ -23,7 +24,7 @@ def test_long_password():
input_ = None
password = "weopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioejiojweopiopdsjmkldjvoisdjfioej"

zxcvbn(password, user_inputs=[input_])
zxcvbn(password, user_inputs=[input_], max_length=316)


def test_dictionary_password():
Expand Down
10 changes: 10 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[tox]
envlist = py38, py39, py310, py311, py312, py313
isolated_build = True

[testenv]
deps =
pytest
commands =
pytest
python tests/test_compatibility.py tests/password_expected_value.json
8 changes: 7 additions & 1 deletion zxcvbn/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import os
from datetime import datetime

from . import matching, scoring, time_estimates, feedback

def zxcvbn(password, user_inputs=None):

def zxcvbn(password, user_inputs=None, max_length=72):
# Throw error if password exceeds max length
if len(password) > max_length:
raise ValueError(f"Password exceeds max length of {max_length} characters.")

try:
# Python 2 string types
basestring = (str, unicode)
Expand Down
8 changes: 7 additions & 1 deletion zxcvbn/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
help='user data to be added to the dictionaries that are tested against '
'(name, birthdate, etc)',
)
parser.add_argument(
'--max-length',
default=72,
type=int,
help='Override password max length (default: 72)'
)

class JSONEncoder(json.JSONEncoder):
def default(self, o):
Expand All @@ -36,7 +42,7 @@ def cli():
else:
password = getpass.getpass()

res = zxcvbn(password, user_inputs=args.user_input)
res = zxcvbn(password, user_inputs=args.user_input, max_length=args.max_length)
json.dump(res, sys.stdout, indent=2, cls=JSONEncoder)
sys.stdout.write('\n')

Expand Down