ldap_digests.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. """passlib.handlers.digests - plain hash digests
  2. """
  3. #=============================================================================
  4. # imports
  5. #=============================================================================
  6. # core
  7. from base64 import b64encode, b64decode
  8. from hashlib import md5, sha1, sha256, sha512
  9. import logging; log = logging.getLogger(__name__)
  10. import re
  11. # site
  12. # pkg
  13. from passlib.handlers.misc import plaintext
  14. from passlib.utils import unix_crypt_schemes, to_unicode
  15. from passlib.utils.compat import uascii_to_str, unicode, u
  16. from passlib.utils.decor import classproperty
  17. import passlib.utils.handlers as uh
  18. # local
  19. __all__ = [
  20. "ldap_plaintext",
  21. "ldap_md5",
  22. "ldap_sha1",
  23. "ldap_salted_md5",
  24. "ldap_salted_sha1",
  25. "ldap_salted_sha256",
  26. "ldap_salted_sha512",
  27. ##"get_active_ldap_crypt_schemes",
  28. "ldap_des_crypt",
  29. "ldap_bsdi_crypt",
  30. "ldap_md5_crypt",
  31. "ldap_sha1_crypt",
  32. "ldap_bcrypt",
  33. "ldap_sha256_crypt",
  34. "ldap_sha512_crypt",
  35. ]
  36. #=============================================================================
  37. # ldap helpers
  38. #=============================================================================
  39. class _Base64DigestHelper(uh.StaticHandler):
  40. """helper for ldap_md5 / ldap_sha1"""
  41. # XXX: could combine this with hex digests in digests.py
  42. ident = None # required - prefix identifier
  43. _hash_func = None # required - hash function
  44. _hash_regex = None # required - regexp to recognize hash
  45. checksum_chars = uh.PADDED_BASE64_CHARS
  46. @classproperty
  47. def _hash_prefix(cls):
  48. """tell StaticHandler to strip ident from checksum"""
  49. return cls.ident
  50. def _calc_checksum(self, secret):
  51. if isinstance(secret, unicode):
  52. secret = secret.encode("utf-8")
  53. chk = self._hash_func(secret).digest()
  54. return b64encode(chk).decode("ascii")
  55. class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
  56. """helper for ldap_salted_md5 / ldap_salted_sha1"""
  57. setting_kwds = ("salt", "salt_size")
  58. checksum_chars = uh.PADDED_BASE64_CHARS
  59. ident = None # required - prefix identifier
  60. _hash_func = None # required - hash function
  61. _hash_regex = None # required - regexp to recognize hash
  62. min_salt_size = max_salt_size = 4
  63. # NOTE: openldap implementation uses 4 byte salt,
  64. # but it's been reported (issue 30) that some servers use larger salts.
  65. # the semi-related rfc3112 recommends support for up to 16 byte salts.
  66. min_salt_size = 4
  67. default_salt_size = 4
  68. max_salt_size = 16
  69. @classmethod
  70. def from_string(cls, hash):
  71. hash = to_unicode(hash, "ascii", "hash")
  72. m = cls._hash_regex.match(hash)
  73. if not m:
  74. raise uh.exc.InvalidHashError(cls)
  75. try:
  76. data = b64decode(m.group("tmp").encode("ascii"))
  77. except TypeError:
  78. raise uh.exc.MalformedHashError(cls)
  79. cs = cls.checksum_size
  80. assert cs
  81. return cls(checksum=data[:cs], salt=data[cs:])
  82. def to_string(self):
  83. data = self.checksum + self.salt
  84. hash = self.ident + b64encode(data).decode("ascii")
  85. return uascii_to_str(hash)
  86. def _calc_checksum(self, secret):
  87. if isinstance(secret, unicode):
  88. secret = secret.encode("utf-8")
  89. return self._hash_func(secret + self.salt).digest()
  90. #=============================================================================
  91. # implementations
  92. #=============================================================================
  93. class ldap_md5(_Base64DigestHelper):
  94. """This class stores passwords using LDAP's plain MD5 format, and follows the :ref:`password-hash-api`.
  95. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
  96. """
  97. name = "ldap_md5"
  98. ident = u("{MD5}")
  99. _hash_func = md5
  100. _hash_regex = re.compile(u(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$"))
  101. class ldap_sha1(_Base64DigestHelper):
  102. """This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`.
  103. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
  104. """
  105. name = "ldap_sha1"
  106. ident = u("{SHA}")
  107. _hash_func = sha1
  108. _hash_regex = re.compile(u(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$"))
  109. class ldap_salted_md5(_SaltedBase64DigestHelper):
  110. """This class stores passwords using LDAP's salted MD5 format, and follows the :ref:`password-hash-api`.
  111. It supports a 4-16 byte salt.
  112. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  113. :type salt: bytes
  114. :param salt:
  115. Optional salt string.
  116. If not specified, one will be autogenerated (this is recommended).
  117. If specified, it may be any 4-16 byte string.
  118. :type salt_size: int
  119. :param salt_size:
  120. Optional number of bytes to use when autogenerating new salts.
  121. Defaults to 4 bytes for compatibility with the LDAP spec,
  122. but some systems use larger salts, and Passlib supports
  123. any value between 4-16.
  124. :type relaxed: bool
  125. :param relaxed:
  126. By default, providing an invalid value for one of the other
  127. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  128. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  129. will be issued instead. Correctable errors include
  130. ``salt`` strings that are too long.
  131. .. versionadded:: 1.6
  132. .. versionchanged:: 1.6
  133. This format now supports variable length salts, instead of a fix 4 bytes.
  134. """
  135. name = "ldap_salted_md5"
  136. ident = u("{SMD5}")
  137. checksum_size = 16
  138. _hash_func = md5
  139. _hash_regex = re.compile(u(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$"))
  140. class ldap_salted_sha1(_SaltedBase64DigestHelper):
  141. """
  142. This class stores passwords using LDAP's "Salted SHA1" format,
  143. and follows the :ref:`password-hash-api`.
  144. It supports a 4-16 byte salt.
  145. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  146. :type salt: bytes
  147. :param salt:
  148. Optional salt string.
  149. If not specified, one will be autogenerated (this is recommended).
  150. If specified, it may be any 4-16 byte string.
  151. :type salt_size: int
  152. :param salt_size:
  153. Optional number of bytes to use when autogenerating new salts.
  154. Defaults to 4 bytes for compatibility with the LDAP spec,
  155. but some systems use larger salts, and Passlib supports
  156. any value between 4-16.
  157. :type relaxed: bool
  158. :param relaxed:
  159. By default, providing an invalid value for one of the other
  160. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  161. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  162. will be issued instead. Correctable errors include
  163. ``salt`` strings that are too long.
  164. .. versionadded:: 1.6
  165. .. versionchanged:: 1.6
  166. This format now supports variable length salts, instead of a fix 4 bytes.
  167. """
  168. name = "ldap_salted_sha1"
  169. ident = u("{SSHA}")
  170. checksum_size = 20
  171. _hash_func = sha1
  172. # NOTE: 32 = ceil((20 + 4) * 4/3)
  173. _hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$"))
  174. class ldap_salted_sha256(_SaltedBase64DigestHelper):
  175. """
  176. This class stores passwords using LDAP's "Salted SHA2-256" format,
  177. and follows the :ref:`password-hash-api`.
  178. It supports a 4-16 byte salt.
  179. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  180. :type salt: bytes
  181. :param salt:
  182. Optional salt string.
  183. If not specified, one will be autogenerated (this is recommended).
  184. If specified, it may be any 4-16 byte string.
  185. :type salt_size: int
  186. :param salt_size:
  187. Optional number of bytes to use when autogenerating new salts.
  188. Defaults to 8 bytes for compatibility with the LDAP spec,
  189. but Passlib supports any value between 4-16.
  190. :type relaxed: bool
  191. :param relaxed:
  192. By default, providing an invalid value for one of the other
  193. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  194. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  195. will be issued instead. Correctable errors include
  196. ``salt`` strings that are too long.
  197. .. versionadded:: 1.7.3
  198. """
  199. name = "ldap_salted_sha256"
  200. ident = u("{SSHA256}")
  201. checksum_size = 32
  202. default_salt_size = 8
  203. _hash_func = sha256
  204. # NOTE: 48 = ceil((32 + 4) * 4/3)
  205. _hash_regex = re.compile(u(r"^\{SSHA256\}(?P<tmp>[+/a-zA-Z0-9]{48,}={0,2})$"))
  206. class ldap_salted_sha512(_SaltedBase64DigestHelper):
  207. """
  208. This class stores passwords using LDAP's "Salted SHA2-512" format,
  209. and follows the :ref:`password-hash-api`.
  210. It supports a 4-16 byte salt.
  211. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  212. :type salt: bytes
  213. :param salt:
  214. Optional salt string.
  215. If not specified, one will be autogenerated (this is recommended).
  216. If specified, it may be any 4-16 byte string.
  217. :type salt_size: int
  218. :param salt_size:
  219. Optional number of bytes to use when autogenerating new salts.
  220. Defaults to 8 bytes for compatibility with the LDAP spec,
  221. but Passlib supports any value between 4-16.
  222. :type relaxed: bool
  223. :param relaxed:
  224. By default, providing an invalid value for one of the other
  225. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  226. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  227. will be issued instead. Correctable errors include
  228. ``salt`` strings that are too long.
  229. .. versionadded:: 1.7.3
  230. """
  231. name = "ldap_salted_sha512"
  232. ident = u("{SSHA512}")
  233. checksum_size = 64
  234. default_salt_size = 8
  235. _hash_func = sha512
  236. # NOTE: 91 = ceil((64 + 4) * 4/3)
  237. _hash_regex = re.compile(u(r"^\{SSHA512\}(?P<tmp>[+/a-zA-Z0-9]{91,}={0,2})$"))
  238. class ldap_plaintext(plaintext):
  239. """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
  240. This class acts much like the generic :class:`!passlib.hash.plaintext` handler,
  241. except that it will identify a hash only if it does NOT begin with the ``{XXX}`` identifier prefix
  242. used by RFC2307 passwords.
  243. The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
  244. following additional contextual keyword:
  245. :type encoding: str
  246. :param encoding:
  247. This controls the character encoding to use (defaults to ``utf-8``).
  248. This encoding will be used to encode :class:`!unicode` passwords
  249. under Python 2, and decode :class:`!bytes` hashes under Python 3.
  250. .. versionchanged:: 1.6
  251. The ``encoding`` keyword was added.
  252. """
  253. # NOTE: this subclasses plaintext, since all it does differently
  254. # is override identify()
  255. name = "ldap_plaintext"
  256. _2307_pat = re.compile(u(r"^\{\w+\}.*$"))
  257. @uh.deprecated_method(deprecated="1.7", removed="2.0")
  258. @classmethod
  259. def genconfig(cls):
  260. # Overridding plaintext.genconfig() since it returns "",
  261. # but have to return non-empty value due to identify() below
  262. return "!"
  263. @classmethod
  264. def identify(cls, hash):
  265. # NOTE: identifies all strings EXCEPT those with {XXX} prefix
  266. hash = uh.to_unicode_for_identify(hash)
  267. return bool(hash) and cls._2307_pat.match(hash) is None
  268. #=============================================================================
  269. # {CRYPT} wrappers
  270. # the following are wrappers around the base crypt algorithms,
  271. # which add the ldap required {CRYPT} prefix
  272. #=============================================================================
  273. ldap_crypt_schemes = [ 'ldap_' + name for name in unix_crypt_schemes ]
  274. def _init_ldap_crypt_handlers():
  275. # NOTE: I don't like to implicitly modify globals() like this,
  276. # but don't want to write out all these handlers out either :)
  277. g = globals()
  278. for wname in unix_crypt_schemes:
  279. name = 'ldap_' + wname
  280. g[name] = uh.PrefixWrapper(name, wname, prefix=u("{CRYPT}"), lazy=True)
  281. del g
  282. _init_ldap_crypt_handlers()
  283. ##_lcn_host = None
  284. ##def get_host_ldap_crypt_schemes():
  285. ## global _lcn_host
  286. ## if _lcn_host is None:
  287. ## from passlib.hosts import host_context
  288. ## schemes = host_context.schemes()
  289. ## _lcn_host = [
  290. ## "ldap_" + name
  291. ## for name in unix_crypt_names
  292. ## if name in schemes
  293. ## ]
  294. ## return _lcn_host
  295. #=============================================================================
  296. # eof
  297. #=============================================================================