summaryrefslogtreecommitdiff
path: root/passlib
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-03-10 16:54:15 -0500
committerEli Collins <elic@assurancetechnologies.com>2012-03-10 16:54:15 -0500
commitb5c69a10a71fd8f3c0ace6ec5051635b1e4bbe25 (patch)
tree33ef6883cd2ca29f8765341fe4f72814302dc9de /passlib
parent929d2168c5119c4b9b401e0ece4a39bf8b944b08 (diff)
downloadpasslib-b5c69a10a71fd8f3c0ace6ec5051635b1e4bbe25.tar.gz
added support for lmhash
Diffstat (limited to 'passlib')
-rw-r--r--passlib/handlers/nthash.py100
-rw-r--r--passlib/registry.py1
-rw-r--r--passlib/tests/test_handlers.py38
-rw-r--r--passlib/win32.py9
4 files changed, 143 insertions, 5 deletions
diff --git a/passlib/handlers/nthash.py b/passlib/handlers/nthash.py
index 4b29b0e..f412ac8 100644
--- a/passlib/handlers/nthash.py
+++ b/passlib/handlers/nthash.py
@@ -3,23 +3,115 @@
#imports
#=========================================================
#core
+from binascii import hexlify
import re
import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
-from passlib.utils import to_unicode
-from passlib.utils.compat import bytes, str_to_uascii, u, uascii_to_str
+from passlib.utils import to_unicode, to_bytes
+from passlib.utils.compat import b, bytes, str_to_uascii, u, uascii_to_str
from passlib.utils.md4 import md4
import passlib.utils.handlers as uh
#pkg
#local
__all__ = [
- "NTHash",
+ "lmhash",
+ "nthash",
]
#=========================================================
-#handler
+# lanman hash
+#=========================================================
+
+class _HasEncodingContext(uh.GenericHandler):
+ # NOTE: consider moving this to helpers if other classes could use it.
+ context_kwds = ("encoding",)
+ _default_encoding = "utf-8"
+
+ def __init__(self, encoding=None, **kwds):
+ super(_HasEncodingContext, self).__init__(**kwds)
+ self.encoding = encoding or self._default_encoding
+
+class lmhash(_HasEncodingContext, uh.StaticHandler):
+ """This class implements the Lan Manager Password hash, and follows the :ref:`password-hash-api`.
+
+ It has no salt and a single fixed round.
+
+ The :meth:`encrypt()` and :meth:`verify` methods accept a single
+ optional keyword:
+
+ :param encoding:
+
+ This specifies what character encoding LMHASH should use when
+ calculating digest. It defaults to ``cp437``, the most
+ common encoding encountered.
+
+ Note that while this class outputs digests in lower-case hexidecimal,
+ it will accept upper-case as well.
+ """
+ #=========================================================
+ # class attrs
+ #=========================================================
+ name = "lmhash"
+ checksum_chars = uh.HEX_CHARS
+ checksum_size = 32
+ _default_encoding = "cp437"
+
+ #=========================================================
+ # methods
+ #=========================================================
+ @classmethod
+ def _norm_hash(cls, hash):
+ return hash.lower()
+
+ def _calc_checksum(self, secret):
+ return hexlify(self.raw(secret, self.encoding)).decode("ascii")
+
+ # magic constant used by LMHASH
+ _magic = b("KGS!@#$%")
+
+ @classmethod
+ def raw(cls, secret, encoding="cp437"):
+ """encode password using LANMAN hash algorithm.
+
+ :arg secret: secret as unicode or utf-8 encoded bytes
+ :arg encoding:
+ optional encoding to use for unicode inputs.
+ this defaults to ``cp437``, which is the
+ common case for most situations.
+
+ :returns: returns string of raw bytes
+ """
+ # some nice empircal data re: different encodings is at...
+ # http://www.openwall.com/lists/john-dev/2011/08/01/2
+ # http://www.freerainbowtables.com/phpBB3/viewtopic.php?t=387&p=12163
+ from passlib.utils.des import des_encrypt_block
+ MAGIC = cls._magic
+ if isinstance(secret, unicode):
+ # perform uppercasing while we're still unicode,
+ # to give a better shot at getting non-ascii chars right.
+ # (though some codepages do NOT upper-case the same as unicode).
+ secret = secret.upper().encode(encoding)
+ elif isinstance(secret, bytes):
+ # FIXME: just trusting ascii upper will work?
+ # and if not, how to do codepage specific case conversion?
+ # we could decode first using <encoding>,
+ # but *that* might not always be right.
+ secret = secret.upper()
+ else:
+ raise TypeError("secret must be unicode or bytes")
+ if len(secret) < 14:
+ secret += b("\x00") * (14-len(secret))
+ return des_encrypt_block(secret[0:7], MAGIC) + \
+ des_encrypt_block(secret[7:14], MAGIC)
+
+ #=========================================================
+ # eoc
+ #=========================================================
+
+#=========================================================
+# ntlm hash
#=========================================================
class nthash(uh.HasManyIdents, uh.GenericHandler):
"""This class implements the NT Password hash in a manner compatible with the :ref:`modular-crypt-format`, and follows the :ref:`password-hash-api`.
diff --git a/passlib/registry.py b/passlib/registry.py
index dadf385..d2319f9 100644
--- a/passlib/registry.py
+++ b/passlib/registry.py
@@ -123,6 +123,7 @@ _handler_locations = {
("passlib.handlers.pbkdf2", "ldap_pbkdf2_sha256"),
"ldap_pbkdf2_sha512":
("passlib.handlers.pbkdf2", "ldap_pbkdf2_sha512"),
+ "lmhash": ("passlib.handlers.nthash", "lmhash"),
"md5_crypt": ("passlib.handlers.md5_crypt", "md5_crypt"),
"mssql2000": ("passlib.handlers.mssql", "mssql2000"),
"mssql2005": ("passlib.handlers.mssql", "mssql2005"),
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index 1276fb7..732da34 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -869,6 +869,44 @@ class ldap_pbkdf2_test(TestCase):
)
#=========================================================
+# lanman
+#=========================================================
+class lmhash_test(HandlerCase):
+ handler = hash.lmhash
+ secret_size = 14
+ secret_case_insensitive = True
+
+ known_correct_hashes = [
+ #
+ # custom
+ #
+ ('', 'aad3b435b51404eeaad3b435b51404ee'),
+ ('zzZZZzz', 'a5e6066de61c3e35aad3b435b51404ee'),
+ ('passphrase', '855c3697d9979e78ac404c4ba2c66533'),
+ ('Yokohama', '5ecd9236d21095ce7584248b8d2c9f9e'),
+
+ # ensures cp437 used for unicode
+ (u('ENCYCLOP\xC6DIA'), 'fed6416bffc9750d48462b9d7aaac065'),
+ (u('encyclop\xE6dia'), 'fed6416bffc9750d48462b9d7aaac065'),
+ ]
+
+ # TODO: test encoding keyword.
+
+ known_unidentified_hashes = [
+ # bad char in otherwise correct hash
+ '855c3697d9979e78ac404c4ba2c6653X',
+ ]
+
+ # override default list since lmhash uses cp437 as default encoding
+ stock_passwords = [
+ u("test"),
+ b("test"),
+ u("\u00AC\u00BA"),
+ ]
+
+ fuzz_password_alphabet = u('qwerty1234<>.@*#! \u00AC')
+
+#=========================================================
#md5 crypt
#=========================================================
class _md5_crypt_test(HandlerCase):
diff --git a/passlib/win32.py b/passlib/win32.py
index a572f9a..44fa435 100644
--- a/passlib/win32.py
+++ b/passlib/win32.py
@@ -1,4 +1,4 @@
-"""passlib.win32 - MS Windows support
+"""passlib.win32 - MS Windows support - DEPRECATED, WILL BE REMOVED IN 1.8
the LMHASH and NTHASH algorithms are used in various windows related contexts,
but generally not in a manner compatible with how passlib is structured.
@@ -20,6 +20,13 @@ this module provided two functions to aid in any use-cases which exist.
See also :mod:`passlib.hash.nthash`.
"""
+
+from warnings import warn
+warn("the 'passlib.win32' module is deprecated, and will be removed in "
+ "passlib 1.8; please use the 'passlib.hash.nthash' and "
+ "'passlib.hash.lmhash' classes instead.",
+ DeprecationWarning)
+
#=========================================================
#imports
#=========================================================