You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

273 rivejä
9.9 KiB

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