summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2020-10-05 21:14:41 -0400
committerEli Collins <elic@assurancetechnologies.com>2020-10-05 21:14:41 -0400
commit3f1ad211d9499f5c36e9cc90c888c6e0da50ab3c (patch)
treec01df999752b89a3c272c4c0e6acff52b2977ef6
parenta2809fd98ed58eb006d204b05cc1519bdf1b12de (diff)
downloadpasslib-3f1ad211d9499f5c36e9cc90c888c6e0da50ab3c.tar.gz
passlib.hash: added ldap_salted_sha256 & ldap_salted_512 (issue 124)
-rw-r--r--docs/history/1.7.rst7
-rw-r--r--docs/lib/passlib.hash.django_std.rst2
-rw-r--r--docs/lib/passlib.hash.ldap_std.rst5
-rw-r--r--docs/lib/passlib.hash.rst2
-rw-r--r--passlib/apps.py14
-rw-r--r--passlib/handlers/ldap_digests.py91
-rw-r--r--passlib/hash.py2
-rw-r--r--passlib/registry.py2
-rw-r--r--passlib/tests/test_handlers.py61
9 files changed, 178 insertions, 8 deletions
diff --git a/docs/history/1.7.rst b/docs/history/1.7.rst
index c5b15d0..18bdae7 100644
--- a/docs/history/1.7.rst
+++ b/docs/history/1.7.rst
@@ -35,6 +35,13 @@ Administrative Changes
The mailing list and documentation urls remain the same.
+New Features
+------------
+
+* .. py:currentmodule:: passlib.hash
+
+ :class:`ldap_salted_sha512`: LDAP "salted hash" support added for SHA-256 and SHA-512 (:issue:`124`).
+
Bugfixes
--------
diff --git a/docs/lib/passlib.hash.django_std.rst b/docs/lib/passlib.hash.django_std.rst
index 5135f4f..e733b56 100644
--- a/docs/lib/passlib.hash.django_std.rst
+++ b/docs/lib/passlib.hash.django_std.rst
@@ -128,8 +128,6 @@ Interface
.. versionadded:: 1.6
-.. autoclass:: django_bcrypt_sha256()
-
Format
------
An example :class:`!django_pbkdf2_sha256` hash (of ``password``) is:
diff --git a/docs/lib/passlib.hash.ldap_std.rst b/docs/lib/passlib.hash.ldap_std.rst
index d3788c1..1245749 100644
--- a/docs/lib/passlib.hash.ldap_std.rst
+++ b/docs/lib/passlib.hash.ldap_std.rst
@@ -62,6 +62,8 @@ Salted Hashes
=============
.. autoclass:: ldap_salted_md5()
.. autoclass:: ldap_salted_sha1()
+.. autoclass:: ldap_salted_sha256()
+.. autoclass:: ldap_salted_sha512()
These hashes have the format :samp:`{prefix}{data}`.
@@ -95,6 +97,9 @@ The LDAP salted hashes should not be considered very secure.
which is only borderline sufficient to defeat rainbow tables,
and cannot (portably) be increased.
+* The SHA2 salted hashes (SSHA256, SSHA512) are only marginally better.
+ they use the newer SHA2 hash; and 64 bits of entropy in their salt.
+
Plaintext
=========
.. autoclass:: ldap_plaintext()
diff --git a/docs/lib/passlib.hash.rst b/docs/lib/passlib.hash.rst
index aaf7e60..0103a4f 100644
--- a/docs/lib/passlib.hash.rst
+++ b/docs/lib/passlib.hash.rst
@@ -168,6 +168,8 @@ and are supported by OpenLDAP.
* :class:`passlib.hash.ldap_sha1` - SHA1 digest
* :class:`passlib.hash.ldap_salted_md5` - salted MD5 digest
* :class:`passlib.hash.ldap_salted_sha1` - salted SHA1 digest
+* :class:`passlib.hash.ldap_salted_sha256` - salted SHA256 digest
+* :class:`passlib.hash.ldap_salted_sha512` - salted SHA512 digest
.. toctree::
:maxdepth: 1
diff --git a/passlib/apps.py b/passlib/apps.py
index 7c4be06..30cd6af 100644
--- a/passlib/apps.py
+++ b/passlib/apps.py
@@ -124,9 +124,17 @@ django_context = django110_context
#=============================================================================
# ldap
#=============================================================================
-std_ldap_schemes = ["ldap_salted_sha1", "ldap_salted_md5",
- "ldap_sha1", "ldap_md5",
- "ldap_plaintext" ]
+
+#: standard ldap schemes
+std_ldap_schemes = [
+ "ldap_salted_sha512",
+ "ldap_salted_sha256",
+ "ldap_salted_sha1",
+ "ldap_salted_md5",
+ "ldap_sha1",
+ "ldap_md5",
+ "ldap_plaintext",
+]
# create context with all std ldap schemes EXCEPT crypt
ldap_nocrypt_context = LazyCryptContext(std_ldap_schemes)
diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py
index 17ab774..30254f0 100644
--- a/passlib/handlers/ldap_digests.py
+++ b/passlib/handlers/ldap_digests.py
@@ -5,7 +5,7 @@
#=============================================================================
# core
from base64 import b64encode, b64decode
-from hashlib import md5, sha1
+from hashlib import md5, sha1, sha256, sha512
import logging; log = logging.getLogger(__name__)
import re
# site
@@ -22,6 +22,8 @@ __all__ = [
"ldap_sha1",
"ldap_salted_md5",
"ldap_salted_sha1",
+ "ldap_salted_sha256",
+ "ldap_salted_sha512",
##"get_active_ldap_crypt_schemes",
"ldap_des_crypt",
@@ -160,7 +162,9 @@ class ldap_salted_md5(_SaltedBase64DigestHelper):
_hash_regex = re.compile(u(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$"))
class ldap_salted_sha1(_SaltedBase64DigestHelper):
- """This class stores passwords using LDAP's salted SHA1 format, and follows the :ref:`password-hash-api`.
+ """
+ This class stores passwords using LDAP's "Salted SHA1" format,
+ and follows the :ref:`password-hash-api`.
It supports a 4-16 byte salt.
@@ -196,8 +200,91 @@ class ldap_salted_sha1(_SaltedBase64DigestHelper):
ident = u("{SSHA}")
checksum_size = 20
_hash_func = sha1
+ # NOTE: 32 = ceil((20 + 4) * 4/3)
_hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$"))
+
+
+class ldap_salted_sha256(_SaltedBase64DigestHelper):
+ """
+ This class stores passwords using LDAP's "Salted SHA2-256" format,
+ and follows the :ref:`password-hash-api`.
+
+ It supports a 4-16 byte salt.
+
+ The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
+
+ :type salt: bytes
+ :param salt:
+ Optional salt string.
+ If not specified, one will be autogenerated (this is recommended).
+ If specified, it may be any 4-16 byte string.
+
+ :type salt_size: int
+ :param salt_size:
+ Optional number of bytes to use when autogenerating new salts.
+ Defaults to 8 bytes for compatibility with the LDAP spec,
+ but Passlib supports any value between 4-16.
+
+ :type relaxed: bool
+ :param relaxed:
+ By default, providing an invalid value for one of the other
+ keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+ and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+ will be issued instead. Correctable errors include
+ ``salt`` strings that are too long.
+
+ .. versionadded:: 1.7.3
+ """
+ name = "ldap_salted_sha256"
+ ident = u("{SSHA256}")
+ checksum_size = 32
+ default_salt_size = 8
+ _hash_func = sha256
+ # NOTE: 48 = ceil((32 + 4) * 4/3)
+ _hash_regex = re.compile(u(r"^\{SSHA256\}(?P<tmp>[+/a-zA-Z0-9]{48,}={0,2})$"))
+
+
+class ldap_salted_sha512(_SaltedBase64DigestHelper):
+ """
+ This class stores passwords using LDAP's "Salted SHA2-512" format,
+ and follows the :ref:`password-hash-api`.
+
+ It supports a 4-16 byte salt.
+
+ The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
+
+ :type salt: bytes
+ :param salt:
+ Optional salt string.
+ If not specified, one will be autogenerated (this is recommended).
+ If specified, it may be any 4-16 byte string.
+
+ :type salt_size: int
+ :param salt_size:
+ Optional number of bytes to use when autogenerating new salts.
+ Defaults to 8 bytes for compatibility with the LDAP spec,
+ but Passlib supports any value between 4-16.
+
+ :type relaxed: bool
+ :param relaxed:
+ By default, providing an invalid value for one of the other
+ keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+ and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+ will be issued instead. Correctable errors include
+ ``salt`` strings that are too long.
+
+ .. versionadded:: 1.7.3
+ """
+ name = "ldap_salted_sha512"
+ ident = u("{SSHA512}")
+ checksum_size = 64
+ default_salt_size = 8
+ _hash_func = sha512
+ # NOTE: 91 = ceil((64 + 4) * 4/3)
+ _hash_regex = re.compile(u(r"^\{SSHA512\}(?P<tmp>[+/a-zA-Z0-9]{91,}={0,2})$"))
+
+
class ldap_plaintext(plaintext):
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
diff --git a/passlib/hash.py b/passlib/hash.py
index 9b72448..2cc0628 100644
--- a/passlib/hash.py
+++ b/passlib/hash.py
@@ -43,7 +43,7 @@ if False:
from passlib.handlers.digests import hex_md4, hex_md5, hex_sha1, hex_sha256, hex_sha512, htdigest
from passlib.handlers.django import django_bcrypt, django_bcrypt_sha256, django_des_crypt, django_disabled, django_pbkdf2_sha1, django_pbkdf2_sha256, django_salted_md5, django_salted_sha1
from passlib.handlers.fshp import fshp
- from passlib.handlers.ldap_digests import ldap_bcrypt, ldap_bsdi_crypt, ldap_des_crypt, ldap_md5, ldap_md5_crypt, ldap_plaintext, ldap_salted_md5, ldap_salted_sha1, ldap_sha1, ldap_sha1_crypt, ldap_sha256_crypt, ldap_sha512_crypt
+ from passlib.handlers.ldap_digests import ldap_bcrypt, ldap_bsdi_crypt, ldap_des_crypt, ldap_md5, ldap_md5_crypt, ldap_plaintext, ldap_salted_md5, ldap_salted_sha1, ldap_salted_sha256, ldap_salted_sha512, ldap_sha1, ldap_sha1_crypt, ldap_sha256_crypt, ldap_sha512_crypt
from passlib.handlers.md5_crypt import apr_md5_crypt, md5_crypt
from passlib.handlers.misc import plaintext, unix_disabled, unix_fallback
from passlib.handlers.mssql import mssql2000, mssql2005
diff --git a/passlib/registry.py b/passlib/registry.py
index 5c9cb6e..9964b25 100644
--- a/passlib/registry.py
+++ b/passlib/registry.py
@@ -125,6 +125,8 @@ _locations = dict(
ldap_hex_sha1 = "passlib.handlers.roundup",
ldap_salted_md5 = "passlib.handlers.ldap_digests",
ldap_salted_sha1 = "passlib.handlers.ldap_digests",
+ ldap_salted_sha256 = "passlib.handlers.ldap_digests",
+ ldap_salted_sha512 = "passlib.handlers.ldap_digests",
ldap_des_crypt = "passlib.handlers.ldap_digests",
ldap_bsdi_crypt = "passlib.handlers.ldap_digests",
ldap_md5_crypt = "passlib.handlers.ldap_digests",
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index 9d81036..e7d2277 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -556,6 +556,67 @@ class ldap_salted_sha1_test(HandlerCase):
'{SSHA}P90+qijSp8MJ1tN25j5o1PflUvlqjXHOGeOck===',
]
+
+class ldap_salted_sha256_test(HandlerCase):
+ handler = hash.ldap_salted_sha256
+ known_correct_hashes = [
+ # generated locally
+ # salt size = 8
+ ("password", '{SSHA256}x1tymSTVjozxQ2PtT46ysrzhZxbcskK0o2f8hEFx7fAQQmhtDSEkJA=='),
+ ("test", '{SSHA256}xfqc9aOR6z15YaEk3/Ufd7UL9+JozB/1EPmCDTizL0GkdA7BuNda6w=='),
+ ("toomanysecrets", '{SSHA256}RrTKrg6HFXcjJ+eDAq4UtbODxOr9RLeG+I69FoJvutcbY0zpfU+p1Q=='),
+ (u('letm\xe8\xefn'), '{SSHA256}km7UjUTBZN8a+gf1ND2/qn15N7LsO/jmGYJXvyTfJKAbI0RoLWWslQ=='),
+
+ # alternate salt sizes (4, 15, 16)
+ # generated locally
+ ('test', '{SSHA256}TFv2RpwyO0U9mA0Hk8FsXRa1I+4dNUtv27Qa8dzGVLinlDIm'),
+ ('test', '{SSHA256}J6MFQdkfjdmXz9UyUPb773kekJdm4dgSL4y8WQEQW11VipHSundOKaV0LsV4L6U='),
+ ('test', '{SSHA256}uBLazLaiBaPb6Cpnvq2XTYDkvXbYIuqRW1anMKk85d1/j1GqFQIgpHSOMUYIIcS4'),
+ ]
+
+ known_malformed_hashes = [
+ # salt too small (3)
+ '{SSHA256}Lpdyr1+lR+rtxgp3SpQnUuNw33ENivTl28nzF2ZI4Gm41/o=',
+
+ # incorrect base64 encoding
+ '{SSHA256}TFv2RpwyO0U9mA0Hk8FsXRa1I+4dNUtv27Qa8dzGVLinlDI@',
+ '{SSHA256}TFv2RpwyO0U9mA0Hk8FsXRa1I+4dNUtv27Qa8dzGVLinlDI',
+ '{SSHA256}TFv2RpwyO0U9mA0Hk8FsXRa1I+4dNUtv27Qa8dzGVLinlDIm===',
+ ]
+
+
+
+class ldap_salted_sha512_test(HandlerCase):
+ handler = hash.ldap_salted_sha512
+ known_correct_hashes = [
+ # generated by testing ldap server web interface (see issue 124 comments)
+ # salt size = 8
+ ("toomanysecrets", '{SSHA512}wExp4xjiCHS0zidJDC4UJq9EEeIebAQPJ1PWSwfhxWjfutI9XiiKuHm2AE41cEFfK+8HyI8bh+ztbczUGsvVFIgICWWPt7qu'),
+ (u('letm\xe8\xefn'), '{SSHA512}mpNUSmZc3TNx+RnPwkIAVMf7ocEKLPrIoQNsg4Eu8dHvyCeb2xzHp5A6n4tF7ntknSvfvRZaJII4ImvNJlYsgiwAm0FMqR+3'),
+
+ # generated locally
+ # salt size = 8
+ ("password", '{SSHA512}f/lFQskkl7PdMsTGJxHZq8LDt/l+UqRMm6/pj4pV7/xZkcOaKCgvQqp+KCeXc/Vd4RY6vEHWn4y0DnFcQ6wgyv9fyxk='),
+ ("test", '{SSHA512}Tgx/uhHnlM9/GgQvI31dN7cheDXg7WypZwaaIkyRsgV/BKIzBG3G/wUd9o1dpi06p3SYzMedg0lvTc3b6CtdO0Xo/f9/L+Uc'),
+
+ # alternate salt sizes (4, 15, 16)
+ # generated locally
+ ('test', '{SSHA512}Yg9DQ2wURCFGwobu7R2O6cq7nVbnGMPrFCX0aPQ9kj/y1hd6k9PEzkgWCB5aXdPwPzNrVb0PkiHiBnG1CxFiT+B8L8U='),
+ ('test', '{SSHA512}5ecDGWs5RY4xLszUO6hAcl90W3wAozGQoI4Gqj8xSZdcfU1lVEM4aY8s+4xVeLitcn7BO8i7xkzMFWLoxas7SeHc23sP4dx77937PyeE0A=='),
+ ('test', '{SSHA512}6FQv5W47HGg2MFBFZofoiIbO8KRW75Pm51NKoInpthYQQ5ujazHGhVGzrj3JXgA7j0k+UNmkHdbJjdY5xcUHPzynFEII4fwfIySEcG5NKSU='),
+ ]
+
+ known_malformed_hashes = [
+ # salt too small (3)
+ '{SSHA512}zFnn4/8x8GveUaMqgrYWyIWqFQ0Irt6gADPtRk4Uv3nUC6uR5cD8+YdQni/0ZNij9etm6p17kSFuww3M6l+d6AbAeA==',
+
+ # incorrect base64 encoding
+ '{SSHA512}Tgx/uhHnlM9/GgQvI31dN7cheDXg7WypZwaaIkyRsgV/BKIzBG3G/wUd9o1dpi06p3SYzMedg0lvTc3b6CtdO0Xo/f9/L+U',
+ '{SSHA512}Tgx/uhHnlM9/GgQvI31dN7cheDXg7WypZwaaIkyRsgV/BKIzBG3G/wUd9o1dpi06p3SYzMedg0lvTc3b6CtdO0Xo/f9/L+U@',
+ '{SSHA512}Tgx/uhHnlM9/GgQvI31dN7cheDXg7WypZwaaIkyRsgV/BKIzBG3G/wUd9o1dpi06p3SYzMedg0lvTc3b6CtdO0Xo/f9/L+U===',
+ ]
+
+
class ldap_plaintext_test(HandlerCase):
# TODO: integrate EncodingHandlerMixin
handler = hash.ldap_plaintext