windows.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. """passlib.handlers.nthash - Microsoft Windows -related hashes"""
  2. #=============================================================================
  3. # imports
  4. #=============================================================================
  5. # core
  6. from binascii import hexlify
  7. import logging; log = logging.getLogger(__name__)
  8. from warnings import warn
  9. # site
  10. # pkg
  11. from passlib.utils import to_unicode, right_pad_string
  12. from passlib.utils.compat import unicode
  13. from passlib.crypto.digest import lookup_hash
  14. md4 = lookup_hash("md4").const
  15. import passlib.utils.handlers as uh
  16. # local
  17. __all__ = [
  18. "lmhash",
  19. "nthash",
  20. "bsd_nthash",
  21. "msdcc",
  22. "msdcc2",
  23. ]
  24. #=============================================================================
  25. # lanman hash
  26. #=============================================================================
  27. class lmhash(uh.TruncateMixin, uh.HasEncodingContext, uh.StaticHandler):
  28. """This class implements the Lan Manager Password hash, and follows the :ref:`password-hash-api`.
  29. It has no salt and a single fixed round.
  30. The :meth:`~passlib.ifc.PasswordHash.using` method accepts a single
  31. optional keyword:
  32. :param bool truncate_error:
  33. By default, this will silently truncate passwords larger than 14 bytes.
  34. Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
  35. to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
  36. .. versionadded:: 1.7
  37. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.verify` methods accept a single
  38. optional keyword:
  39. :type encoding: str
  40. :param encoding:
  41. This specifies what character encoding LMHASH should use when
  42. calculating digest. It defaults to ``cp437``, the most
  43. common encoding encountered.
  44. Note that while this class outputs digests in lower-case hexadecimal,
  45. it will accept upper-case as well.
  46. """
  47. #===================================================================
  48. # class attrs
  49. #===================================================================
  50. #--------------------
  51. # PasswordHash
  52. #--------------------
  53. name = "lmhash"
  54. setting_kwds = ("truncate_error",)
  55. #--------------------
  56. # GenericHandler
  57. #--------------------
  58. checksum_chars = uh.HEX_CHARS
  59. checksum_size = 32
  60. #--------------------
  61. # TruncateMixin
  62. #--------------------
  63. truncate_size = 14
  64. #--------------------
  65. # custom
  66. #--------------------
  67. default_encoding = "cp437"
  68. #===================================================================
  69. # methods
  70. #===================================================================
  71. @classmethod
  72. def _norm_hash(cls, hash):
  73. return hash.lower()
  74. def _calc_checksum(self, secret):
  75. # check for truncation (during .hash() calls only)
  76. if self.use_defaults:
  77. self._check_truncate_policy(secret)
  78. return hexlify(self.raw(secret, self.encoding)).decode("ascii")
  79. # magic constant used by LMHASH
  80. _magic = b"KGS!@#$%"
  81. @classmethod
  82. def raw(cls, secret, encoding=None):
  83. """encode password using LANMAN hash algorithm.
  84. :type secret: unicode or utf-8 encoded bytes
  85. :arg secret: secret to hash
  86. :type encoding: str
  87. :arg encoding:
  88. optional encoding to use for unicode inputs.
  89. this defaults to ``cp437``, which is the
  90. common case for most situations.
  91. :returns: returns string of raw bytes
  92. """
  93. if not encoding:
  94. encoding = cls.default_encoding
  95. # some nice empircal data re: different encodings is at...
  96. # http://www.openwall.com/lists/john-dev/2011/08/01/2
  97. # http://www.freerainbowtables.com/phpBB3/viewtopic.php?t=387&p=12163
  98. from passlib.crypto.des import des_encrypt_block
  99. MAGIC = cls._magic
  100. if isinstance(secret, unicode):
  101. # perform uppercasing while we're still unicode,
  102. # to give a better shot at getting non-ascii chars right.
  103. # (though some codepages do NOT upper-case the same as unicode).
  104. secret = secret.upper().encode(encoding)
  105. elif isinstance(secret, bytes):
  106. # FIXME: just trusting ascii upper will work?
  107. # and if not, how to do codepage specific case conversion?
  108. # we could decode first using <encoding>,
  109. # but *that* might not always be right.
  110. secret = secret.upper()
  111. else:
  112. raise TypeError("secret must be unicode or bytes")
  113. secret = right_pad_string(secret, 14)
  114. return des_encrypt_block(secret[0:7], MAGIC) + \
  115. des_encrypt_block(secret[7:14], MAGIC)
  116. #===================================================================
  117. # eoc
  118. #===================================================================
  119. #=============================================================================
  120. # ntlm hash
  121. #=============================================================================
  122. class nthash(uh.StaticHandler):
  123. """This class implements the NT Password hash, and follows the :ref:`password-hash-api`.
  124. It has no salt and a single fixed round.
  125. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
  126. Note that while this class outputs lower-case hexadecimal digests,
  127. it will accept upper-case digests as well.
  128. """
  129. #===================================================================
  130. # class attrs
  131. #===================================================================
  132. name = "nthash"
  133. checksum_chars = uh.HEX_CHARS
  134. checksum_size = 32
  135. #===================================================================
  136. # methods
  137. #===================================================================
  138. @classmethod
  139. def _norm_hash(cls, hash):
  140. return hash.lower()
  141. def _calc_checksum(self, secret):
  142. return hexlify(self.raw(secret)).decode("ascii")
  143. @classmethod
  144. def raw(cls, secret):
  145. """encode password using MD4-based NTHASH algorithm
  146. :arg secret: secret as unicode or utf-8 encoded bytes
  147. :returns: returns string of raw bytes
  148. """
  149. secret = to_unicode(secret, "utf-8", param="secret")
  150. # XXX: found refs that say only first 128 chars are used.
  151. return md4(secret.encode("utf-16-le")).digest()
  152. @classmethod
  153. def raw_nthash(cls, secret, hex=False):
  154. warn("nthash.raw_nthash() is deprecated, and will be removed "
  155. "in Passlib 1.8, please use nthash.raw() instead",
  156. DeprecationWarning)
  157. ret = nthash.raw(secret)
  158. return hexlify(ret).decode("ascii") if hex else ret
  159. #===================================================================
  160. # eoc
  161. #===================================================================
  162. bsd_nthash = uh.PrefixWrapper("bsd_nthash", nthash, prefix="$3$$", ident="$3$$",
  163. doc="""The class support FreeBSD's representation of NTHASH
  164. (which is compatible with the :ref:`modular-crypt-format`),
  165. and follows the :ref:`password-hash-api`.
  166. It has no salt and a single fixed round.
  167. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
  168. """)
  169. ##class ntlm_pair(object):
  170. ## "combined lmhash & nthash"
  171. ## name = "ntlm_pair"
  172. ## setting_kwds = ()
  173. ## _hash_regex = re.compile(u"^(?P<lm>[0-9a-f]{32}):(?P<nt>[0-9][a-f]{32})$",
  174. ## re.I)
  175. ##
  176. ## @classmethod
  177. ## def identify(cls, hash):
  178. ## hash = to_unicode(hash, "latin-1", "hash")
  179. ## return len(hash) == 65 and cls._hash_regex.match(hash) is not None
  180. ##
  181. ## @classmethod
  182. ## def hash(cls, secret, config=None):
  183. ## if config is not None and not cls.identify(config):
  184. ## raise uh.exc.InvalidHashError(cls)
  185. ## return lmhash.hash(secret) + ":" + nthash.hash(secret)
  186. ##
  187. ## @classmethod
  188. ## def verify(cls, secret, hash):
  189. ## hash = to_unicode(hash, "ascii", "hash")
  190. ## m = cls._hash_regex.match(hash)
  191. ## if not m:
  192. ## raise uh.exc.InvalidHashError(cls)
  193. ## lm, nt = m.group("lm", "nt")
  194. ## # NOTE: verify against both in case encoding issue
  195. ## # causes one not to match.
  196. ## return lmhash.verify(secret, lm) or nthash.verify(secret, nt)
  197. #=============================================================================
  198. # msdcc v1
  199. #=============================================================================
  200. class msdcc(uh.HasUserContext, uh.StaticHandler):
  201. """This class implements Microsoft's Domain Cached Credentials password hash,
  202. and follows the :ref:`password-hash-api`.
  203. It has a fixed number of rounds, and uses the associated
  204. username as the salt.
  205. The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
  206. have the following optional keywords:
  207. :type user: str
  208. :param user:
  209. String containing name of user account this password is associated with.
  210. This is required to properly calculate the hash.
  211. This keyword is case-insensitive, and should contain just the username
  212. (e.g. ``Administrator``, not ``SOMEDOMAIN\\Administrator``).
  213. Note that while this class outputs lower-case hexadecimal digests,
  214. it will accept upper-case digests as well.
  215. """
  216. name = "msdcc"
  217. checksum_chars = uh.HEX_CHARS
  218. checksum_size = 32
  219. @classmethod
  220. def _norm_hash(cls, hash):
  221. return hash.lower()
  222. def _calc_checksum(self, secret):
  223. return hexlify(self.raw(secret, self.user)).decode("ascii")
  224. @classmethod
  225. def raw(cls, secret, user):
  226. """encode password using mscash v1 algorithm
  227. :arg secret: secret as unicode or utf-8 encoded bytes
  228. :arg user: username to use as salt
  229. :returns: returns string of raw bytes
  230. """
  231. secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le")
  232. user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le")
  233. return md4(md4(secret).digest() + user).digest()
  234. #=============================================================================
  235. # msdcc2 aka mscash2
  236. #=============================================================================
  237. class msdcc2(uh.HasUserContext, uh.StaticHandler):
  238. """This class implements version 2 of Microsoft's Domain Cached Credentials
  239. password hash, and follows the :ref:`password-hash-api`.
  240. It has a fixed number of rounds, and uses the associated
  241. username as the salt.
  242. The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
  243. have the following extra keyword:
  244. :type user: str
  245. :param user:
  246. String containing name of user account this password is associated with.
  247. This is required to properly calculate the hash.
  248. This keyword is case-insensitive, and should contain just the username
  249. (e.g. ``Administrator``, not ``SOMEDOMAIN\\Administrator``).
  250. """
  251. name = "msdcc2"
  252. checksum_chars = uh.HEX_CHARS
  253. checksum_size = 32
  254. @classmethod
  255. def _norm_hash(cls, hash):
  256. return hash.lower()
  257. def _calc_checksum(self, secret):
  258. return hexlify(self.raw(secret, self.user)).decode("ascii")
  259. @classmethod
  260. def raw(cls, secret, user):
  261. """encode password using msdcc v2 algorithm
  262. :type secret: unicode or utf-8 bytes
  263. :arg secret: secret
  264. :type user: str
  265. :arg user: username to use as salt
  266. :returns: returns string of raw bytes
  267. """
  268. from passlib.crypto.digest import pbkdf2_hmac
  269. secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le")
  270. user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le")
  271. tmp = md4(md4(secret).digest() + user).digest()
  272. return pbkdf2_hmac("sha1", tmp, user, 10240, 16)
  273. #=============================================================================
  274. # eof
  275. #=============================================================================