Add support for bcrypt_sha256 hasher

This patch adds new hashing alhorythm bcrypt_sha256, which is based on
the bcrypt but does not have limitations on the leght of the passwords,
since passwords are passed through HMAC-SHA2-256 first.
At accepts exactly same parameters as bcrypt does.
However, it prefix the hash using `prefix` attribute rather then
`indent_values` which are same as for bcrypt.

Change-Id: I5430ebf5a20142c1a9caab960ced9b3ee2e782c1
This commit is contained in:
Dmitriy Rabotyagov 2023-08-10 11:55:57 +02:00
parent 6730c761d1
commit 9b0b414e3e
4 changed files with 37 additions and 5 deletions

View File

@ -27,6 +27,7 @@ CONF = keystone.conf.CONF
LOG = log.getLogger(__name__)
SUPPORTED_HASHERS = frozenset([passlib.hash.bcrypt,
passlib.hash.bcrypt_sha256,
passlib.hash.scrypt,
passlib.hash.pbkdf2_sha512,
passlib.hash.sha512_crypt])
@ -39,12 +40,26 @@ _HASHER_NAME_MAP = {hasher.name: hasher for hasher in SUPPORTED_HASHERS}
# '$<ident>$<metadata>$<hash>') so we can do a fast-lookup on the hasher to
# use. If has hasher has multiple ident options it is encoded in the
# .ident_values attribute whereas hashers that have a single option
# (sha512_crypt) only has the .ident attribute.
# ( ) only has the .ident attribute.
# NOTE(noonedeadpunk): Though bcrypt_sha256 does define <ident> as part of
# the metadata, actual indent is represented with a <prefix> instead.
def _get_hash_ident(hashers):
for hasher in hashers:
if hasattr(hasher, 'prefix'):
ident = (getattr(hasher, 'prefix'),)
elif hasattr(hasher, 'ident_values'):
ident = getattr(hasher, 'ident_values')
else:
ident = (getattr(hasher, 'ident'),)
yield (hasher, ident)
_HASHER_IDENT_MAP = {
prefix: module for module, prefix in itertools.chain(
*[zip([mod] * len(getattr(mod, 'ident_values', (mod.ident,))),
getattr(mod, 'ident_values', (mod.ident,)))
for mod in SUPPORTED_HASHERS])}
*[zip([mod] * len(ident), ident)
for mod, ident in _get_hash_ident(SUPPORTED_HASHERS)]
)
}
def _get_hasher_from_ident(hashed):

View File

@ -114,7 +114,7 @@ Maximum number of entities that will be returned in an identity collection.
password_hash_algorithm = cfg.StrOpt(
'password_hash_algorithm',
choices=['bcrypt', 'scrypt', 'pbkdf2_sha512'],
choices=['bcrypt', 'bcrypt_sha256', 'scrypt', 'pbkdf2_sha512'],
default='bcrypt',
help=utils.fmt("""
The password hashing algorithm to use for passwords stored within keystone.

View File

@ -145,6 +145,17 @@ class UtilsTestCase(unit.BaseTestCase):
common_utils.hash_password,
invalid_length_password)
def test_bcrypt_sha256_not_truncate_password(self):
self.config_fixture.config(strict_password_check=True)
self.config_fixture.config(group='identity',
password_hash_algorithm='bcrypt_sha256')
password = '0' * 128
password_verified = \
common_utils.verify_length_and_trunc_password(password)
hashed = common_utils.hash_password(password)
self.assertTrue(common_utils.check_password(password, hashed))
self.assertEqual(password.encode('utf-8'), password_verified)
def _create_test_user(self, password=OPTIONAL):
user = {"name": "hthtest"}
if password is not self.OPTIONAL:

View File

@ -0,0 +1,6 @@
---
features:
- |
Added support for the ``bcrypt_sha256`` password hashing algorythm, which
does workaround limitation on a password length BCrypt have by running the
password through HMAC-SHA2-256 first.