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.
 
 
 
 

520 lines
21 KiB

  1. """passlib.handlers.sha2_crypt - SHA256-Crypt / SHA512-Crypt"""
  2. #=============================================================================
  3. # imports
  4. #=============================================================================
  5. # core
  6. import hashlib
  7. import logging; log = logging.getLogger(__name__)
  8. # site
  9. # pkg
  10. from passlib.utils import safe_crypt, test_crypt, \
  11. repeat_string, to_unicode
  12. from passlib.utils.binary import h64
  13. from passlib.utils.compat import byte_elem_value, u, \
  14. uascii_to_str, unicode
  15. import passlib.utils.handlers as uh
  16. # local
  17. __all__ = [
  18. "sha512_crypt",
  19. "sha256_crypt",
  20. ]
  21. #=============================================================================
  22. # pure-python backend, used by both sha256_crypt & sha512_crypt
  23. # when crypt.crypt() backend is not available.
  24. #=============================================================================
  25. _BNULL = b'\x00'
  26. # pre-calculated offsets used to speed up C digest stage (see notes below).
  27. # sequence generated using the following:
  28. ##perms_order = "p,pp,ps,psp,sp,spp".split(",")
  29. ##def offset(i):
  30. ## key = (("p" if i % 2 else "") + ("s" if i % 3 else "") +
  31. ## ("p" if i % 7 else "") + ("" if i % 2 else "p"))
  32. ## return perms_order.index(key)
  33. ##_c_digest_offsets = [(offset(i), offset(i+1)) for i in range(0,42,2)]
  34. _c_digest_offsets = (
  35. (0, 3), (5, 1), (5, 3), (1, 2), (5, 1), (5, 3), (1, 3),
  36. (4, 1), (5, 3), (1, 3), (5, 0), (5, 3), (1, 3), (5, 1),
  37. (4, 3), (1, 3), (5, 1), (5, 2), (1, 3), (5, 1), (5, 3),
  38. )
  39. # map used to transpose bytes when encoding final sha256_crypt digest
  40. _256_transpose_map = (
  41. 20, 10, 0, 11, 1, 21, 2, 22, 12, 23, 13, 3, 14, 4, 24, 5,
  42. 25, 15, 26, 16, 6, 17, 7, 27, 8, 28, 18, 29, 19, 9, 30, 31,
  43. )
  44. # map used to transpose bytes when encoding final sha512_crypt digest
  45. _512_transpose_map = (
  46. 42, 21, 0, 1, 43, 22, 23, 2, 44, 45, 24, 3, 4, 46, 25, 26,
  47. 5, 47, 48, 27, 6, 7, 49, 28, 29, 8, 50, 51, 30, 9, 10, 52,
  48. 31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56, 57, 36, 15,
  49. 16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40, 41, 20, 62, 63,
  50. )
  51. def _raw_sha2_crypt(pwd, salt, rounds, use_512=False):
  52. """perform raw sha256-crypt / sha512-crypt
  53. this function provides a pure-python implementation of the internals
  54. for the SHA256-Crypt and SHA512-Crypt algorithms; it doesn't
  55. handle any of the parsing/validation of the hash strings themselves.
  56. :arg pwd: password chars/bytes to hash
  57. :arg salt: salt chars to use
  58. :arg rounds: linear rounds cost
  59. :arg use_512: use sha512-crypt instead of sha256-crypt mode
  60. :returns:
  61. encoded checksum chars
  62. """
  63. #===================================================================
  64. # init & validate inputs
  65. #===================================================================
  66. # NOTE: the setup portion of this algorithm scales ~linearly in time
  67. # with the size of the password, making it vulnerable to a DOS from
  68. # unreasonably large inputs. the following code has some optimizations
  69. # which would make things even worse, using O(pwd_len**2) memory
  70. # when calculating digest P.
  71. #
  72. # to mitigate these two issues: 1) this code switches to a
  73. # O(pwd_len)-memory algorithm for passwords that are much larger
  74. # than average, and 2) Passlib enforces a library-wide max limit on
  75. # the size of passwords it will allow, to prevent this algorithm and
  76. # others from being DOSed in this way (see passlib.exc.PasswordSizeError
  77. # for details).
  78. # validate secret
  79. if isinstance(pwd, unicode):
  80. # XXX: not sure what official unicode policy is, using this as default
  81. pwd = pwd.encode("utf-8")
  82. assert isinstance(pwd, bytes)
  83. if _BNULL in pwd:
  84. raise uh.exc.NullPasswordError(sha512_crypt if use_512 else sha256_crypt)
  85. pwd_len = len(pwd)
  86. # validate rounds
  87. assert 1000 <= rounds <= 999999999, "invalid rounds"
  88. # NOTE: spec says out-of-range rounds should be clipped, instead of
  89. # causing an error. this function assumes that's been taken care of
  90. # by the handler class.
  91. # validate salt
  92. assert isinstance(salt, unicode), "salt not unicode"
  93. salt = salt.encode("ascii")
  94. salt_len = len(salt)
  95. assert salt_len < 17, "salt too large"
  96. # NOTE: spec says salts larger than 16 bytes should be truncated,
  97. # instead of causing an error. this function assumes that's been
  98. # taken care of by the handler class.
  99. # load sha256/512 specific constants
  100. if use_512:
  101. hash_const = hashlib.sha512
  102. transpose_map = _512_transpose_map
  103. else:
  104. hash_const = hashlib.sha256
  105. transpose_map = _256_transpose_map
  106. #===================================================================
  107. # digest B - used as subinput to digest A
  108. #===================================================================
  109. db = hash_const(pwd + salt + pwd).digest()
  110. #===================================================================
  111. # digest A - used to initialize first round of digest C
  112. #===================================================================
  113. # start out with pwd + salt
  114. a_ctx = hash_const(pwd + salt)
  115. a_ctx_update = a_ctx.update
  116. # add pwd_len bytes of b, repeating b as many times as needed.
  117. a_ctx_update(repeat_string(db, pwd_len))
  118. # for each bit in pwd_len: add b if it's 1, or pwd if it's 0
  119. i = pwd_len
  120. while i:
  121. a_ctx_update(db if i & 1 else pwd)
  122. i >>= 1
  123. # finish A
  124. da = a_ctx.digest()
  125. #===================================================================
  126. # digest P from password - used instead of password itself
  127. # when calculating digest C.
  128. #===================================================================
  129. if pwd_len < 96:
  130. # this method is faster under python, but uses O(pwd_len**2) memory;
  131. # so we don't use it for larger passwords to avoid a potential DOS.
  132. dp = repeat_string(hash_const(pwd * pwd_len).digest(), pwd_len)
  133. else:
  134. # this method is slower under python, but uses a fixed amount of memory.
  135. tmp_ctx = hash_const(pwd)
  136. tmp_ctx_update = tmp_ctx.update
  137. i = pwd_len-1
  138. while i:
  139. tmp_ctx_update(pwd)
  140. i -= 1
  141. dp = repeat_string(tmp_ctx.digest(), pwd_len)
  142. assert len(dp) == pwd_len
  143. #===================================================================
  144. # digest S - used instead of salt itself when calculating digest C
  145. #===================================================================
  146. ds = hash_const(salt * (16 + byte_elem_value(da[0]))).digest()[:salt_len]
  147. assert len(ds) == salt_len, "salt_len somehow > hash_len!"
  148. #===================================================================
  149. # digest C - for a variable number of rounds, combine A, S, and P
  150. # digests in various ways; in order to burn CPU time.
  151. #===================================================================
  152. # NOTE: the original SHA256/512-Crypt specification performs the C digest
  153. # calculation using the following loop:
  154. #
  155. ##dc = da
  156. ##i = 0
  157. ##while i < rounds:
  158. ## tmp_ctx = hash_const(dp if i & 1 else dc)
  159. ## if i % 3:
  160. ## tmp_ctx.update(ds)
  161. ## if i % 7:
  162. ## tmp_ctx.update(dp)
  163. ## tmp_ctx.update(dc if i & 1 else dp)
  164. ## dc = tmp_ctx.digest()
  165. ## i += 1
  166. #
  167. # The code Passlib uses (below) implements an equivalent algorithm,
  168. # it's just been heavily optimized to pre-calculate a large number
  169. # of things beforehand. It works off of a couple of observations
  170. # about the original algorithm:
  171. #
  172. # 1. each round is a combination of 'dc', 'ds', and 'dp'; determined
  173. # by the whether 'i' a multiple of 2,3, and/or 7.
  174. # 2. since lcm(2,3,7)==42, the series of combinations will repeat
  175. # every 42 rounds.
  176. # 3. even rounds 0-40 consist of 'hash(dc + round-specific-constant)';
  177. # while odd rounds 1-41 consist of hash(round-specific-constant + dc)
  178. #
  179. # Using these observations, the following code...
  180. # * calculates the round-specific combination of ds & dp for each round 0-41
  181. # * runs through as many 42-round blocks as possible
  182. # * runs through as many pairs of rounds as possible for remaining rounds
  183. # * performs once last round if the total rounds should be odd.
  184. #
  185. # this cuts out a lot of the control overhead incurred when running the
  186. # original loop 40,000+ times in python, resulting in ~20% increase in
  187. # speed under CPython (though still 2x slower than glibc crypt)
  188. # prepare the 6 combinations of ds & dp which are needed
  189. # (order of 'perms' must match how _c_digest_offsets was generated)
  190. dp_dp = dp+dp
  191. dp_ds = dp+ds
  192. perms = [dp, dp_dp, dp_ds, dp_ds+dp, ds+dp, ds+dp_dp]
  193. # build up list of even-round & odd-round constants,
  194. # and store in 21-element list as (even,odd) pairs.
  195. data = [ (perms[even], perms[odd]) for even, odd in _c_digest_offsets]
  196. # perform as many full 42-round blocks as possible
  197. dc = da
  198. blocks, tail = divmod(rounds, 42)
  199. while blocks:
  200. for even, odd in data:
  201. dc = hash_const(odd + hash_const(dc + even).digest()).digest()
  202. blocks -= 1
  203. # perform any leftover rounds
  204. if tail:
  205. # perform any pairs of rounds
  206. pairs = tail>>1
  207. for even, odd in data[:pairs]:
  208. dc = hash_const(odd + hash_const(dc + even).digest()).digest()
  209. # if rounds was odd, do one last round (since we started at 0,
  210. # last round will be an even-numbered round)
  211. if tail & 1:
  212. dc = hash_const(dc + data[pairs][0]).digest()
  213. #===================================================================
  214. # encode digest using appropriate transpose map
  215. #===================================================================
  216. return h64.encode_transposed_bytes(dc, transpose_map).decode("ascii")
  217. #=============================================================================
  218. # handlers
  219. #=============================================================================
  220. _UROUNDS = u("rounds=")
  221. _UDOLLAR = u("$")
  222. _UZERO = u("0")
  223. class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
  224. uh.GenericHandler):
  225. """class containing common code shared by sha256_crypt & sha512_crypt"""
  226. #===================================================================
  227. # class attrs
  228. #===================================================================
  229. # name - set by subclass
  230. setting_kwds = ("salt", "rounds", "implicit_rounds", "salt_size")
  231. # ident - set by subclass
  232. checksum_chars = uh.HASH64_CHARS
  233. # checksum_size - set by subclass
  234. max_salt_size = 16
  235. salt_chars = uh.HASH64_CHARS
  236. min_rounds = 1000 # bounds set by spec
  237. max_rounds = 999999999 # bounds set by spec
  238. rounds_cost = "linear"
  239. _cdb_use_512 = False # flag for _calc_digest_builtin()
  240. _rounds_prefix = None # ident + _UROUNDS
  241. #===================================================================
  242. # methods
  243. #===================================================================
  244. implicit_rounds = False
  245. def __init__(self, implicit_rounds=None, **kwds):
  246. super(_SHA2_Common, self).__init__(**kwds)
  247. # if user calls hash() w/ 5000 rounds, default to compact form.
  248. if implicit_rounds is None:
  249. implicit_rounds = (self.use_defaults and self.rounds == 5000)
  250. self.implicit_rounds = implicit_rounds
  251. def _parse_salt(self, salt):
  252. # required per SHA2-crypt spec -- truncate config salts rather than throwing error
  253. return self._norm_salt(salt, relaxed=self.checksum is None)
  254. def _parse_rounds(self, rounds):
  255. # required per SHA2-crypt spec -- clip config rounds rather than throwing error
  256. return self._norm_rounds(rounds, relaxed=self.checksum is None)
  257. @classmethod
  258. def from_string(cls, hash):
  259. # basic format this parses -
  260. # $5$[rounds=<rounds>$]<salt>[$<checksum>]
  261. # TODO: this *could* use uh.parse_mc3(), except that the rounds
  262. # portion has a slightly different grammar.
  263. # convert to unicode, check for ident prefix, split on dollar signs.
  264. hash = to_unicode(hash, "ascii", "hash")
  265. ident = cls.ident
  266. if not hash.startswith(ident):
  267. raise uh.exc.InvalidHashError(cls)
  268. assert len(ident) == 3
  269. parts = hash[3:].split(_UDOLLAR)
  270. # extract rounds value
  271. if parts[0].startswith(_UROUNDS):
  272. assert len(_UROUNDS) == 7
  273. rounds = parts.pop(0)[7:]
  274. if rounds.startswith(_UZERO) and rounds != _UZERO:
  275. raise uh.exc.ZeroPaddedRoundsError(cls)
  276. rounds = int(rounds)
  277. implicit_rounds = False
  278. else:
  279. rounds = 5000
  280. implicit_rounds = True
  281. # rest should be salt and checksum
  282. if len(parts) == 2:
  283. salt, chk = parts
  284. elif len(parts) == 1:
  285. salt = parts[0]
  286. chk = None
  287. else:
  288. raise uh.exc.MalformedHashError(cls)
  289. # return new object
  290. return cls(
  291. rounds=rounds,
  292. salt=salt,
  293. checksum=chk or None,
  294. implicit_rounds=implicit_rounds,
  295. )
  296. def to_string(self):
  297. if self.rounds == 5000 and self.implicit_rounds:
  298. hash = u("%s%s$%s") % (self.ident, self.salt,
  299. self.checksum or u(''))
  300. else:
  301. hash = u("%srounds=%d$%s$%s") % (self.ident, self.rounds,
  302. self.salt, self.checksum or u(''))
  303. return uascii_to_str(hash)
  304. #===================================================================
  305. # backends
  306. #===================================================================
  307. backends = ("os_crypt", "builtin")
  308. #---------------------------------------------------------------
  309. # os_crypt backend
  310. #---------------------------------------------------------------
  311. #: test hash for OS detection -- provided by subclass
  312. _test_hash = None
  313. @classmethod
  314. def _load_backend_os_crypt(cls):
  315. if test_crypt(*cls._test_hash):
  316. cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
  317. return True
  318. else:
  319. return False
  320. def _calc_checksum_os_crypt(self, secret):
  321. hash = safe_crypt(secret, self.to_string())
  322. if hash:
  323. # NOTE: avoiding full parsing routine via from_string().checksum,
  324. # and just extracting the bit we need.
  325. cs = self.checksum_size
  326. assert hash.startswith(self.ident) and hash[-cs-1] == _UDOLLAR
  327. return hash[-cs:]
  328. else:
  329. # py3's crypt.crypt() can't handle non-utf8 bytes.
  330. # fallback to builtin alg, which is always available.
  331. return self._calc_checksum_builtin(secret)
  332. #---------------------------------------------------------------
  333. # builtin backend
  334. #---------------------------------------------------------------
  335. @classmethod
  336. def _load_backend_builtin(cls):
  337. cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
  338. return True
  339. def _calc_checksum_builtin(self, secret):
  340. return _raw_sha2_crypt(secret, self.salt, self.rounds,
  341. self._cdb_use_512)
  342. #===================================================================
  343. # eoc
  344. #===================================================================
  345. class sha256_crypt(_SHA2_Common):
  346. """This class implements the SHA256-Crypt password hash, and follows the :ref:`password-hash-api`.
  347. It supports a variable-length salt, and a variable number of rounds.
  348. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  349. :type salt: str
  350. :param salt:
  351. Optional salt string.
  352. If not specified, one will be autogenerated (this is recommended).
  353. If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
  354. :type rounds: int
  355. :param rounds:
  356. Optional number of rounds to use.
  357. Defaults to 535000, must be between 1000 and 999999999, inclusive.
  358. :type implicit_rounds: bool
  359. :param implicit_rounds:
  360. this is an internal option which generally doesn't need to be touched.
  361. this flag determines whether the hash should omit the rounds parameter
  362. when encoding it to a string; this is only permitted by the spec for rounds=5000,
  363. and the flag is ignored otherwise. the spec requires the two different
  364. encodings be preserved as they are, instead of normalizing them.
  365. :type relaxed: bool
  366. :param relaxed:
  367. By default, providing an invalid value for one of the other
  368. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  369. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  370. will be issued instead. Correctable errors include ``rounds``
  371. that are too small or too large, and ``salt`` strings that are too long.
  372. .. versionadded:: 1.6
  373. """
  374. #===================================================================
  375. # class attrs
  376. #===================================================================
  377. name = "sha256_crypt"
  378. ident = u("$5$")
  379. checksum_size = 43
  380. # NOTE: using 25/75 weighting of builtin & os_crypt backends
  381. default_rounds = 535000
  382. #===================================================================
  383. # backends
  384. #===================================================================
  385. _test_hash = ("test", "$5$rounds=1000$test$QmQADEXMG8POI5W"
  386. "Dsaeho0P36yK3Tcrgboabng6bkb/")
  387. #===================================================================
  388. # eoc
  389. #===================================================================
  390. #=============================================================================
  391. # sha 512 crypt
  392. #=============================================================================
  393. class sha512_crypt(_SHA2_Common):
  394. """This class implements the SHA512-Crypt password hash, and follows the :ref:`password-hash-api`.
  395. It supports a variable-length salt, and a variable number of rounds.
  396. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  397. :type salt: str
  398. :param salt:
  399. Optional salt string.
  400. If not specified, one will be autogenerated (this is recommended).
  401. If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
  402. :type rounds: int
  403. :param rounds:
  404. Optional number of rounds to use.
  405. Defaults to 656000, must be between 1000 and 999999999, inclusive.
  406. :type implicit_rounds: bool
  407. :param implicit_rounds:
  408. this is an internal option which generally doesn't need to be touched.
  409. this flag determines whether the hash should omit the rounds parameter
  410. when encoding it to a string; this is only permitted by the spec for rounds=5000,
  411. and the flag is ignored otherwise. the spec requires the two different
  412. encodings be preserved as they are, instead of normalizing them.
  413. :type relaxed: bool
  414. :param relaxed:
  415. By default, providing an invalid value for one of the other
  416. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  417. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  418. will be issued instead. Correctable errors include ``rounds``
  419. that are too small or too large, and ``salt`` strings that are too long.
  420. .. versionadded:: 1.6
  421. """
  422. #===================================================================
  423. # class attrs
  424. #===================================================================
  425. name = "sha512_crypt"
  426. ident = u("$6$")
  427. checksum_size = 86
  428. _cdb_use_512 = True
  429. # NOTE: using 25/75 weighting of builtin & os_crypt backends
  430. default_rounds = 656000
  431. #===================================================================
  432. # backend
  433. #===================================================================
  434. _test_hash = ("test", "$6$rounds=1000$test$2M/Lx6Mtobqj"
  435. "Ljobw0Wmo4Q5OFx5nVLJvmgseatA6oMn"
  436. "yWeBdRDx4DU.1H3eGmse6pgsOgDisWBG"
  437. "I5c7TZauS0")
  438. #===================================================================
  439. # eoc
  440. #===================================================================
  441. #=============================================================================
  442. # eof
  443. #=============================================================================