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.
 
 
 
 

159 lines
5.7 KiB

  1. """passlib.handlers.sha1_crypt
  2. """
  3. #=============================================================================
  4. # imports
  5. #=============================================================================
  6. # core
  7. import logging; log = logging.getLogger(__name__)
  8. # site
  9. # pkg
  10. from passlib.utils import safe_crypt, test_crypt
  11. from passlib.utils.binary import h64
  12. from passlib.utils.compat import u, unicode, irange
  13. from passlib.crypto.digest import compile_hmac
  14. import passlib.utils.handlers as uh
  15. # local
  16. __all__ = [
  17. ]
  18. #=============================================================================
  19. # sha1-crypt
  20. #=============================================================================
  21. _BNULL = b'\x00'
  22. class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
  23. """This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`.
  24. It supports a variable-length salt, and a variable number of rounds.
  25. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  26. :type salt: str
  27. :param salt:
  28. Optional salt string.
  29. If not specified, an 8 character one will be autogenerated (this is recommended).
  30. If specified, it must be 0-64 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
  31. :type salt_size: int
  32. :param salt_size:
  33. Optional number of bytes to use when autogenerating new salts.
  34. Defaults to 8 bytes, but can be any value between 0 and 64.
  35. :type rounds: int
  36. :param rounds:
  37. Optional number of rounds to use.
  38. Defaults to 480000, must be between 1 and 4294967295, inclusive.
  39. :type relaxed: bool
  40. :param relaxed:
  41. By default, providing an invalid value for one of the other
  42. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  43. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  44. will be issued instead. Correctable errors include ``rounds``
  45. that are too small or too large, and ``salt`` strings that are too long.
  46. .. versionadded:: 1.6
  47. """
  48. #===================================================================
  49. # class attrs
  50. #===================================================================
  51. #--GenericHandler--
  52. name = "sha1_crypt"
  53. setting_kwds = ("salt", "salt_size", "rounds")
  54. ident = u("$sha1$")
  55. checksum_size = 28
  56. checksum_chars = uh.HASH64_CHARS
  57. #--HasSalt--
  58. default_salt_size = 8
  59. max_salt_size = 64
  60. salt_chars = uh.HASH64_CHARS
  61. #--HasRounds--
  62. default_rounds = 480000 # current passlib default
  63. min_rounds = 1 # really, this should be higher.
  64. max_rounds = 4294967295 # 32-bit integer limit
  65. rounds_cost = "linear"
  66. #===================================================================
  67. # formatting
  68. #===================================================================
  69. @classmethod
  70. def from_string(cls, hash):
  71. rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
  72. return cls(rounds=rounds, salt=salt, checksum=chk)
  73. def to_string(self, config=False):
  74. chk = None if config else self.checksum
  75. return uh.render_mc3(self.ident, self.rounds, self.salt, chk)
  76. #===================================================================
  77. # backend
  78. #===================================================================
  79. backends = ("os_crypt", "builtin")
  80. #---------------------------------------------------------------
  81. # os_crypt backend
  82. #---------------------------------------------------------------
  83. @classmethod
  84. def _load_backend_os_crypt(cls):
  85. if test_crypt("test", '$sha1$1$Wq3GL2Vp$C8U25GvfHS8qGHim'
  86. 'ExLaiSFlGkAe'):
  87. cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
  88. return True
  89. else:
  90. return False
  91. def _calc_checksum_os_crypt(self, secret):
  92. config = self.to_string(config=True)
  93. hash = safe_crypt(secret, config)
  94. if hash:
  95. assert hash.startswith(config) and len(hash) == len(config) + 29
  96. return hash[-28:]
  97. else:
  98. # py3's crypt.crypt() can't handle non-utf8 bytes.
  99. # fallback to builtin alg, which is always available.
  100. return self._calc_checksum_builtin(secret)
  101. #---------------------------------------------------------------
  102. # builtin backend
  103. #---------------------------------------------------------------
  104. @classmethod
  105. def _load_backend_builtin(cls):
  106. cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
  107. return True
  108. def _calc_checksum_builtin(self, secret):
  109. if isinstance(secret, unicode):
  110. secret = secret.encode("utf-8")
  111. if _BNULL in secret:
  112. raise uh.exc.NullPasswordError(self)
  113. rounds = self.rounds
  114. # NOTE: this seed value is NOT the same as the config string
  115. result = (u("%s$sha1$%s") % (self.salt, rounds)).encode("ascii")
  116. # NOTE: this algorithm is essentially PBKDF1, modified to use HMAC.
  117. keyed_hmac = compile_hmac("sha1", secret)
  118. for _ in irange(rounds):
  119. result = keyed_hmac(result)
  120. return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii")
  121. _chk_offsets = [
  122. 2,1,0,
  123. 5,4,3,
  124. 8,7,6,
  125. 11,10,9,
  126. 14,13,12,
  127. 17,16,15,
  128. 0,19,18,
  129. ]
  130. #===================================================================
  131. # eoc
  132. #===================================================================
  133. #=============================================================================
  134. # eof
  135. #=============================================================================