From 1050ddf44f0713a587cd0ba239e23c95064a39bc Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 27 Jan 2014 21:04:03 -0600 Subject: PBKDF2 support for OpenSSL backend --- cryptography/hazmat/backends/interfaces.py | 15 ++++++++ cryptography/hazmat/backends/openssl/backend.py | 46 +++++++++++++++++++++++-- cryptography/hazmat/primitives/kdf/__init__.py | 0 cryptography/hazmat/primitives/kdf/pbkdf2.py | 46 +++++++++++++++++++++++++ docs/hazmat/backends/interfaces.rst | 39 +++++++++++++++++++++ pytest.ini | 3 +- tests/conftest.py | 3 +- tests/hazmat/primitives/test_pbkdf2_vectors.py | 37 ++++++++++++++++++++ tests/hazmat/primitives/utils.py | 25 ++++++++++++++ tests/utils.py | 4 +++ 10 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 cryptography/hazmat/primitives/kdf/__init__.py create mode 100644 cryptography/hazmat/primitives/kdf/pbkdf2.py create mode 100644 tests/hazmat/primitives/test_pbkdf2_vectors.py diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 4fbb3488..936520eb 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -65,3 +65,18 @@ class HMACBackend(six.with_metaclass(abc.ABCMeta)): """ Create a HashContext for calculating a message authentication code. """ + + +class PBKDF2Backend(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def pbkdf2_hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported for PBKDF2 by this + backend. + """ + + @abc.abstractmethod + def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + """ + Return length bytes derived from provided PBKDF2 parameters. + """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index d8d4669a..ca7d1778 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -20,9 +20,9 @@ from cryptography.exceptions import ( UnsupportedAlgorithm, InvalidTag, InternalError ) from cryptography.hazmat.backends.interfaces import ( - CipherBackend, HashBackend, HMACBackend + CipherBackend, HashBackend, HMACBackend, PBKDF2Backend ) -from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives import interfaces, hashes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, Blowfish, Camellia, TripleDES, ARC4, ) @@ -35,6 +35,7 @@ from cryptography.hazmat.bindings.openssl.binding import Binding @utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2Backend) class Backend(object): """ OpenSSL API binding interfaces. @@ -133,6 +134,47 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + def pbkdf2_hash_supported(self, algorithm): + if self._lib.Cryptography_HAS_PBKDF2_HMAC: + digest = self._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + return digest != self._ffi.NULL + else: + return isinstance(algorithm, hashes.SHA1) + + def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + buf = self._ffi.new("char[]", length) + if self._lib.Cryptography_HAS_PBKDF2_HMAC: + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + assert evp_md != self._ffi.NULL + res = self._lib.PKCS5_PBKDF2_HMAC( + key_material, + len(key_material), + salt, + len(salt), + iterations, + evp_md, + length, + buf + ) + assert res == 1 + else: + # OpenSSL < 1.0.0 + assert isinstance(algorithm, hashes.SHA1) + res = self._lib.PKCS5_PBKDF2_HMAC_SHA1( + key_material, + len(key_material), + salt, + len(salt), + iterations, + length, + buf + ) + assert res == 1 + + return self._ffi.buffer(buf)[:] + def _handle_error(self, mode): code = self._lib.ERR_get_error() if not code and isinstance(mode, GCM): diff --git a/cryptography/hazmat/primitives/kdf/__init__.py b/cryptography/hazmat/primitives/kdf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py new file mode 100644 index 00000000..cc01246f --- /dev/null +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -0,0 +1,46 @@ +# 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, division, print_function + +from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm +from cryptography.hazmat.primitives import constant_time + + +class PBKDF2(object): + def __init__(self, algorithm, length, salt, iterations, backend): + if not backend.pbkdf2_hash_supported(algorithm): + raise UnsupportedAlgorithm( + "{0} is not supported by this backend".format(algorithm.name) + ) + self.algorithm = algorithm + if length > 2**31 - 1: + raise ValueError("Requested length too large.") + self._length = length + # TODO: handle salt + self._salt = salt + self.iterations = iterations + self._backend = backend + + def derive(self, key_material): + return self._backend.derive_pbkdf2( + self.algorithm, + self._length, + self._salt, + self.iterations, + key_material + ) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(key_material, expected_key): + raise InvalidKey("Signature did not match digest.") diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 11e2f2a2..fa4f800c 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -131,3 +131,42 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: :class:`~cryptography.hazmat.primitives.interfaces.HashContext` + + + +.. class:: PBKDF2Backend + + A backend with methods for using PBKDF2. + + .. method:: pbkdf2_hash_supported(algorithm) + + Check if the specified ``algorithm`` is supported by this backend. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: ``True`` if the specified ``algorithm`` is supported for + PBKDF2 by this backend, otherwise ``False``. + + .. method:: derive_pbkdf2(self, algorithm, length, salt, iterations, + key_material) + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param int length: The desired length of the derived key. Maximum is + 2\ :sup:`31` - 1. + + :param bytes salt: A salt. `RFC 2898`_ recommends 64-bits or longer. + + :param int iterations: The number of iterations to perform of the hash + function. + + :param bytes key_material: The key material to use as a basis for + the derived key. This is typically a password. + + :return bytes: Derived key. + +.. _`RFC 2898`: https://www.ietf.org/rfc/rfc2898.txt diff --git a/pytest.ini b/pytest.ini index 36d4edc4..89fda539 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,7 +1,8 @@ [pytest] addopts = -r s markers = - hmac: this test requires a backend providing HMACBackend cipher: this test requires a backend providing CipherBackend hash: this test requires a backend providing HashBackend + hmac: this test requires a backend providing HMACBackend + pbkdf2: this test requires a backend providing PBKDF2Backend supported: parametrized test requiring only_if and skip_message diff --git a/tests/conftest.py b/tests/conftest.py index a9acb54a..7370294f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pytest from cryptography.hazmat.backends import _ALL_BACKENDS from cryptography.hazmat.backends.interfaces import ( - HMACBackend, CipherBackend, HashBackend + HMACBackend, CipherBackend, HashBackend, PBKDF2Backend ) from .utils import check_for_iface, check_backend_support, select_backends @@ -21,6 +21,7 @@ def pytest_runtest_setup(item): check_for_iface("hmac", HMACBackend, item) check_for_iface("cipher", CipherBackend, item) check_for_iface("hash", HashBackend, item) + check_for_iface("pbkdf2", PBKDF2Backend, item) check_backend_support(item) diff --git a/tests/hazmat/primitives/test_pbkdf2_vectors.py b/tests/hazmat/primitives/test_pbkdf2_vectors.py new file mode 100644 index 00000000..e6e3935f --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2_vectors.py @@ -0,0 +1,37 @@ +# 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, division, print_function + +import pytest + +from cryptography.hazmat.primitives import hashes + +from .utils import generate_pbkdf2_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.pbkdf2_hash_supported(hashes.SHA1()), + skip_message="Does not support SHA1 for PBKDF2", +) +@pytest.mark.pbkdf2 +class TestPBKDF2_SHA1(object): + test_pbkdf2_sha1 = generate_pbkdf2_test( + load_nist_vectors, + "KDF", + [ + "rfc-6070-PBKDF2-SHA1.txt", + ], + hashes.SHA1(), + ) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index f27afe41..3a1d6d88 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -4,6 +4,7 @@ import os import pytest from cryptography.hazmat.primitives import hashes, hmac +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.exceptions import ( AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, @@ -211,6 +212,30 @@ def hmac_test(backend, algorithm, params): assert h.finalize() == binascii.unhexlify(md.encode("ascii")) +def generate_pbkdf2_test(param_loader, path, file_names, algorithm): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_pbkdf2(self, backend, params): + pbkdf2_test(backend, algorithm, params) + return test_pbkdf2 + + +def pbkdf2_test(backend, algorithm, params): + # Password and salt can contain \0, which should be loaded as a null char. + # The NIST loader loads them as literal strings so we replace with the + # proper value. + kdf = PBKDF2( + algorithm, + int(params["length"]), + params["salt"], + int(params["iterations"]), + backend + ) + derived_key = kdf.derive(params["password"]) + assert binascii.hexlify(derived_key) == params["derived_key"] + + def generate_aead_exception_test(cipher_factory, mode_factory): def test_aead_exception(self, backend): aead_exception_test(backend, cipher_factory, mode_factory) diff --git a/tests/utils.py b/tests/utils.py index 507bc421..5c0e524f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -89,6 +89,10 @@ def load_nist_vectors(vector_data): # Build our data using a simple Key = Value format name, value = [c.strip() for c in line.split("=")] + # Some tests (PBKDF2) contain \0, which should be interpreted as a + # null character rather than literal. + value = value.replace("\\0", "\0") + # COUNT is a special token that indicates a new block of data if name.upper() == "COUNT": test_data = {} -- cgit v1.2.3 From b6d764c3f28837ed8854dfa836029a0b4650246f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 27 Jan 2014 22:32:11 -0600 Subject: pbkdf2 docs --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 2 +- docs/hazmat/backends/interfaces.rst | 3 +- docs/hazmat/primitives/index.rst | 1 + .../hazmat/primitives/key-derivation-functions.rst | 40 ++++++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 docs/hazmat/primitives/key-derivation-functions.rst diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index cc01246f..014529b0 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -43,4 +43,4 @@ class PBKDF2(object): def verify(self, key_material, expected_key): if not constant_time.bytes_eq(key_material, expected_key): - raise InvalidKey("Signature did not match digest.") + raise InvalidKey("Keys do not match.") diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index fa4f800c..14ca6880 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -159,7 +159,7 @@ A specific ``backend`` may provide one or more of these interfaces. :param int length: The desired length of the derived key. Maximum is 2\ :sup:`31` - 1. - :param bytes salt: A salt. `RFC 2898`_ recommends 64-bits or longer. + :param bytes salt: A salt. :param int iterations: The number of iterations to perform of the hash function. @@ -169,4 +169,3 @@ A specific ``backend`` may provide one or more of these interfaces. :return bytes: Derived key. -.. _`RFC 2898`: https://www.ietf.org/rfc/rfc2898.txt diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst index b115fdbc..2a29bd8f 100644 --- a/docs/hazmat/primitives/index.rst +++ b/docs/hazmat/primitives/index.rst @@ -9,6 +9,7 @@ Primitives cryptographic-hashes hmac symmetric-encryption + key-derivation-functions padding constant-time interfaces diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst new file mode 100644 index 00000000..af2d910f --- /dev/null +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -0,0 +1,40 @@ +.. hazmat:: + +Key Derivation Functions +======================== + +.. currentmodule:: cryptography.hazmat.primitives.kdf + +Key derivation functions derive key material from information such as passwords +using a pseudo-random function (PRF). + +.. class:: PBKDF2(algorithm, length, salt, iterations, backend): + + .. doctest:: + + >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> # derive + >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> key = kdf.derive(b"my great password") + >>> # verify + >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> kdf.verify(b"my great password", key) + None + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param int length: The desired length of the derived key. Maximum is + 2\ :sup:`31` - 1. + + :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or + longer. + + :param int iterations: The number of iterations to perform of the hash + function. + +.. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf -- cgit v1.2.3 From 6f2a04b4cf3cb938cdd58205a4fc7e8ddb6af299 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 12:05:51 -0600 Subject: test coverage, other changes --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 17 ++++++-- tests/hazmat/primitives/test_pbkdf2.py | 63 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 tests/hazmat/primitives/test_pbkdf2.py diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 014529b0..27f9c7e2 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -13,26 +13,34 @@ from __future__ import absolute_import, division, print_function -from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm -from cryptography.hazmat.primitives import constant_time +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, AlreadyFinalized +) +from cryptography.hazmat.primitives import constant_time, interfaces +@utils.register_interface(interfaces.KeyDerivationFunction) class PBKDF2(object): def __init__(self, algorithm, length, salt, iterations, backend): if not backend.pbkdf2_hash_supported(algorithm): raise UnsupportedAlgorithm( "{0} is not supported by this backend".format(algorithm.name) ) + self._called = False self.algorithm = algorithm if length > 2**31 - 1: raise ValueError("Requested length too large.") self._length = length - # TODO: handle salt self._salt = salt self.iterations = iterations self._backend = backend def derive(self, key_material): + if self._called: + raise AlreadyFinalized("PBKDF2 instances can only be called once") + else: + self._called = True return self._backend.derive_pbkdf2( self.algorithm, self._length, @@ -42,5 +50,6 @@ class PBKDF2(object): ) def verify(self, key_material, expected_key): - if not constant_time.bytes_eq(key_material, expected_key): + derived_key = self.derive(key_material) + if not constant_time.bytes_eq(derived_key, expected_key): raise InvalidKey("Keys do not match.") diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py new file mode 100644 index 00000000..6dd10129 --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -0,0 +1,63 @@ +# 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, division, print_function + +import pytest + +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, AlreadyFinalized +) +from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 +from cryptography.hazmat.backends import default_backend + + +@utils.register_interface(interfaces.HashAlgorithm) +class UnsupportedDummyHash(object): + name = "unsupported-dummy-hash" + + +class TestPBKDF2(object): + def test_already_finalized(self): + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.derive(b"password2") + + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.verify(b"password", key) + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + def test_unsupported_algorithm(self): + with pytest.raises(UnsupportedAlgorithm): + PBKDF2(UnsupportedDummyHash(), 20, b"salt", 10, default_backend()) + + def test_invalid_key(self): + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(InvalidKey): + kdf.verify(b"password2", key) + + def test_salt_too_long(self): + with pytest.raises(ValueError): + PBKDF2(hashes.SHA1(), 2**31, b"salt", 10, default_backend()) -- cgit v1.2.3 From 5d1af21519c728f1514efc1018eb427e7fb18559 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 12:19:32 -0600 Subject: documentation improvements for KDF --- docs/hazmat/backends/interfaces.rst | 4 ++- docs/hazmat/primitives/index.rst | 2 +- .../hazmat/primitives/key-derivation-functions.rst | 31 +++++++++++++--------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 14ca6880..975a7b02 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -136,6 +136,8 @@ A specific ``backend`` may provide one or more of these interfaces. .. class:: PBKDF2Backend + .. versionadded:: 0.2 + A backend with methods for using PBKDF2. .. method:: pbkdf2_hash_supported(algorithm) @@ -157,7 +159,7 @@ A specific ``backend`` may provide one or more of these interfaces. provider. :param int length: The desired length of the derived key. Maximum is - 2\ :sup:`31` - 1. + 2\ :sup:`31` - 1. :param bytes salt: A salt. diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst index 2a29bd8f..bde07392 100644 --- a/docs/hazmat/primitives/index.rst +++ b/docs/hazmat/primitives/index.rst @@ -9,7 +9,7 @@ Primitives cryptographic-hashes hmac symmetric-encryption - key-derivation-functions padding + key-derivation-functions constant-time interfaces diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index af2d910f..51d73bc2 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -10,8 +10,16 @@ using a pseudo-random function (PRF). .. class:: PBKDF2(algorithm, length, salt, iterations, backend): + .. versionadded:: 0.2 + + This class conforms to the + :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` + interface. + .. doctest:: + >>> import os + >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 >>> from cryptography.hazmat.backends import default_backend >>> backend = default_backend() @@ -22,19 +30,18 @@ using a pseudo-random function (PRF). >>> # verify >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) >>> kdf.verify(b"my great password", key) - None - - :param algorithm: An instance of a - :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` - provider. - :param int length: The desired length of the derived key. Maximum is + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + :param int length: The desired length of the derived key. Maximum is 2\ :sup:`31` - 1. - - :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or - longer. - - :param int iterations: The number of iterations to perform of the hash - function. + :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or + longer. + :param int iterations: The number of iterations to perform of the hash + function. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + provider. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf -- cgit v1.2.3 From 695313ff7cb1a7026a2624b8c61d495978d6f41c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 13:13:48 -0600 Subject: remove length check (which cffi handles) --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 2 -- tests/hazmat/primitives/test_pbkdf2.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 27f9c7e2..1cc35f60 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -29,8 +29,6 @@ class PBKDF2(object): ) self._called = False self.algorithm = algorithm - if length > 2**31 - 1: - raise ValueError("Requested length too large.") self._length = length self._salt = salt self.iterations = iterations diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py index 6dd10129..4d15d7a7 100644 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -57,7 +57,3 @@ class TestPBKDF2(object): kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) with pytest.raises(InvalidKey): kdf.verify(b"password2", key) - - def test_salt_too_long(self): - with pytest.raises(ValueError): - PBKDF2(hashes.SHA1(), 2**31, b"salt", 10, default_backend()) -- cgit v1.2.3 From 98e40e658ef00dc6972f5420896bd57b385c8435 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 15:07:49 -0600 Subject: rename PBKDF2 to PBKDF2HMAC, address many other review comments --- cryptography/hazmat/backends/interfaces.py | 7 ++++--- cryptography/hazmat/backends/openssl/backend.py | 12 ++++++++---- cryptography/hazmat/primitives/kdf/pbkdf2.py | 5 +++-- docs/hazmat/backends/interfaces.rst | 16 ++++++++-------- docs/hazmat/primitives/key-derivation-functions.rst | 2 +- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 936520eb..53c75181 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -67,16 +67,17 @@ class HMACBackend(six.with_metaclass(abc.ABCMeta)): """ -class PBKDF2Backend(six.with_metaclass(abc.ABCMeta)): +class PBKDF2HMACBackend(six.with_metaclass(abc.ABCMeta)): @abc.abstractmethod - def pbkdf2_hash_supported(self, algorithm): + def pbkdf2_hmac_supported(self, algorithm): """ Return True if the hash algorithm is supported for PBKDF2 by this backend. """ @abc.abstractmethod - def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): """ Return length bytes derived from provided PBKDF2 parameters. """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index ca7d1778..dbdb2e56 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -20,7 +20,7 @@ from cryptography.exceptions import ( UnsupportedAlgorithm, InvalidTag, InternalError ) from cryptography.hazmat.backends.interfaces import ( - CipherBackend, HashBackend, HMACBackend, PBKDF2Backend + CipherBackend, HashBackend, HMACBackend, PBKDF2HMACBackend ) from cryptography.hazmat.primitives import interfaces, hashes from cryptography.hazmat.primitives.ciphers.algorithms import ( @@ -35,7 +35,7 @@ from cryptography.hazmat.bindings.openssl.binding import Binding @utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) -@utils.register_interface(PBKDF2Backend) +@utils.register_interface(PBKDF2HMACBackend) class Backend(object): """ OpenSSL API binding interfaces. @@ -134,15 +134,19 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) - def pbkdf2_hash_supported(self, algorithm): + def pbkdf2_hmac_supported(self, algorithm): if self._lib.Cryptography_HAS_PBKDF2_HMAC: digest = self._lib.EVP_get_digestbyname( algorithm.name.encode("ascii")) return digest != self._ffi.NULL else: + # OpenSSL < 1.0.0 has an explicit PBKDF2-HMAC-SHA1 function, + # so if the PBKDF2_HMAC function is missing we only support + # SHA1 via PBKDF2_HMAC_SHA1. return isinstance(algorithm, hashes.SHA1) - def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): buf = self._ffi.new("char[]", length) if self._lib.Cryptography_HAS_PBKDF2_HMAC: evp_md = self._lib.EVP_get_digestbyname( diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 1cc35f60..940d9910 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -21,11 +21,12 @@ from cryptography.hazmat.primitives import constant_time, interfaces @utils.register_interface(interfaces.KeyDerivationFunction) -class PBKDF2(object): +class PBKDF2HMAC(object): def __init__(self, algorithm, length, salt, iterations, backend): if not backend.pbkdf2_hash_supported(algorithm): raise UnsupportedAlgorithm( - "{0} is not supported by this backend".format(algorithm.name) + "{0} is not supported for PBKDF2 by this backend".format( + algorithm.name) ) self._called = False self.algorithm = algorithm diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 975a7b02..e22c6bb3 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -134,32 +134,32 @@ A specific ``backend`` may provide one or more of these interfaces. -.. class:: PBKDF2Backend +.. class:: PBKDF2HMACBackend .. versionadded:: 0.2 - A backend with methods for using PBKDF2. + A backend with methods for using PBKDF2 using HMAC as a PRF. - .. method:: pbkdf2_hash_supported(algorithm) + .. method:: pbkdf2_hmac_supported(algorithm) Check if the specified ``algorithm`` is supported by this backend. - :param algorithm: An instance of a + :param prf: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :returns: ``True`` if the specified ``algorithm`` is supported for - PBKDF2 by this backend, otherwise ``False``. + PBKDF2 HMAC by this backend, otherwise ``False``. - .. method:: derive_pbkdf2(self, algorithm, length, salt, iterations, - key_material) + .. method:: derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material) :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :param int length: The desired length of the derived key. Maximum is - 2\ :sup:`31` - 1. + (2\ :sup:`32` - 1) * ``algorithm.digest_size`` :param bytes salt: A salt. diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 51d73bc2..bad7a36c 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -35,7 +35,7 @@ using a pseudo-random function (PRF). :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :param int length: The desired length of the derived key. Maximum is - 2\ :sup:`31` - 1. + (2\ :sup:`32` - 1) * ``algorithm.digest_size`` :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or longer. :param int iterations: The number of iterations to perform of the hash -- cgit v1.2.3 From b3f763f1beae2a5fa1fdd3c27b6e9cb777ce7f50 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 16:42:15 -0600 Subject: finish PBKDF2HMAC rename, more docs --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 4 ++-- .../hazmat/primitives/key-derivation-functions.rst | 26 +++++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 940d9910..a496cc27 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -23,7 +23,7 @@ from cryptography.hazmat.primitives import constant_time, interfaces @utils.register_interface(interfaces.KeyDerivationFunction) class PBKDF2HMAC(object): def __init__(self, algorithm, length, salt, iterations, backend): - if not backend.pbkdf2_hash_supported(algorithm): + if not backend.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( "{0} is not supported for PBKDF2 by this backend".format( algorithm.name) @@ -40,7 +40,7 @@ class PBKDF2HMAC(object): raise AlreadyFinalized("PBKDF2 instances can only be called once") else: self._called = True - return self._backend.derive_pbkdf2( + return self._backend.derive_pbkdf2_hmac( self.algorithm, self._length, self._salt, diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index bad7a36c..661b4611 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -8,7 +8,7 @@ Key Derivation Functions Key derivation functions derive key material from information such as passwords using a pseudo-random function (PRF). -.. class:: PBKDF2(algorithm, length, salt, iterations, backend): +.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): .. versionadded:: 0.2 @@ -20,28 +20,42 @@ using a pseudo-random function (PRF). >>> import os >>> from cryptography.hazmat.primitives import hashes - >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 + >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC >>> from cryptography.hazmat.backends import default_backend >>> backend = default_backend() >>> salt = os.urandom(16) >>> # derive - >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> kdf = PBKDF2HMAC( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... iterations=50000, + ... backend=backend + ... ) >>> key = kdf.derive(b"my great password") >>> # verify - >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> kdf = PBKDF2HMAC( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... iterations=50000, + ... backend=backend + ... ) >>> kdf.verify(b"my great password", key) :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :param int length: The desired length of the derived key. Maximum is - (2\ :sup:`32` - 1) * ``algorithm.digest_size`` + (2\ :sup:`32` - 1) * ``algorithm.digest_size``. :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or longer. :param int iterations: The number of iterations to perform of the hash - function. + function. See OWASP's `Password Storage Cheat Sheet`_ for more + detailed recommendations. :param backend: A :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` provider. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf +.. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet -- cgit v1.2.3 From 1277bc7e993dec8bbe64f1b5aeaaae6cff6429dd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 17:09:59 -0600 Subject: okay this time really finish the rename. Up example iterations to 100k --- docs/hazmat/primitives/key-derivation-functions.rst | 4 ++-- pytest.ini | 2 +- tests/conftest.py | 4 ++-- tests/hazmat/primitives/test_pbkdf2.py | 20 ++++++++++---------- tests/hazmat/primitives/test_pbkdf2_vectors.py | 8 ++++---- tests/hazmat/primitives/utils.py | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 661b4611..4cb67701 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -29,7 +29,7 @@ using a pseudo-random function (PRF). ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=50000, + ... iterations=100000, ... backend=backend ... ) >>> key = kdf.derive(b"my great password") @@ -38,7 +38,7 @@ using a pseudo-random function (PRF). ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=50000, + ... iterations=100000, ... backend=backend ... ) >>> kdf.verify(b"my great password", key) diff --git a/pytest.ini b/pytest.ini index 89fda539..2a1b6e9f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,5 +4,5 @@ markers = cipher: this test requires a backend providing CipherBackend hash: this test requires a backend providing HashBackend hmac: this test requires a backend providing HMACBackend - pbkdf2: this test requires a backend providing PBKDF2Backend + pbkdf2hmac: this test requires a backend providing PBKDF2HMACBackend supported: parametrized test requiring only_if and skip_message diff --git a/tests/conftest.py b/tests/conftest.py index 7370294f..ecad1b23 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pytest from cryptography.hazmat.backends import _ALL_BACKENDS from cryptography.hazmat.backends.interfaces import ( - HMACBackend, CipherBackend, HashBackend, PBKDF2Backend + HMACBackend, CipherBackend, HashBackend, PBKDF2HMACBackend ) from .utils import check_for_iface, check_backend_support, select_backends @@ -21,7 +21,7 @@ def pytest_runtest_setup(item): check_for_iface("hmac", HMACBackend, item) check_for_iface("cipher", CipherBackend, item) check_for_iface("hash", HashBackend, item) - check_for_iface("pbkdf2", PBKDF2Backend, item) + check_for_iface("pbkdf2hmac", PBKDF2HMACBackend, item) check_backend_support(item) diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py index 4d15d7a7..41123557 100644 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -20,40 +20,40 @@ from cryptography.exceptions import ( InvalidKey, UnsupportedAlgorithm, AlreadyFinalized ) from cryptography.hazmat.primitives import hashes, interfaces -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend @utils.register_interface(interfaces.HashAlgorithm) -class UnsupportedDummyHash(object): - name = "unsupported-dummy-hash" +class DummyHash(object): + name = "dummy-hash" -class TestPBKDF2(object): +class TestPBKDF2HMAC(object): def test_already_finalized(self): - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) kdf.derive(b"password") with pytest.raises(AlreadyFinalized): kdf.derive(b"password2") - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) key = kdf.derive(b"password") with pytest.raises(AlreadyFinalized): kdf.verify(b"password", key) - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) kdf.verify(b"password", key) with pytest.raises(AlreadyFinalized): kdf.verify(b"password", key) def test_unsupported_algorithm(self): with pytest.raises(UnsupportedAlgorithm): - PBKDF2(UnsupportedDummyHash(), 20, b"salt", 10, default_backend()) + PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) def test_invalid_key(self): - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) key = kdf.derive(b"password") - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) with pytest.raises(InvalidKey): kdf.verify(b"password2", key) diff --git a/tests/hazmat/primitives/test_pbkdf2_vectors.py b/tests/hazmat/primitives/test_pbkdf2_vectors.py index e6e3935f..cbd4cc9d 100644 --- a/tests/hazmat/primitives/test_pbkdf2_vectors.py +++ b/tests/hazmat/primitives/test_pbkdf2_vectors.py @@ -22,11 +22,11 @@ from ...utils import load_nist_vectors @pytest.mark.supported( - only_if=lambda backend: backend.pbkdf2_hash_supported(hashes.SHA1()), - skip_message="Does not support SHA1 for PBKDF2", + only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), + skip_message="Does not support SHA1 for PBKDF2HMAC", ) -@pytest.mark.pbkdf2 -class TestPBKDF2_SHA1(object): +@pytest.mark.pbkdf2hmac +class TestPBKDF2HMAC_SHA1(object): test_pbkdf2_sha1 = generate_pbkdf2_test( load_nist_vectors, "KDF", diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 3a1d6d88..6b1d055d 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -4,7 +4,7 @@ import os import pytest from cryptography.hazmat.primitives import hashes, hmac -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.exceptions import ( AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, @@ -225,7 +225,7 @@ def pbkdf2_test(backend, algorithm, params): # Password and salt can contain \0, which should be loaded as a null char. # The NIST loader loads them as literal strings so we replace with the # proper value. - kdf = PBKDF2( + kdf = PBKDF2HMAC( algorithm, int(params["length"]), params["salt"], -- cgit v1.2.3 From 3d8c66f9a01b8982902f69ae960fcc85aa43bfb8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 17:36:50 -0600 Subject: update docs with more detailed info re: PBKDF2 usage --- .../hazmat/primitives/key-derivation-functions.rst | 51 ++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 4cb67701..e16a9900 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -5,13 +5,21 @@ Key Derivation Functions .. currentmodule:: cryptography.hazmat.primitives.kdf -Key derivation functions derive key material from information such as passwords -using a pseudo-random function (PRF). +Key derivation functions derive key material from passwords or other data +sources using a pseudo-random function (PRF). Each KDF is suitable for +different tasks (cryptographic key derivation, password storage, +key stretching) so match your needs to their capabilities. .. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): .. versionadded:: 0.2 + PBKDF2 (Password Based Key Derivation Function 2) is typically used for + deriving a cryptographic key from a password. It may also be used for + key storage, but other key storage KDFs such as `scrypt`_ or `bcrypt`_ + are generally considered better solutions since they are designed to be + slow. + This class conforms to the :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` interface. @@ -52,10 +60,47 @@ using a pseudo-random function (PRF). longer. :param int iterations: The number of iterations to perform of the hash function. See OWASP's `Password Storage Cheat Sheet`_ for more - detailed recommendations. + detailed recommendations if you intend to use this for password storage. :param backend: A :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` provider. + .. method:: derive(key_material) + + :param key_material bytes: The input key material. For PBKDF2 this + should be a password. + :return: The new key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This generates and returns a new key from the supplied password. + + .. method:: verify(key_material, expected_key) + + :param key_material bytes: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param expected_key bytes: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. This can be used for + checking whether a user's password attempt matches the stored derived + key. + .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf .. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet +.. _`bcrypt`: http://en.wikipedia.org/wiki/Bcrypt +.. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt -- cgit v1.2.3 From 560ef47949077bbe89f108e1f3349cba4eacbbf0 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 17:44:10 -0600 Subject: add PBKDF2HMAC to changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 545d6945..cc0aacd3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,7 @@ Changelog :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`. * Added support for the OpenSSL backend under Windows. * Improved thread-safety for the OpenSSL backend. +* Added PBKDF2HMAC support to OpenSSL backend. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From aad090fe4273ee80b28f4dc6d1b56cce483c85f7 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 18:28:43 -0600 Subject: simplify pbkdf2_hmac_supported in openssl backend --- cryptography/hazmat/backends/openssl/backend.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index dbdb2e56..cf931dab 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -136,9 +136,7 @@ class Backend(object): def pbkdf2_hmac_supported(self, algorithm): if self._lib.Cryptography_HAS_PBKDF2_HMAC: - digest = self._lib.EVP_get_digestbyname( - algorithm.name.encode("ascii")) - return digest != self._ffi.NULL + return self.hmac_supported(algorithm) else: # OpenSSL < 1.0.0 has an explicit PBKDF2-HMAC-SHA1 function, # so if the PBKDF2_HMAC function is missing we only support -- cgit v1.2.3 From 5c8ea70ca7a36a0e090640b329bd9931232b7b23 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 19:23:01 -0600 Subject: add some unicode checks for salt on init and key_material on derive --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 13 +++++++++++++ tests/hazmat/primitives/test_pbkdf2.py | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index a496cc27..fec1d5c2 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +import six + from cryptography import utils from cryptography.exceptions import ( InvalidKey, UnsupportedAlgorithm, AlreadyFinalized @@ -31,6 +33,11 @@ class PBKDF2HMAC(object): self._called = False self.algorithm = algorithm self._length = length + if isinstance(salt, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) self._salt = salt self.iterations = iterations self._backend = backend @@ -40,6 +47,12 @@ class PBKDF2HMAC(object): raise AlreadyFinalized("PBKDF2 instances can only be called once") else: self._called = True + + if isinstance(key_material, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) return self._backend.derive_pbkdf2_hmac( self.algorithm, self._length, diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py index 41123557..6ad225a8 100644 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -14,6 +14,7 @@ from __future__ import absolute_import, division, print_function import pytest +import six from cryptography import utils from cryptography.exceptions import ( @@ -57,3 +58,12 @@ class TestPBKDF2HMAC(object): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) with pytest.raises(InvalidKey): kdf.verify(b"password2", key) + + def test_unicode_error_with_salt(self): + with pytest.raises(TypeError): + PBKDF2HMAC(hashes.SHA1(), 20, six.u("salt"), 10, default_backend()) + + def test_unicode_error_with_key_material(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(TypeError): + kdf.derive(six.u("unicode here")) -- cgit v1.2.3 From 2e8617bea70e1b29b4138f591c2264041f5b1099 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 19:27:06 -0600 Subject: rename test files --- tests/hazmat/primitives/test_pbkdf2.py | 69 ---------------------- tests/hazmat/primitives/test_pbkdf2_vectors.py | 37 ------------ tests/hazmat/primitives/test_pbkdf2hmac.py | 69 ++++++++++++++++++++++ tests/hazmat/primitives/test_pbkdf2hmac_vectors.py | 37 ++++++++++++ 4 files changed, 106 insertions(+), 106 deletions(-) delete mode 100644 tests/hazmat/primitives/test_pbkdf2.py delete mode 100644 tests/hazmat/primitives/test_pbkdf2_vectors.py create mode 100644 tests/hazmat/primitives/test_pbkdf2hmac.py create mode 100644 tests/hazmat/primitives/test_pbkdf2hmac_vectors.py diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py deleted file mode 100644 index 6ad225a8..00000000 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ /dev/null @@ -1,69 +0,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, division, print_function - -import pytest -import six - -from cryptography import utils -from cryptography.exceptions import ( - InvalidKey, UnsupportedAlgorithm, AlreadyFinalized -) -from cryptography.hazmat.primitives import hashes, interfaces -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from cryptography.hazmat.backends import default_backend - - -@utils.register_interface(interfaces.HashAlgorithm) -class DummyHash(object): - name = "dummy-hash" - - -class TestPBKDF2HMAC(object): - def test_already_finalized(self): - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - kdf.derive(b"password") - with pytest.raises(AlreadyFinalized): - kdf.derive(b"password2") - - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - key = kdf.derive(b"password") - with pytest.raises(AlreadyFinalized): - kdf.verify(b"password", key) - - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - kdf.verify(b"password", key) - with pytest.raises(AlreadyFinalized): - kdf.verify(b"password", key) - - def test_unsupported_algorithm(self): - with pytest.raises(UnsupportedAlgorithm): - PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) - - def test_invalid_key(self): - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - key = kdf.derive(b"password") - - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - with pytest.raises(InvalidKey): - kdf.verify(b"password2", key) - - def test_unicode_error_with_salt(self): - with pytest.raises(TypeError): - PBKDF2HMAC(hashes.SHA1(), 20, six.u("salt"), 10, default_backend()) - - def test_unicode_error_with_key_material(self): - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - with pytest.raises(TypeError): - kdf.derive(six.u("unicode here")) diff --git a/tests/hazmat/primitives/test_pbkdf2_vectors.py b/tests/hazmat/primitives/test_pbkdf2_vectors.py deleted file mode 100644 index cbd4cc9d..00000000 --- a/tests/hazmat/primitives/test_pbkdf2_vectors.py +++ /dev/null @@ -1,37 +0,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, division, print_function - -import pytest - -from cryptography.hazmat.primitives import hashes - -from .utils import generate_pbkdf2_test -from ...utils import load_nist_vectors - - -@pytest.mark.supported( - only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), - skip_message="Does not support SHA1 for PBKDF2HMAC", -) -@pytest.mark.pbkdf2hmac -class TestPBKDF2HMAC_SHA1(object): - test_pbkdf2_sha1 = generate_pbkdf2_test( - load_nist_vectors, - "KDF", - [ - "rfc-6070-PBKDF2-SHA1.txt", - ], - hashes.SHA1(), - ) diff --git a/tests/hazmat/primitives/test_pbkdf2hmac.py b/tests/hazmat/primitives/test_pbkdf2hmac.py new file mode 100644 index 00000000..6ad225a8 --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2hmac.py @@ -0,0 +1,69 @@ +# 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, division, print_function + +import pytest +import six + +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, AlreadyFinalized +) +from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.backends import default_backend + + +@utils.register_interface(interfaces.HashAlgorithm) +class DummyHash(object): + name = "dummy-hash" + + +class TestPBKDF2HMAC(object): + def test_already_finalized(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.derive(b"password2") + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.verify(b"password", key) + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + def test_unsupported_algorithm(self): + with pytest.raises(UnsupportedAlgorithm): + PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) + + def test_invalid_key(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(InvalidKey): + kdf.verify(b"password2", key) + + def test_unicode_error_with_salt(self): + with pytest.raises(TypeError): + PBKDF2HMAC(hashes.SHA1(), 20, six.u("salt"), 10, default_backend()) + + def test_unicode_error_with_key_material(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(TypeError): + kdf.derive(six.u("unicode here")) diff --git a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py new file mode 100644 index 00000000..cbd4cc9d --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py @@ -0,0 +1,37 @@ +# 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, division, print_function + +import pytest + +from cryptography.hazmat.primitives import hashes + +from .utils import generate_pbkdf2_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), + skip_message="Does not support SHA1 for PBKDF2HMAC", +) +@pytest.mark.pbkdf2hmac +class TestPBKDF2HMAC_SHA1(object): + test_pbkdf2_sha1 = generate_pbkdf2_test( + load_nist_vectors, + "KDF", + [ + "rfc-6070-PBKDF2-SHA1.txt", + ], + hashes.SHA1(), + ) -- cgit v1.2.3 From 99d5190656184ad791e50eab72c631e7f829e283 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 20:16:20 -0600 Subject: some alternate language --- docs/hazmat/primitives/key-derivation-functions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index e16a9900..c77b763a 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -97,7 +97,7 @@ key stretching) so match your needs to their capabilities. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. This can be used for - checking whether a user's password attempt matches the stored derived + checking whether the password a user provides matches the stored derived key. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf -- cgit v1.2.3 From 589b90826db025bcd7fc02e29b4831a09df3269d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 21:25:41 -0600 Subject: doc updates based on review --- docs/hazmat/backends/interfaces.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index e22c6bb3..ca3a5433 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -133,7 +133,6 @@ A specific ``backend`` may provide one or more of these interfaces. :class:`~cryptography.hazmat.primitives.interfaces.HashContext` - .. class:: PBKDF2HMACBackend .. versionadded:: 0.2 @@ -144,7 +143,7 @@ A specific ``backend`` may provide one or more of these interfaces. Check if the specified ``algorithm`` is supported by this backend. - :param prf: An instance of a + :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. -- cgit v1.2.3 From d6b02d410370c439e662e8a2f5741a2d092bab8e Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 21:25:53 -0600 Subject: review fixes --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index fec1d5c2..2c8dd541 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -30,7 +30,7 @@ class PBKDF2HMAC(object): "{0} is not supported for PBKDF2 by this backend".format( algorithm.name) ) - self._called = False + self._used = False self.algorithm = algorithm self._length = length if isinstance(salt, six.text_type): @@ -43,10 +43,9 @@ class PBKDF2HMAC(object): self._backend = backend def derive(self, key_material): - if self._called: + if self._used: raise AlreadyFinalized("PBKDF2 instances can only be called once") - else: - self._called = True + self._used = True if isinstance(key_material, six.text_type): raise TypeError( -- cgit v1.2.3 From 298e533e01053a5fc1ba00ba640a3daf128d1151 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 11:16:22 -0600 Subject: update docs for pbkdf2 --- docs/hazmat/primitives/key-derivation-functions.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index c77b763a..e652ecbf 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -14,11 +14,10 @@ key stretching) so match your needs to their capabilities. .. versionadded:: 0.2 - PBKDF2 (Password Based Key Derivation Function 2) is typically used for + `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for deriving a cryptographic key from a password. It may also be used for - key storage, but other key storage KDFs such as `scrypt`_ or `bcrypt`_ - are generally considered better solutions since they are designed to be - slow. + key storage, but an alternate key storage KDF such as `scrypt` is generally + considered a better solution since it is designed to be slow. This class conforms to the :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` @@ -102,5 +101,5 @@ key stretching) so match your needs to their capabilities. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf .. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet -.. _`bcrypt`: http://en.wikipedia.org/wiki/Bcrypt +.. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 .. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt -- cgit v1.2.3 From 831c04b669a8a2723c92407c029909850365db09 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 12:54:53 -0600 Subject: called -> used --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 2c8dd541..c08e7d9a 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -44,7 +44,7 @@ class PBKDF2HMAC(object): def derive(self, key_material): if self._used: - raise AlreadyFinalized("PBKDF2 instances can only be called once") + raise AlreadyFinalized("PBKDF2 instances can only be used once") self._used = True if isinstance(key_material, six.text_type): -- cgit v1.2.3 From 6fb1a5a99d3742763961d907c9f297f89f2f0b91 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 13:44:07 -0600 Subject: add test for null char replacement --- tests/test_utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index f852f3ab..8ecb33f9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -180,6 +180,25 @@ def test_load_nist_vectors(): ] +def test_load_nist_vectors_with_null_chars(): + vector_data = textwrap.dedent(""" + COUNT = 0 + KEY = thing\\0withnulls + + COUNT = 1 + KEY = 00000000000000000000000000000000 + """).splitlines() + + assert load_nist_vectors(vector_data) == [ + { + "key": b"thing\x00withnulls", + }, + { + "key": b"00000000000000000000000000000000", + }, + ] + + def test_load_cryptrec_vectors(): vector_data = textwrap.dedent(""" # Vectors taken from http://info.isl.ntt.co.jp/crypt/eng/camellia/ -- cgit v1.2.3 From c58b478530a93df90d0c612df259d1668cdd3f6b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 13:56:25 -0600 Subject: update docs re: PBKDF2HMAC iterations --- docs/hazmat/backends/interfaces.rst | 4 +++- docs/hazmat/primitives/key-derivation-functions.rst | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index ca3a5433..5b3e852a 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -163,7 +163,9 @@ A specific ``backend`` may provide one or more of these interfaces. :param bytes salt: A salt. :param int iterations: The number of iterations to perform of the hash - function. + function. This can be used to control the length of time the + operation takes. Higher numbers help mitigate brute force attacks + against derived keys. :param bytes key_material: The key material to use as a basis for the derived key. This is typically a password. diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index e652ecbf..bf069faa 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -58,7 +58,9 @@ key stretching) so match your needs to their capabilities. :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or longer. :param int iterations: The number of iterations to perform of the hash - function. See OWASP's `Password Storage Cheat Sheet`_ for more + function. This can be used to control the length of time the operation + takes. Higher numbers help mitigate brute force attacks against derived + keys. See OWASP's `Password Storage Cheat Sheet`_ for more detailed recommendations if you intend to use this for password storage. :param backend: A :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` -- cgit v1.2.3 From 1cab104d7c95aae20bd6068c5cb54f4dce149d91 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 14:30:11 -0600 Subject: expand docs to talk more about the purposes of KDFs --- .../hazmat/primitives/key-derivation-functions.rst | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index bf069faa..56c3a2bd 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -6,9 +6,24 @@ Key Derivation Functions .. currentmodule:: cryptography.hazmat.primitives.kdf Key derivation functions derive key material from passwords or other data -sources using a pseudo-random function (PRF). Each KDF is suitable for -different tasks (cryptographic key derivation, password storage, -key stretching) so match your needs to their capabilities. +sources using a pseudo-random function (PRF). Different KDFs are suitable for +different tasks such as: + +- Cryptographic key derivation + + Deriving a key suitable for use as input to an encryption algorithm. + Typically this means taking a password and running it through an algorithm + such as :class:`~cryptography.hazmat.primitives.kdf.PBKDF2HMAC` or HKDF. + This process is typically known as `key stretching`_. + +- Password storage + + When storing passwords you want to use an algorithm that is computationally + intensive. Legitimate users will only need to compute it once (for example, + taking the user's password, running it through the KDF, then comparing it + to the stored value), while attackers will need to do it billions of times. + Ideal password storage KDFs will be demanding on both computational and + memory resources. .. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): @@ -17,7 +32,7 @@ key stretching) so match your needs to their capabilities. `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for deriving a cryptographic key from a password. It may also be used for key storage, but an alternate key storage KDF such as `scrypt` is generally - considered a better solution since it is designed to be slow. + considered a better solution. This class conforms to the :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` @@ -105,3 +120,4 @@ key stretching) so match your needs to their capabilities. .. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet .. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 .. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt +.. _`key stretching`: http://en.wikipedia.org/wiki/Key_stretching -- cgit v1.2.3 From fb042ad11a98f3a8eb7103d052e4d703687c8739 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 14:37:09 -0600 Subject: switch to private attributes in pbkdf2hmac --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index c08e7d9a..71b88211 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -31,7 +31,7 @@ class PBKDF2HMAC(object): algorithm.name) ) self._used = False - self.algorithm = algorithm + self._algorithm = algorithm self._length = length if isinstance(salt, six.text_type): raise TypeError( @@ -39,7 +39,7 @@ class PBKDF2HMAC(object): "material." ) self._salt = salt - self.iterations = iterations + self._iterations = iterations self._backend = backend def derive(self, key_material): @@ -53,10 +53,10 @@ class PBKDF2HMAC(object): "material." ) return self._backend.derive_pbkdf2_hmac( - self.algorithm, + self._algorithm, self._length, self._salt, - self.iterations, + self._iterations, key_material ) -- cgit v1.2.3 From 0b181182aef574c436a92a175937af32e54a2378 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 16:34:47 -0600 Subject: a bit more language work + changelog changes for pbkdf2hmac --- docs/changelog.rst | 2 +- docs/hazmat/primitives/key-derivation-functions.rst | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index be42b5db..f401fe7c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,7 +14,7 @@ Changelog * Improved thread-safety for the OpenSSL backend. * Fixed compilation on systems where OpenSSL's ``ec.h`` header is not available, such as CentOS. -* Added PBKDF2HMAC support to OpenSSL backend. +* Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 56c3a2bd..529f4416 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -5,18 +5,18 @@ Key Derivation Functions .. currentmodule:: cryptography.hazmat.primitives.kdf -Key derivation functions derive key material from passwords or other data -sources using a pseudo-random function (PRF). Different KDFs are suitable for -different tasks such as: +Key derivation functions derive bytes suitable for cryptographic operations +from passwords or other data sources using a pseudo-random function (PRF). +Different KDFs are suitable for different tasks such as: -- Cryptographic key derivation +* Cryptographic key derivation Deriving a key suitable for use as input to an encryption algorithm. Typically this means taking a password and running it through an algorithm - such as :class:`~cryptography.hazmat.primitives.kdf.PBKDF2HMAC` or HKDF. + such as :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC` or HKDF. This process is typically known as `key stretching`_. -- Password storage +* Password storage When storing passwords you want to use an algorithm that is computationally intensive. Legitimate users will only need to compute it once (for example, @@ -25,13 +25,15 @@ different tasks such as: Ideal password storage KDFs will be demanding on both computational and memory resources. -.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): +.. currentmodule:: cryptography.hazmat.primitives.kdf.pbkdf2 + +.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend) .. versionadded:: 0.2 `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for deriving a cryptographic key from a password. It may also be used for - key storage, but an alternate key storage KDF such as `scrypt` is generally + key storage, but an alternate key storage KDF such as `scrypt`_ is generally considered a better solution. This class conforms to the @@ -85,7 +87,7 @@ different tasks such as: :param key_material bytes: The input key material. For PBKDF2 this should be a password. - :return: The new key. + :return bytes: the derived key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is -- cgit v1.2.3