__init__.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # Author:: Donald Stufft (<donald@stufft.io>)
  2. # Copyright:: Copyright (c) 2013 Donald Stufft
  3. # License:: Apache License, Version 2.0
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. from __future__ import absolute_import
  17. from __future__ import division
  18. import os
  19. import re
  20. import warnings
  21. import six
  22. from . import _bcrypt # type: ignore
  23. from .__about__ import (
  24. __author__,
  25. __copyright__,
  26. __email__,
  27. __license__,
  28. __summary__,
  29. __title__,
  30. __uri__,
  31. __version__,
  32. )
  33. __all__ = [
  34. "__title__",
  35. "__summary__",
  36. "__uri__",
  37. "__version__",
  38. "__author__",
  39. "__email__",
  40. "__license__",
  41. "__copyright__",
  42. "gensalt",
  43. "hashpw",
  44. "kdf",
  45. "checkpw",
  46. ]
  47. _normalize_re = re.compile(br"^\$2y\$")
  48. def gensalt(rounds: int = 12, prefix: bytes = b"2b") -> bytes:
  49. if prefix not in (b"2a", b"2b"):
  50. raise ValueError("Supported prefixes are b'2a' or b'2b'")
  51. if rounds < 4 or rounds > 31:
  52. raise ValueError("Invalid rounds")
  53. salt = os.urandom(16)
  54. output = _bcrypt.ffi.new("char[]", 30)
  55. _bcrypt.lib.encode_base64(output, salt, len(salt))
  56. return (
  57. b"$"
  58. + prefix
  59. + b"$"
  60. + ("%2.2u" % rounds).encode("ascii")
  61. + b"$"
  62. + _bcrypt.ffi.string(output)
  63. )
  64. def hashpw(password: bytes, salt: bytes) -> bytes:
  65. if isinstance(password, six.text_type) or isinstance(salt, six.text_type):
  66. raise TypeError("Unicode-objects must be encoded before hashing")
  67. if b"\x00" in password:
  68. raise ValueError("password may not contain NUL bytes")
  69. # bcrypt originally suffered from a wraparound bug:
  70. # http://www.openwall.com/lists/oss-security/2012/01/02/4
  71. # This bug was corrected in the OpenBSD source by truncating inputs to 72
  72. # bytes on the updated prefix $2b$, but leaving $2a$ unchanged for
  73. # compatibility. However, pyca/bcrypt 2.0.0 *did* correctly truncate inputs
  74. # on $2a$, so we do it here to preserve compatibility with 2.0.0
  75. password = password[:72]
  76. # When the original 8bit bug was found the original library we supported
  77. # added a new prefix, $2y$, that fixes it. This prefix is exactly the same
  78. # as the $2b$ prefix added by OpenBSD other than the name. Since the
  79. # OpenBSD library does not support the $2y$ prefix, if the salt given to us
  80. # is for the $2y$ prefix, we'll just mugne it so that it's a $2b$ prior to
  81. # passing it into the C library.
  82. original_salt, salt = salt, _normalize_re.sub(b"$2b$", salt)
  83. hashed = _bcrypt.ffi.new("char[]", 128)
  84. retval = _bcrypt.lib.bcrypt_hashpass(password, salt, hashed, len(hashed))
  85. if retval != 0:
  86. raise ValueError("Invalid salt")
  87. # Now that we've gotten our hashed password, we want to ensure that the
  88. # prefix we return is the one that was passed in, so we'll use the prefix
  89. # from the original salt and concatenate that with the return value (minus
  90. # the return value's prefix). This will ensure that if someone passed in a
  91. # salt with a $2y$ prefix, that they get back a hash with a $2y$ prefix
  92. # even though we munged it to $2b$.
  93. return original_salt[:4] + _bcrypt.ffi.string(hashed)[4:]
  94. def checkpw(password: bytes, hashed_password: bytes) -> bool:
  95. if isinstance(password, six.text_type) or isinstance(
  96. hashed_password, six.text_type
  97. ):
  98. raise TypeError("Unicode-objects must be encoded before checking")
  99. if b"\x00" in password or b"\x00" in hashed_password:
  100. raise ValueError(
  101. "password and hashed_password may not contain NUL bytes"
  102. )
  103. ret = hashpw(password, hashed_password)
  104. if len(ret) != len(hashed_password):
  105. return False
  106. return _bcrypt.lib.timingsafe_bcmp(ret, hashed_password, len(ret)) == 0
  107. def kdf(
  108. password: bytes,
  109. salt: bytes,
  110. desired_key_bytes: int,
  111. rounds: int,
  112. ignore_few_rounds: bool = False,
  113. ) -> bytes:
  114. if isinstance(password, six.text_type) or isinstance(salt, six.text_type):
  115. raise TypeError("Unicode-objects must be encoded before hashing")
  116. if len(password) == 0 or len(salt) == 0:
  117. raise ValueError("password and salt must not be empty")
  118. if desired_key_bytes <= 0 or desired_key_bytes > 512:
  119. raise ValueError("desired_key_bytes must be 1-512")
  120. if rounds < 1:
  121. raise ValueError("rounds must be 1 or more")
  122. if rounds < 50 and not ignore_few_rounds:
  123. # They probably think bcrypt.kdf()'s rounds parameter is logarithmic,
  124. # expecting this value to be slow enough (it probably would be if this
  125. # were bcrypt). Emit a warning.
  126. warnings.warn(
  127. (
  128. "Warning: bcrypt.kdf() called with only {0} round(s). "
  129. "This few is not secure: the parameter is linear, like PBKDF2."
  130. ).format(rounds),
  131. UserWarning,
  132. stacklevel=2,
  133. )
  134. key = _bcrypt.ffi.new("uint8_t[]", desired_key_bytes)
  135. res = _bcrypt.lib.bcrypt_pbkdf(
  136. password, len(password), salt, len(salt), key, len(key), rounds
  137. )
  138. _bcrypt_assert(res == 0)
  139. return _bcrypt.ffi.buffer(key, desired_key_bytes)[:]
  140. def _bcrypt_assert(ok: bool) -> None:
  141. if not ok:
  142. raise SystemError("bcrypt assertion failed")