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