digests.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. """passlib.handlers.digests - plain hash digests
  2. """
  3. #=============================================================================
  4. # imports
  5. #=============================================================================
  6. # core
  7. import hashlib
  8. import logging; log = logging.getLogger(__name__)
  9. # site
  10. # pkg
  11. from passlib.utils import to_native_str, to_bytes, render_bytes, consteq
  12. from passlib.utils.compat import unicode, str_to_uascii
  13. import passlib.utils.handlers as uh
  14. from passlib.crypto.digest import lookup_hash
  15. # local
  16. __all__ = [
  17. "create_hex_hash",
  18. "hex_md4",
  19. "hex_md5",
  20. "hex_sha1",
  21. "hex_sha256",
  22. "hex_sha512",
  23. ]
  24. #=============================================================================
  25. # helpers for hexadecimal hashes
  26. #=============================================================================
  27. class HexDigestHash(uh.StaticHandler):
  28. """this provides a template for supporting passwords stored as plain hexadecimal hashes"""
  29. #===================================================================
  30. # class attrs
  31. #===================================================================
  32. _hash_func = None # hash function to use - filled in by create_hex_hash()
  33. checksum_size = None # filled in by create_hex_hash()
  34. checksum_chars = uh.HEX_CHARS
  35. #===================================================================
  36. # methods
  37. #===================================================================
  38. @classmethod
  39. def _norm_hash(cls, hash):
  40. return hash.lower()
  41. def _calc_checksum(self, secret):
  42. if isinstance(secret, unicode):
  43. secret = secret.encode("utf-8")
  44. return str_to_uascii(self._hash_func(secret).hexdigest())
  45. #===================================================================
  46. # eoc
  47. #===================================================================
  48. def create_hex_hash(digest, module=__name__):
  49. # NOTE: could set digest_name=hash.name for cpython, but not for some other platforms.
  50. info = lookup_hash(digest)
  51. name = "hex_" + info.name
  52. return type(name, (HexDigestHash,), dict(
  53. name=name,
  54. __module__=module, # so ABCMeta won't clobber it
  55. _hash_func=staticmethod(info.const), # sometimes it's a function, sometimes not. so wrap it.
  56. checksum_size=info.digest_size*2,
  57. __doc__="""This class implements a plain hexadecimal %s hash, and follows the :ref:`password-hash-api`.
  58. It supports no optional or contextual keywords.
  59. """ % (info.name,)
  60. ))
  61. #=============================================================================
  62. # predefined handlers
  63. #=============================================================================
  64. hex_md4 = create_hex_hash("md4")
  65. hex_md5 = create_hex_hash("md5")
  66. hex_md5.django_name = "unsalted_md5"
  67. hex_sha1 = create_hex_hash("sha1")
  68. hex_sha256 = create_hex_hash("sha256")
  69. hex_sha512 = create_hex_hash("sha512")
  70. #=============================================================================
  71. # htdigest
  72. #=============================================================================
  73. class htdigest(uh.MinimalHandler):
  74. """htdigest hash function.
  75. .. todo::
  76. document this hash
  77. """
  78. name = "htdigest"
  79. setting_kwds = ()
  80. context_kwds = ("user", "realm", "encoding")
  81. default_encoding = "utf-8"
  82. @classmethod
  83. def hash(cls, secret, user, realm, encoding=None):
  84. # NOTE: this was deliberately written so that raw bytes are passed through
  85. # unchanged, the encoding kwd is only used to handle unicode values.
  86. if not encoding:
  87. encoding = cls.default_encoding
  88. uh.validate_secret(secret)
  89. if isinstance(secret, unicode):
  90. secret = secret.encode(encoding)
  91. user = to_bytes(user, encoding, "user")
  92. realm = to_bytes(realm, encoding, "realm")
  93. data = render_bytes("%s:%s:%s", user, realm, secret)
  94. return hashlib.md5(data).hexdigest()
  95. @classmethod
  96. def _norm_hash(cls, hash):
  97. """normalize hash to native string, and validate it"""
  98. hash = to_native_str(hash, param="hash")
  99. if len(hash) != 32:
  100. raise uh.exc.MalformedHashError(cls, "wrong size")
  101. for char in hash:
  102. if char not in uh.LC_HEX_CHARS:
  103. raise uh.exc.MalformedHashError(cls, "invalid chars in hash")
  104. return hash
  105. @classmethod
  106. def verify(cls, secret, hash, user, realm, encoding="utf-8"):
  107. hash = cls._norm_hash(hash)
  108. other = cls.hash(secret, user, realm, encoding)
  109. return consteq(hash, other)
  110. @classmethod
  111. def identify(cls, hash):
  112. try:
  113. cls._norm_hash(hash)
  114. except ValueError:
  115. return False
  116. return True
  117. @uh.deprecated_method(deprecated="1.7", removed="2.0")
  118. @classmethod
  119. def genconfig(cls):
  120. return cls.hash("", "", "")
  121. @uh.deprecated_method(deprecated="1.7", removed="2.0")
  122. @classmethod
  123. def genhash(cls, secret, config, user, realm, encoding=None):
  124. # NOTE: 'config' is ignored, as this hash has no salting / other configuration.
  125. # just have to make sure it's valid.
  126. cls._norm_hash(config)
  127. return cls.hash(secret, user, realm, encoding)
  128. #=============================================================================
  129. # eof
  130. #=============================================================================