123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- """passlib.handlers.nthash - Microsoft Windows -related hashes"""
- #=============================================================================
- # imports
- #=============================================================================
- # core
- from binascii import hexlify
- import logging; log = logging.getLogger(__name__)
- from warnings import warn
- # site
- # pkg
- from passlib.utils import to_unicode, right_pad_string
- from passlib.utils.compat import unicode
- from passlib.crypto.digest import lookup_hash
- md4 = lookup_hash("md4").const
- import passlib.utils.handlers as uh
- # local
- __all__ = [
- "lmhash",
- "nthash",
- "bsd_nthash",
- "msdcc",
- "msdcc2",
- ]
- #=============================================================================
- # lanman hash
- #=============================================================================
- class lmhash(uh.TruncateMixin, uh.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:`~passlib.ifc.PasswordHash.using` method accepts a single
- optional keyword:
- :param bool truncate_error:
- By default, this will silently truncate passwords larger than 14 bytes.
- Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
- to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
- .. versionadded:: 1.7
- The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.verify` methods accept a single
- optional keyword:
- :type encoding: str
- :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 hexadecimal,
- it will accept upper-case as well.
- """
- #===================================================================
- # class attrs
- #===================================================================
- #--------------------
- # PasswordHash
- #--------------------
- name = "lmhash"
- setting_kwds = ("truncate_error",)
- #--------------------
- # GenericHandler
- #--------------------
- checksum_chars = uh.HEX_CHARS
- checksum_size = 32
- #--------------------
- # TruncateMixin
- #--------------------
- truncate_size = 14
- #--------------------
- # custom
- #--------------------
- default_encoding = "cp437"
- #===================================================================
- # methods
- #===================================================================
- @classmethod
- def _norm_hash(cls, hash):
- return hash.lower()
- def _calc_checksum(self, secret):
- # check for truncation (during .hash() calls only)
- if self.use_defaults:
- self._check_truncate_policy(secret)
- return hexlify(self.raw(secret, self.encoding)).decode("ascii")
- # magic constant used by LMHASH
- _magic = b"KGS!@#$%"
- @classmethod
- def raw(cls, secret, encoding=None):
- """encode password using LANMAN hash algorithm.
- :type secret: unicode or utf-8 encoded bytes
- :arg secret: secret to hash
- :type encoding: str
- :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
- """
- if not encoding:
- encoding = cls.default_encoding
- # 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.crypto.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")
- secret = right_pad_string(secret, 14)
- return des_encrypt_block(secret[0:7], MAGIC) + \
- des_encrypt_block(secret[7:14], MAGIC)
- #===================================================================
- # eoc
- #===================================================================
- #=============================================================================
- # ntlm hash
- #=============================================================================
- class nthash(uh.StaticHandler):
- """This class implements the NT Password hash, and follows the :ref:`password-hash-api`.
- It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
- Note that while this class outputs lower-case hexadecimal digests,
- it will accept upper-case digests as well.
- """
- #===================================================================
- # class attrs
- #===================================================================
- name = "nthash"
- checksum_chars = uh.HEX_CHARS
- checksum_size = 32
- #===================================================================
- # methods
- #===================================================================
- @classmethod
- def _norm_hash(cls, hash):
- return hash.lower()
- def _calc_checksum(self, secret):
- return hexlify(self.raw(secret)).decode("ascii")
- @classmethod
- def raw(cls, secret):
- """encode password using MD4-based NTHASH algorithm
- :arg secret: secret as unicode or utf-8 encoded bytes
- :returns: returns string of raw bytes
- """
- secret = to_unicode(secret, "utf-8", param="secret")
- # XXX: found refs that say only first 128 chars are used.
- return md4(secret.encode("utf-16-le")).digest()
- @classmethod
- def raw_nthash(cls, secret, hex=False):
- warn("nthash.raw_nthash() is deprecated, and will be removed "
- "in Passlib 1.8, please use nthash.raw() instead",
- DeprecationWarning)
- ret = nthash.raw(secret)
- return hexlify(ret).decode("ascii") if hex else ret
- #===================================================================
- # eoc
- #===================================================================
- bsd_nthash = uh.PrefixWrapper("bsd_nthash", nthash, prefix="$3$$", ident="$3$$",
- doc="""The class support FreeBSD's representation of NTHASH
- (which is compatible with the :ref:`modular-crypt-format`),
- and follows the :ref:`password-hash-api`.
- It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
- """)
- ##class ntlm_pair(object):
- ## "combined lmhash & nthash"
- ## name = "ntlm_pair"
- ## setting_kwds = ()
- ## _hash_regex = re.compile(u"^(?P<lm>[0-9a-f]{32}):(?P<nt>[0-9][a-f]{32})$",
- ## re.I)
- ##
- ## @classmethod
- ## def identify(cls, hash):
- ## hash = to_unicode(hash, "latin-1", "hash")
- ## return len(hash) == 65 and cls._hash_regex.match(hash) is not None
- ##
- ## @classmethod
- ## def hash(cls, secret, config=None):
- ## if config is not None and not cls.identify(config):
- ## raise uh.exc.InvalidHashError(cls)
- ## return lmhash.hash(secret) + ":" + nthash.hash(secret)
- ##
- ## @classmethod
- ## def verify(cls, secret, hash):
- ## hash = to_unicode(hash, "ascii", "hash")
- ## m = cls._hash_regex.match(hash)
- ## if not m:
- ## raise uh.exc.InvalidHashError(cls)
- ## lm, nt = m.group("lm", "nt")
- ## # NOTE: verify against both in case encoding issue
- ## # causes one not to match.
- ## return lmhash.verify(secret, lm) or nthash.verify(secret, nt)
- #=============================================================================
- # msdcc v1
- #=============================================================================
- class msdcc(uh.HasUserContext, uh.StaticHandler):
- """This class implements Microsoft's Domain Cached Credentials password hash,
- and follows the :ref:`password-hash-api`.
- It has a fixed number of rounds, and uses the associated
- username as the salt.
- The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
- have the following optional keywords:
- :type user: str
- :param user:
- String containing name of user account this password is associated with.
- This is required to properly calculate the hash.
- This keyword is case-insensitive, and should contain just the username
- (e.g. ``Administrator``, not ``SOMEDOMAIN\\Administrator``).
- Note that while this class outputs lower-case hexadecimal digests,
- it will accept upper-case digests as well.
- """
- name = "msdcc"
- checksum_chars = uh.HEX_CHARS
- checksum_size = 32
- @classmethod
- def _norm_hash(cls, hash):
- return hash.lower()
- def _calc_checksum(self, secret):
- return hexlify(self.raw(secret, self.user)).decode("ascii")
- @classmethod
- def raw(cls, secret, user):
- """encode password using mscash v1 algorithm
- :arg secret: secret as unicode or utf-8 encoded bytes
- :arg user: username to use as salt
- :returns: returns string of raw bytes
- """
- secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le")
- user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le")
- return md4(md4(secret).digest() + user).digest()
- #=============================================================================
- # msdcc2 aka mscash2
- #=============================================================================
- class msdcc2(uh.HasUserContext, uh.StaticHandler):
- """This class implements version 2 of Microsoft's Domain Cached Credentials
- password hash, and follows the :ref:`password-hash-api`.
- It has a fixed number of rounds, and uses the associated
- username as the salt.
- The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
- have the following extra keyword:
- :type user: str
- :param user:
- String containing name of user account this password is associated with.
- This is required to properly calculate the hash.
- This keyword is case-insensitive, and should contain just the username
- (e.g. ``Administrator``, not ``SOMEDOMAIN\\Administrator``).
- """
- name = "msdcc2"
- checksum_chars = uh.HEX_CHARS
- checksum_size = 32
- @classmethod
- def _norm_hash(cls, hash):
- return hash.lower()
- def _calc_checksum(self, secret):
- return hexlify(self.raw(secret, self.user)).decode("ascii")
- @classmethod
- def raw(cls, secret, user):
- """encode password using msdcc v2 algorithm
- :type secret: unicode or utf-8 bytes
- :arg secret: secret
- :type user: str
- :arg user: username to use as salt
- :returns: returns string of raw bytes
- """
- from passlib.crypto.digest import pbkdf2_hmac
- secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le")
- user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le")
- tmp = md4(md4(secret).digest() + user).digest()
- return pbkdf2_hmac("sha1", tmp, user, 10240, 16)
- #=============================================================================
- # eof
- #=============================================================================
|