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