commit
86484a091f
7 changed files with 325 additions and 0 deletions
@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License |
||||
|
||||
Copyright (c) 2021, Gergely Polonkai |
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this |
||||
list of conditions and the following disclaimer. |
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, |
||||
this list of conditions and the following disclaimer in the documentation |
||||
and/or other materials provided with the distribution. |
||||
|
||||
3. Neither the name of the copyright holder nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,6 @@
|
||||
================ |
||||
Passlib-Werkzeug |
||||
================ |
||||
|
||||
Passlib handlers to understand password hashes generated by Werkzeug’s ``generate_password_hash``, |
||||
and to generate hashes understood by ``check_password_hash``. |
@ -0,0 +1,65 @@
|
||||
"""Passlib module to understand hashed password format of Werkzeug |
||||
""" |
||||
|
||||
__version__ = '0.1.0' |
||||
|
||||
from base64 import b64encode |
||||
|
||||
from passlib.crypto.digest import pbkdf2_hmac |
||||
from passlib.exc import InvalidHashError |
||||
from passlib.registry import register_crypt_handler_path |
||||
import passlib.utils.handlers as uh |
||||
from passlib.utils.binary import AB64_CHARS, ab64_decode, ab64_encode |
||||
from passlib.utils.compat import u |
||||
|
||||
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" |
||||
|
||||
|
||||
class werkzeug_pbkdf2_sha256(uh.HasSalt, uh.HasRounds, uh.GenericHandler): |
||||
checksum_chars = AB64_CHARS |
||||
checksum_size = 64 |
||||
default_rounds = 150000 |
||||
default_salt_chars = SALT_CHARS |
||||
default_salt_size = 8 |
||||
_digest = 'sha256' |
||||
ident = u('pbkdf2:sha256') |
||||
max_rounds = 0xffffffff |
||||
max_salt_size = None |
||||
min_salt_size = 1 |
||||
name = 'werkzeug_pbkdf2_sha256' |
||||
salt_chars = SALT_CHARS |
||||
setting_kwds = ('salt', 'salt_size') |
||||
werkzeug_name = 'pbkdf2:sha256' |
||||
|
||||
@classmethod |
||||
def from_string(cls, password_hash): |
||||
"""Create a new hash object from a string |
||||
""" |
||||
|
||||
try: |
||||
full_method, salt, checksum = password_hash.split('$') |
||||
except ValueError as exc: |
||||
raise InvalidHashError(cls) from exc |
||||
|
||||
method, digest, rounds = full_method.split(':') |
||||
|
||||
if ':'.join((method, digest)) != cls.ident: |
||||
raise InvalidHashError(cls) |
||||
|
||||
if checksum: |
||||
bytes.fromhex(checksum) |
||||
|
||||
return cls(salt=salt, checksum=checksum, rounds=int(rounds)) |
||||
|
||||
def to_string(self): |
||||
full_method = ':'.join((self.ident, str(self.rounds))) |
||||
|
||||
return '$'.join((full_method, self.salt, self.checksum)) |
||||
|
||||
def _calc_checksum(self, secret): |
||||
checksum = pbkdf2_hmac(self._digest, secret, self.salt, self.rounds) |
||||
|
||||
return checksum.hex() |
||||
|
||||
|
||||
register_crypt_handler_path('werkzeug_pbkdf2_sha256', 'passlib_werkzeug') |
@ -0,0 +1,186 @@
|
||||
[[package]] |
||||
name = "atomicwrites" |
||||
version = "1.4.0" |
||||
description = "Atomic file writes." |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" |
||||
|
||||
[[package]] |
||||
name = "attrs" |
||||
version = "20.3.0" |
||||
description = "Classes Without Boilerplate" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" |
||||
|
||||
[package.extras] |
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] |
||||
docs = ["furo", "sphinx", "zope.interface"] |
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] |
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] |
||||
|
||||
[[package]] |
||||
name = "colorama" |
||||
version = "0.4.4" |
||||
description = "Cross-platform colored terminal text." |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" |
||||
|
||||
[[package]] |
||||
name = "more-itertools" |
||||
version = "8.7.0" |
||||
description = "More routines for operating on iterables, beyond itertools" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=3.5" |
||||
|
||||
[[package]] |
||||
name = "packaging" |
||||
version = "20.9" |
||||
description = "Core utilities for Python packages" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" |
||||
|
||||
[package.dependencies] |
||||
pyparsing = ">=2.0.2" |
||||
|
||||
[[package]] |
||||
name = "passlib" |
||||
version = "1.7.4" |
||||
description = "comprehensive password hashing framework supporting over 30 schemes" |
||||
category = "main" |
||||
optional = false |
||||
python-versions = "*" |
||||
|
||||
[package.extras] |
||||
argon2 = ["argon2-cffi (>=18.2.0)"] |
||||
bcrypt = ["bcrypt (>=3.1.0)"] |
||||
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] |
||||
totp = ["cryptography"] |
||||
|
||||
[[package]] |
||||
name = "pluggy" |
||||
version = "0.13.1" |
||||
description = "plugin and hook calling mechanisms for python" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" |
||||
|
||||
[package.extras] |
||||
dev = ["pre-commit", "tox"] |
||||
|
||||
[[package]] |
||||
name = "py" |
||||
version = "1.10.0" |
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" |
||||
|
||||
[[package]] |
||||
name = "pyparsing" |
||||
version = "2.4.7" |
||||
description = "Python parsing module" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" |
||||
|
||||
[[package]] |
||||
name = "pytest" |
||||
version = "5.4.3" |
||||
description = "pytest: simple powerful testing with Python" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=3.5" |
||||
|
||||
[package.dependencies] |
||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} |
||||
attrs = ">=17.4.0" |
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""} |
||||
more-itertools = ">=4.0.0" |
||||
packaging = "*" |
||||
pluggy = ">=0.12,<1.0" |
||||
py = ">=1.5.0" |
||||
wcwidth = "*" |
||||
|
||||
[package.extras] |
||||
checkqa-mypy = ["mypy (==v0.761)"] |
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] |
||||
|
||||
[[package]] |
||||
name = "wcwidth" |
||||
version = "0.2.5" |
||||
description = "Measures the displayed width of unicode strings in a terminal" |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = "*" |
||||
|
||||
[[package]] |
||||
name = "werkzeug" |
||||
version = "1.0.1" |
||||
description = "The comprehensive WSGI web application library." |
||||
category = "dev" |
||||
optional = false |
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" |
||||
|
||||
[package.extras] |
||||
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] |
||||
watchdog = ["watchdog"] |
||||
|
||||
[metadata] |
||||
lock-version = "1.1" |
||||
python-versions = "^3.9" |
||||
content-hash = "d434f83fdf87891fd386ad18a85c40f7191d6ceee7841d1c96e7f1e210448377" |
||||
|
||||
[metadata.files] |
||||
atomicwrites = [ |
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, |
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, |
||||
] |
||||
attrs = [ |
||||
{file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, |
||||
{file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, |
||||
] |
||||
colorama = [ |
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, |
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, |
||||
] |
||||
more-itertools = [ |
||||
{file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, |
||||
{file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, |
||||
] |
||||
packaging = [ |
||||
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, |
||||
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, |
||||
] |
||||
passlib = [ |
||||
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, |
||||
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, |
||||
] |
||||
pluggy = [ |
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, |
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, |
||||
] |
||||
py = [ |
||||
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, |
||||
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, |
||||
] |
||||
pyparsing = [ |
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, |
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, |
||||
] |
||||
pytest = [ |
||||
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, |
||||
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, |
||||
] |
||||
wcwidth = [ |
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, |
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, |
||||
] |
||||
werkzeug = [ |
||||
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, |
||||
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, |
||||
] |
@ -0,0 +1,17 @@
|
||||
[tool.poetry] |
||||
name = "passlib-werkzeug" |
||||
version = "0.1.0" |
||||
description = "" |
||||
authors = ["Gergely Polonkai <gergely@polonkai.eu>"] |
||||
|
||||
[tool.poetry.dependencies] |
||||
python = "^3.9" |
||||
passlib = "^1.7.4" |
||||
|
||||
[tool.poetry.dev-dependencies] |
||||
pytest = "^5.2" |
||||
Werkzeug = "^1.0.1" |
||||
|
||||
[build-system] |
||||
requires = ["poetry-core>=1.0.0"] |
||||
build-backend = "poetry.core.masonry.api" |
@ -0,0 +1,22 @@
|
||||
from passlib.registry import get_crypt_handler |
||||
from werkzeug.security import check_password_hash, generate_password_hash |
||||
|
||||
from passlib_werkzeug import __version__ |
||||
|
||||
|
||||
def test_version(): |
||||
assert __version__ == '0.1.0' |
||||
|
||||
|
||||
def test_werkzeug_understands_our_pbkdf2_sha256(): |
||||
handler = get_crypt_handler('werkzeug_pbkdf2_sha256') |
||||
passlib_hash = handler.using(rounds=150000, salt_size=8).hash('password') |
||||
|
||||
assert check_password_hash(passlib_hash, 'password') |
||||
|
||||
|
||||
def test_we_understand_werkzeug_pbkdf2_sha256(): |
||||
handler = get_crypt_handler('werkzeug_pbkdf2_sha256') |
||||
werkzeug_hash = generate_password_hash('password') |
||||
|
||||
assert handler.verify('password', werkzeug_hash) |
Loading…
Reference in new issue