Add checkpw (#76)

This commit is contained in:
Paul Kehrer 2016-06-29 09:27:21 -05:00 committed by Donald Stufft
parent 81e8efd0cf
commit c9a9ec1e7a
4 changed files with 83 additions and 5 deletions

View File

@ -37,6 +37,10 @@ For Fedora and RHEL-derivatives, the following command will ensure that the requ
Changelog
=========
3.1.0
-----
* Added support for ``checkpw`` as another method of verifying a password.
3.0.0
-----
* Switched the C backend to code obtained from the OpenBSD project rather than
@ -51,8 +55,8 @@ Changelog
Usage
-----
Hashing
~~~~~~~
Password Hashing
~~~~~~~~~~~~~~~~
Hashing and then later checking that a password matches the previous hashed
password is very simple:
@ -63,9 +67,9 @@ password is very simple:
>>> password = b"super secret password"
>>> # Hash a password for the first time, with a randomly-generated salt
>>> hashed = bcrypt.hashpw(password, bcrypt.gensalt())
>>> # Check that a unhashed password matches one that has previously been
>>> # hashed
>>> if bcrypt.hashpw(password, hashed) == hashed:
>>> # Check that an unhashed password matches one that has previously been
>>> # hashed
>>> if bcrypt.checkpw(password, hashed):
... print("It Matches!")
... else:
... print("It Does not Match :(")

View File

@ -78,6 +78,24 @@ def hashpw(password, salt):
return _bcrypt.ffi.string(hashed)
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"
)
# If the user supplies a $2y$ prefix we normalize to $2b$
hashed_password = _normalize_prefix(hashed_password)
ret = hashpw(password, hashed_password)
return _bcrypt.lib.timingsafe_bcmp(ret, hashed_password, len(ret)) == 0
def kdf(password, salt, desired_key_bytes, rounds):
if isinstance(password, six.text_type) or isinstance(salt, six.text_type):
raise TypeError("Unicode-objects must be encoded before hashing")

View File

@ -25,6 +25,7 @@ int bcrypt_hashpass(const char *, const char *, char *, size_t);
int encode_base64(char *, const uint8_t *, size_t);
int bcrypt_pbkdf(const char *, size_t, const uint8_t *, size_t,
uint8_t *, size_t, unsigned int);
int timingsafe_bcmp(const void *, const void *, size_t);
""")
ffi.set_source(

View File

@ -216,6 +216,11 @@ def test_hashpw_new(password, salt, hashed):
assert bcrypt.hashpw(password, salt) == hashed
@pytest.mark.parametrize(("password", "salt", "hashed"), _test_vectors)
def test_checkpw(password, salt, hashed):
assert bcrypt.checkpw(password, hashed) is True
@pytest.mark.parametrize(("password", "salt", "hashed"), _test_vectors)
def test_hashpw_existing(password, salt, hashed):
assert bcrypt.hashpw(password, hashed) == hashed
@ -226,11 +231,47 @@ def test_hashpw_2y_prefix(password, hashed, expected):
assert bcrypt.hashpw(password, hashed) == expected
@pytest.mark.parametrize(("password", "hashed", "expected"), _2y_test_vectors)
def test_checkpw_2y_prefix(password, hashed, expected):
assert bcrypt.checkpw(password, hashed) is True
def test_hashpw_invalid():
with pytest.raises(ValueError):
bcrypt.hashpw(b"password", b"$2z$04$cVWp4XaNU8a4v1uMRum2SO")
def test_checkpw_wrong_password():
assert bcrypt.checkpw(
b"badpass",
b"$2b$04$2Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
) is False
def test_checkpw_bad_salt():
with pytest.raises(ValueError):
bcrypt.checkpw(
b"badpass",
b"$2b$04$?Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
)
def test_checkpw_str_password():
with pytest.raises(TypeError):
bcrypt.checkpw(
six.text_type("password"),
b"$2b$04$cVWp4XaNU8a4v1uMRum2SO",
)
def test_checkpw_str_salt():
with pytest.raises(TypeError):
bcrypt.checkpw(
b"password",
six.text_type("$2b$04$cVWp4XaNU8a4v1uMRum2SO"),
)
def test_hashpw_str_password():
with pytest.raises(TypeError):
bcrypt.hashpw(
@ -247,6 +288,20 @@ def test_hashpw_str_salt():
)
def test_checkpw_nul_byte():
with pytest.raises(ValueError):
bcrypt.checkpw(
b"abc\0def",
b"$2b$04$2Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
)
with pytest.raises(ValueError):
bcrypt.checkpw(
b"abcdef",
b"$2b$04$2S\0w3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
)
def test_hashpw_nul_byte():
salt = bcrypt.gensalt(4)
with pytest.raises(ValueError):