| # 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 ( |
| AlreadyFinalized, InvalidKey, UnsupportedAlgorithm |
| ) |
| from cryptography.hazmat.backends.interfaces import ScryptBackend |
| from cryptography.hazmat.primitives.kdf.scrypt import Scrypt, _MEM_LIMIT |
| |
| 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) |
| |
| |
| def _skip_if_memory_limited(memory_limit, params): |
| # Memory calc adapted from OpenSSL (URL split over 2 lines, thanks PEP8) |
| # https://github.com/openssl/openssl/blob/6286757141a8c6e14d647ec733634a |
| # e0c83d9887/crypto/evp/scrypt.c#L189-L221 |
| blen = int(params["p"]) * 128 * int(params["r"]) |
| vlen = 32 * int(params["r"]) * (int(params["n"]) + 2) * 4 |
| memory_required = blen + vlen |
| if memory_limit < memory_required: |
| pytest.skip("Test exceeds Scrypt memory limit. " |
| "This is likely a 32-bit platform.") |
| |
| |
| def test_memory_limit_skip(): |
| with pytest.raises(pytest.skip.Exception): |
| _skip_if_memory_limited(1000, {"p": 16, "r": 64, "n": 1024}) |
| |
| _skip_if_memory_limited(2 ** 31, {"p": 16, "r": 64, "n": 1024}) |
| |
| |
| @pytest.mark.requires_backend_interface(interface=ScryptBackend) |
| class TestScrypt(object): |
| @pytest.mark.parametrize("params", vectors) |
| def test_derive(self, backend, params): |
| _skip_if_memory_limited(_MEM_LIMIT, 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 |
| |
| 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_scrypt_malloc_failure(self, backend): |
| password = b"NaCl" |
| work_factor = 1024 ** 3 |
| block_size = 589824 |
| parallelization_factor = 16 |
| length = 64 |
| salt = b"NaCl" |
| |
| scrypt = Scrypt(salt, length, work_factor, block_size, |
| parallelization_factor, backend) |
| |
| with pytest.raises(MemoryError): |
| scrypt.derive(password) |
| |
| def test_password_not_bytes(self, backend): |
| password = 1 |
| 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) |
| |
| with pytest.raises(TypeError): |
| scrypt.derive(password) |
| |
| def test_buffer_protocol(self, backend): |
| password = bytearray(b"password") |
| work_factor = 256 |
| block_size = 8 |
| parallelization_factor = 16 |
| length = 10 |
| salt = b"NaCl" |
| |
| scrypt = Scrypt(salt, length, work_factor, block_size, |
| parallelization_factor, backend) |
| |
| assert scrypt.derive(password) == b'\xf4\x92\x86\xb2\x06\x0c\x848W\x87' |
| |
| @pytest.mark.parametrize("params", vectors) |
| def test_verify(self, backend, params): |
| _skip_if_memory_limited(_MEM_LIMIT, 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)) |
| |
| 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) |
| |
| def test_invalid_n(self, backend): |
| # n is less than 2 |
| with pytest.raises(ValueError): |
| Scrypt(b"NaCl", 64, 1, 8, 16, backend) |
| |
| # n is not a power of 2 |
| with pytest.raises(ValueError): |
| Scrypt(b"NaCl", 64, 3, 8, 16, backend) |
| |
| def test_invalid_r(self, backend): |
| with pytest.raises(ValueError): |
| Scrypt(b"NaCl", 64, 2, 0, 16, backend) |
| |
| def test_invalid_p(self, backend): |
| with pytest.raises(ValueError): |
| Scrypt(b"NaCl", 64, 2, 8, 0, backend) |