diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2012-03-10 16:54:15 -0500 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2012-03-10 16:54:15 -0500 |
commit | b5c69a10a71fd8f3c0ace6ec5051635b1e4bbe25 (patch) | |
tree | 33ef6883cd2ca29f8765341fe4f72814302dc9de /passlib | |
parent | 929d2168c5119c4b9b401e0ece4a39bf8b944b08 (diff) | |
download | passlib-b5c69a10a71fd8f3c0ace6ec5051635b1e4bbe25.tar.gz |
added support for lmhash
Diffstat (limited to 'passlib')
-rw-r--r-- | passlib/handlers/nthash.py | 100 | ||||
-rw-r--r-- | passlib/registry.py | 1 | ||||
-rw-r--r-- | passlib/tests/test_handlers.py | 38 | ||||
-rw-r--r-- | passlib/win32.py | 9 |
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 #========================================================= |