From 06288ab5939b2fd904e5f5ab07dac7879ddd2569 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sat, 27 Aug 2016 08:55:31 +0800 Subject: [PATCH 01/11] Scrypt implementation. --- .../primitives/key-derivation-functions.rst | 86 +++++++++++++++++++ .../hazmat/backends/interfaces.py | 9 ++ .../hazmat/backends/multibackend.py | 8 +- .../hazmat/backends/openssl/backend.py | 14 ++- .../hazmat/primitives/kdf/scrypt.py | 44 ++++++++++ tests/hazmat/primitives/test_scrypt.py | 67 +++++++++++++++ 6 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 src/cryptography/hazmat/primitives/kdf/scrypt.py create mode 100644 tests/hazmat/primitives/test_scrypt.py diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 558c9d169c2e..1ebd051e39e4 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -737,6 +737,90 @@ Different KDFs are suitable for different tasks such as: The counter iteration variable will be concatenated after the fixed input data. +.. currentmodule:: cryptography.hazmat.primitives.kdf.scrypt + +.. class:: Scrypt(salt, length, n, r, p, backend) + + .. versionadded:: 1.6 + + Scrypt is a KDF designed by Colin Percival to be resistant against + hardware-assisted attackers by having a tunable memory cost. It is + described in `RFC 7914`_. + + This class conforms to the + :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` + interface. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.scrypt import Scrypt + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> # derive + >>> kdf = Scrypt( + ... salt=salt, + ... length=64, + ... n=1024, + ... r=8, + ... p=16, + ... backend=backend + ... ) + >>> key = kdf.derive(b"my great password") + >>> # verify + >>> kdf = Scrypt( + ... salt=salt, + ... length=64, + ... n=1024, + ... r=8, + ... p=16, + ... backend=backend + ... ) + >>> kdf.verify(b"my great password", key) + + :param bytes salt: A salt. + :param int length: The desired length of the derived key. + :param int n: CPU/Memory cost parameter. It must be larger than 1 and be a + power of 2. + :param int r: Block size parameter. + :param int p: Parallelization parameter. + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend` + + :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: the derived key. + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + + This generates and returns a new key from the supplied password. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: 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. + + 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 the password a user provides matches the stored derived + key. + Interface ~~~~~~~~~ @@ -796,3 +880,5 @@ Interface .. _`HKDF`: .. _`RFC 5869`: https://tools.ietf.org/html/rfc5869 .. _`HKDF paper`: https://eprint.iacr.org/2010/264 +.. _`RFC 7914`: https://tools.ietf.org/html/rfc7914 + diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 5b9e6f381a7a..9a1d704a37a6 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -357,3 +357,12 @@ def dh_parameters_supported(self, p, g): """ Returns whether the backend supports DH with these parameter values. """ + + +@six.add_metaclass(abc.ABCMeta) +class ScryptBackend(object): + @abc.abstractmethod + def derive_scrypt(self, key_material, salt, length, n, r, p): + """ + Return bytes derived from provided Scrypt parameters. + """ diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 48bc7d087cce..deca020ed922 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -9,7 +9,7 @@ from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, X509Backend + PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) @@ -24,6 +24,7 @@ @utils.register_interface(EllipticCurveBackend) @utils.register_interface(PEMSerializationBackend) @utils.register_interface(X509Backend) +@utils.register_interface(ScryptBackend) class MultiBackend(object): name = "multibackend" @@ -409,3 +410,8 @@ def create_x509_revoked_certificate(self, builder): "This backend does not support X.509.", _Reasons.UNSUPPORTED_X509 ) + + def derive_scrypt(self, key_material, salt, length, n, r, p): + for b in self._filtered_backends(ScryptBackend): + return b.derive_scrypt(key_material, salt, length, n, r, p) + raise UnsupportedAlgorithm("This backend does not support scrypt.") diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 7d16e05e7684..955b19770580 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -8,6 +8,7 @@ import calendar import collections import itertools +import sys from contextlib import contextmanager import six @@ -17,7 +18,7 @@ from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, X509Backend + PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) from cryptography.hazmat.backends.openssl.ciphers import ( _AESCTRCipherContext, _CipherContext @@ -114,6 +115,9 @@ def _pem_password_cb(buf, size, writing, userdata_handle): @utils.register_interface(RSABackend) @utils.register_interface(PEMSerializationBackend) @utils.register_interface(X509Backend) +@utils.register_interface_if( + binding.Binding().lib.Cryptography_HAS_SCRYPT, ScryptBackend +) class Backend(object): """ OpenSSL API binding interfaces. @@ -1691,6 +1695,14 @@ def _openssh_public_key_bytes(self, key): serialization._ssh_write_string(public_numbers.encode_point()) ) + def derive_scrypt(self, key_material, salt, length, n, r, p): + buf = self._ffi.new("unsigned char[]", length) + res = self._lib.EVP_PBE_scrypt(key_material, len(key_material), salt, + len(salt), n, r, p, sys.maxsize // 2, + buf, length) + self.openssl_assert(res == 1) + return self._ffi.buffer(buf)[:] + class GetCipherByName(object): def __init__(self, fmt): diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py new file mode 100644 index 000000000000..788cbdb89727 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -0,0 +1,44 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +@utils.register_interface(KeyDerivationFunction) +class Scrypt(object): + def __init__(self, salt, length, n, r, p, backend): + if not isinstance(backend, ScryptBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement ScryptBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + self._length = length + if not isinstance(salt, bytes): + raise TypeError("salt must be bytes.") + self._salt = salt + self._n = n + self._r = r + self._p = p + self._backend = backend + + def derive(self, key_material): + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + return self._backend.derive_scrypt( + key_material, self._salt, self._length, self._n, self._r, self._p + ) + + def verify(self, 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_scrypt.py b/tests/hazmat/primitives/test_scrypt.py new file mode 100644 index 000000000000..30fbc4dceaa7 --- /dev/null +++ b/tests/hazmat/primitives/test_scrypt.py @@ -0,0 +1,67 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import os + +import pytest + +from cryptography.exceptions import InvalidKey + +from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.primitives.kdf.scrypt import Scrypt + +from tests.utils import load_nist_vectors, load_vectors_from_file + +vectors = load_vectors_from_file( + os.path.join("KDF", "scrypt.txt"), load_nist_vectors) + + +@pytest.mark.requires_backend_interface(interface=ScryptBackend) +class TestScrypt(object): + @pytest.mark.parametrize("params", vectors) + def test_derive(self, backend, params): + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert binascii.hexlify(scrypt.derive(password)) == derived_key + + @pytest.mark.parametrize("params", vectors) + def test_verify(self, backend, params): + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert scrypt.verify(password, binascii.unhexlify(derived_key)) is None + + def test_invalid_verify(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + derived_key = b"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(InvalidKey): + scrypt.verify(password, binascii.unhexlify(derived_key)) From ba96e7e7aaf8d2298ab567ae61a99619a9e90623 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sat, 27 Aug 2016 15:15:30 +0800 Subject: [PATCH 02/11] Docs stuff. --- docs/hazmat/backends/interfaces.rst | 30 +++++++++++++++++++++++++++++ docs/hazmat/backends/openssl.rst | 5 +++++ docs/spelling_wordlist.txt | 2 ++ 3 files changed, 37 insertions(+) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 14f72cf6a8be..b79bb2399a63 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -652,3 +652,33 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: ``True`` if the given values of ``p`` and ``g`` are supported by this backend, otherwise ``False``. + + +.. class:: ScryptBackend + + .. versionadded:: 1.6 + + A backend with methods for using Scrypt. + + The following backends implement this interface: + + * :doc:`/hazmat/backends/openssl` + + .. method:: derive_scrypt(self, key_material, salt, length, n, r, p) + + :param bytes key_material: The key material to use as a basis for + the derived key. This is typically a password. + + :param bytes salt: A salt. + + :param int length: The desired length of the derived key. + + :param int n: CPU/Memory cost parameter. It must be larger than 1 and be a + power of 2. + + :param int r: Block size parameter. + + :param int p: Parallelization parameter. + + :return bytes: Derived key. + diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index 8bc7dac5490b..791aab3da2ef 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -24,6 +24,11 @@ greater. * :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend` * :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + It also implements the following interface for OpenSSL versions ``1.1.0`` + and above. + + * :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend` + It also exposes the following: .. attribute:: name diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index f60943a1cefd..38ba8c691699 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -82,3 +82,5 @@ Verifier Verisign wildcard Xcode +Parallelization +tunable. From d5d0c836e4562ee1677ca64db0886fdac073ecef Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sat, 27 Aug 2016 15:33:37 +0800 Subject: [PATCH 03/11] Make example just an example and not a doctest. --- 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 1ebd051e39e4..e03eef99b0f2 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -751,7 +751,7 @@ Different KDFs are suitable for different tasks such as: :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` interface. - .. doctest:: + .. code-block:: python >>> import os >>> from cryptography.hazmat.primitives import hashes From 4e8594613686b04cee62f062952958e9ccf98a67 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sat, 27 Aug 2016 15:38:42 +0800 Subject: [PATCH 04/11] Add changelog entry. --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 18542035b62b..9f9c63365839 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ Changelog * Added support for :class:`~cryptography.hazmat.primitives.hashes.BLAKE2b` and :class:`~cryptography.hazmat.primitives.hashes.BLAKE2s` when using OpenSSL 1.1.0. +* Added support for :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt` + when using OpenSSL 1.1.0. 1.5 - 2016-08-26 ~~~~~~~~~~~~~~~~ From 70a7ed015598279dd4c50993a17fe657c99231b0 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sat, 27 Aug 2016 21:16:56 +0800 Subject: [PATCH 05/11] Docs cleanup. --- docs/hazmat/primitives/key-derivation-functions.rst | 6 +++--- docs/spelling_wordlist.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index e03eef99b0f2..e415c0936118 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -743,9 +743,9 @@ Different KDFs are suitable for different tasks such as: .. versionadded:: 1.6 - Scrypt is a KDF designed by Colin Percival to be resistant against - hardware-assisted attackers by having a tunable memory cost. It is - described in `RFC 7914`_. + Scrypt is a KDF designed for password storage by Colin Percival to be + resistant against hardware-assisted attackers by having a tunable memory + cost. It is described in :rfc:`7914`. This class conforms to the :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 38ba8c691699..31bc995fe144 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -55,6 +55,7 @@ Nonces nonces online paddings +Parallelization pickleable plaintext pre @@ -73,6 +74,7 @@ SHA Solaris Tanja testability +tunable Ubuntu unencrypted unpadded @@ -82,5 +84,3 @@ Verifier Verisign wildcard Xcode -Parallelization -tunable. From ca40cf9a57dea1ecae754e5ae471ecccf52e4f09 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sat, 27 Aug 2016 23:55:50 +0800 Subject: [PATCH 06/11] Add more tests. --- tests/hazmat/primitives/test_scrypt.py | 40 +++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py index 30fbc4dceaa7..9bf451cbd7e9 100644 --- a/tests/hazmat/primitives/test_scrypt.py +++ b/tests/hazmat/primitives/test_scrypt.py @@ -10,7 +10,7 @@ import pytest -from cryptography.exceptions import InvalidKey +from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm from cryptography.hazmat.backends.interfaces import ScryptBackend from cryptography.hazmat.primitives.kdf.scrypt import Scrypt @@ -37,6 +37,44 @@ def test_derive(self, backend, params): parallelization_factor, backend) assert binascii.hexlify(scrypt.derive(password)) == derived_key + def test_unsupported_backend(self): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + backend = object() + + with pytest.raises(UnsupportedAlgorithm): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_salt_not_bytes(self, backend): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = 1 + + with pytest.raises(TypeError): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_password_not_bytes(self, backend): + password = 1 + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + derived_key = b"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(TypeError): + scrypt.derive(password) + @pytest.mark.parametrize("params", vectors) def test_verify(self, backend, params): password = params["password"] From 51334418b393210cb0f787c518770c8f9ab8a74e Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sun, 28 Aug 2016 00:34:42 +0800 Subject: [PATCH 07/11] Add multibackend tests. --- tests/hazmat/backends/test_multibackend.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index bf54d5cecb12..7dead4244e7b 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -13,10 +13,10 @@ from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, X509Backend + PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) from cryptography.hazmat.backends.multibackend import MultiBackend -from cryptography.hazmat.primitives import cmac, hashes, hmac +from cryptography.hazmat.primitives import cmac, hashes, hmac, kdf from cryptography.hazmat.primitives.asymmetric import ec, padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes @@ -231,6 +231,12 @@ def create_x509_revoked_certificate(self, builder): pass +@utils.register_interface(ScryptBackend) +class DummyScryptBackend(object): + def derive_scrypt(self, key_material, salt, length, n, r, p): + pass + + class TestMultiBackend(object): def test_raises_error_with_empty_list(self): with pytest.raises(ValueError): @@ -558,3 +564,11 @@ def test_x509_backend(self): ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): backend.create_x509_revoked_certificate(object()) + + def test_scrypt(self): + backend = MultiBackend([DummyScryptBackend()]) + backend.derive_scrypt(b"key", b"salt", 1, 1, 1, 1) + + backend = MultiBackend([DummyBackend]) + with pytest.raises(UnsupportedAlgorithm): + backend.derive_scrypt(b"key", b"salt", 1, 1, 1, 1) From 79ffdac721b99cf103188f7019ff8749a7594a54 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sun, 28 Aug 2016 00:39:04 +0800 Subject: [PATCH 08/11] PEP8. --- tests/hazmat/backends/test_multibackend.py | 2 +- tests/hazmat/primitives/test_scrypt.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 7dead4244e7b..1cd873367b66 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -16,7 +16,7 @@ PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) from cryptography.hazmat.backends.multibackend import MultiBackend -from cryptography.hazmat.primitives import cmac, hashes, hmac, kdf +from cryptography.hazmat.primitives import cmac, hashes, hmac from cryptography.hazmat.primitives.asymmetric import ec, padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py index 9bf451cbd7e9..f12bf7b21c69 100644 --- a/tests/hazmat/primitives/test_scrypt.py +++ b/tests/hazmat/primitives/test_scrypt.py @@ -67,7 +67,6 @@ def test_password_not_bytes(self, backend): parallelization_factor = 16 length = 64 salt = b"NaCl" - derived_key = b"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773" scrypt = Scrypt(salt, length, work_factor, block_size, parallelization_factor, backend) From 271c4fa8d7b0cd40b65d60f797abb2b631739eed Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Sun, 28 Aug 2016 05:39:58 +0800 Subject: [PATCH 09/11] Add docs about Scrypt parameters. --- .../hazmat/primitives/key-derivation-functions.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index e415c0936118..9db134849bfd 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -786,6 +786,17 @@ Different KDFs are suitable for different tasks such as: power of 2. :param int r: Block size parameter. :param int p: Parallelization parameter. + + The computational and memory cost of Scrypt can be adjusted by manipulating + the 3 parameters: n, r and p. In general, the memory cost of Scrypt is + affected by the values of both n and r while n also determines the number + of iterations performed. p increases the computational cost without + affecting memory usage. A more in-depth explanation of the 3 parameters can + be found `here`_. + + :rfc:`7914` `recommends`_ values of r=8 and p=1 while scaling n to the + number appropriate for your system. + :param backend: An instance of :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend`. @@ -881,4 +892,5 @@ Interface .. _`RFC 5869`: https://tools.ietf.org/html/rfc5869 .. _`HKDF paper`: https://eprint.iacr.org/2010/264 .. _`RFC 7914`: https://tools.ietf.org/html/rfc7914 - +.. _`here`: http://stackoverflow.com/a/30308723/1170681 +.. _`recommends`: https://tools.ietf.org/html/rfc7914#section-2 From 9e19cd8d4f09438b2833d22b2be1a77976668809 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Thu, 1 Sep 2016 19:44:35 +0800 Subject: [PATCH 10/11] Docs cleanup. --- docs/hazmat/primitives/key-derivation-functions.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index d9431fc6e24d..88cb57bfeb45 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -890,6 +890,5 @@ Interface .. _`key stretching`: https://en.wikipedia.org/wiki/Key_stretching .. _`HKDF`: https://en.wikipedia.org/wiki/HKDF .. _`HKDF paper`: https://eprint.iacr.org/2010/264 -.. _`RFC 7914`: https://tools.ietf.org/html/rfc7914 -.. _`here`: http://stackoverflow.com/a/30308723/1170681 +.. _`here`: https://stackoverflow.com/a/30308723/1170681 .. _`recommends`: https://tools.ietf.org/html/rfc7914#section-2 From 39c46f2b30669ec88e64317bae5957e84b2ffee7 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Thu, 1 Sep 2016 20:03:30 +0800 Subject: [PATCH 11/11] Add AlreadyFinalized. --- .../primitives/key-derivation-functions.rst | 10 ++++++++++ .../hazmat/primitives/kdf/scrypt.py | 7 ++++++- tests/hazmat/primitives/test_scrypt.py | 19 +++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 88cb57bfeb45..03260c06b675 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -812,6 +812,11 @@ Different KDFs are suitable for different tasks such as: :return bytes: the derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. + :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. @@ -825,6 +830,11 @@ Different KDFs are suitable for different tasks such as: :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 diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py index 788cbdb89727..09181d975d4e 100644 --- a/src/cryptography/hazmat/primitives/kdf/scrypt.py +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -6,7 +6,7 @@ from cryptography import utils from cryptography.exceptions import ( - InvalidKey, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons ) from cryptography.hazmat.backends.interfaces import ScryptBackend from cryptography.hazmat.primitives import constant_time @@ -25,6 +25,7 @@ def __init__(self, salt, length, n, r, p, backend): self._length = length if not isinstance(salt, bytes): raise TypeError("salt must be bytes.") + self._used = False self._salt = salt self._n = n self._r = r @@ -32,6 +33,10 @@ def __init__(self, salt, length, n, r, p, backend): self._backend = backend def derive(self, key_material): + if self._used: + raise AlreadyFinalized("Scrypt instances can only be used once.") + self._used = True + if not isinstance(key_material, bytes): raise TypeError("key_material must be bytes.") return self._backend.derive_scrypt( diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py index f12bf7b21c69..de4100e3f6ce 100644 --- a/tests/hazmat/primitives/test_scrypt.py +++ b/tests/hazmat/primitives/test_scrypt.py @@ -10,8 +10,9 @@ import pytest -from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm - +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm +) from cryptography.hazmat.backends.interfaces import ScryptBackend from cryptography.hazmat.primitives.kdf.scrypt import Scrypt @@ -102,3 +103,17 @@ def test_invalid_verify(self, backend): with pytest.raises(InvalidKey): scrypt.verify(password, binascii.unhexlify(derived_key)) + + def test_already_finalized(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + scrypt.derive(password) + with pytest.raises(AlreadyFinalized): + scrypt.derive(password)