From ab33266b16d9a1cd3cf6abcf0a7b80e86f915d95 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 23 Dec 2013 15:01:28 -0800 Subject: hkdf --- cryptography/hkdf.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 cryptography/hkdf.py diff --git a/cryptography/hkdf.py b/cryptography/hkdf.py new file mode 100644 index 00000000..9665ce57 --- /dev/null +++ b/cryptography/hkdf.py @@ -0,0 +1,43 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import constant_time + +def hkdf_derive(input_key, key_length, salt=None, info=None, hash=None, backend=None): + if hash is None: + hash = hashes.SHA256() + + if backend is None: + backend = default_backend() + + if info is None: + info = b"" + + if salt is None: + salt = b"\x00" * (hash.digest_size // 8) + + h = hmac.HMAC(salt, hash, backend=backend) + h.update(input_key) + PRK = h.finalize() + + output = [b''] + counter = 1 + + while (hash.digest_size // 8) * len(output) < key_length: + h = hmac.HMAC(PRK, hash, backend=backend) + h.update(output[-1]) + h.update(info) + h.update(chr(counter)) + output.append(h.finalize()) + counter += 1 + + return b"".join(output)[:key_length] + + +def hkdf_verify(expected, input_key, key_length, salt=None, info=None, + hash=None, backend=None): + derived = hkdf_derive(input_key, key_length, salt=salt, info=info, + hash=hash, backend=backend) + + return constant_time.bytes_eq(expected, derived) + -- cgit v1.2.3 From 66c9cd928601725e27aa64255e56b3a7e481a08d Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 20 Jan 2014 16:05:53 -0800 Subject: Refactor HKDF support and provide vectors for tests. --- cryptography/hazmat/primitives/kdf/hkdf.py | 52 ++++++++++++++ cryptography/hkdf.py | 43 ----------- tests/hazmat/primitives/test_hkdf_vectors.py | 50 +++++++++++++ tests/hazmat/primitives/utils.py | 31 ++++++++ .../primitives/vectors/KDF/rfc-5869-HKDF.txt | 83 ++++++++++++++++++++++ tests/test_utils.py | 57 ++++++++++++++- tests/utils.py | 44 ++++++++++++ 7 files changed, 315 insertions(+), 45 deletions(-) create mode 100644 cryptography/hazmat/primitives/kdf/hkdf.py delete mode 100644 cryptography/hkdf.py create mode 100644 tests/hazmat/primitives/test_hkdf_vectors.py create mode 100644 tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py new file mode 100644 index 00000000..8d36c80b --- /dev/null +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -0,0 +1,52 @@ +# 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 cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import constant_time + +def hkdf_derive(input_key, key_length, salt, info, hash, backend): + if hash is None: + hash = hashes.SHA256() + + if info is None: + info = b"" + + if salt is None: + salt = b"\x00" * (hash.digest_size // 8) + + h = hmac.HMAC(salt, hash, backend=backend) + h.update(input_key) + PRK = h.finalize() + + output = [b''] + counter = 1 + + while (hash.digest_size // 8) * len(output) < key_length: + h = hmac.HMAC(PRK, hash, backend=backend) + h.update(output[-1]) + h.update(info) + h.update(chr(counter)) + output.append(h.finalize()) + counter += 1 + + return b"".join(output)[:key_length] + + +def hkdf_verify(expected, input_key, key_length, salt, info, hash, backend): + derived = hkdf_derive(input_key, key_length, salt=salt, info=info, + hash=hash, backend=backend) + + if not constant_time.bytes_eq(expected, derived): + raise ValueError("") + diff --git a/cryptography/hkdf.py b/cryptography/hkdf.py deleted file mode 100644 index 9665ce57..00000000 --- a/cryptography/hkdf.py +++ /dev/null @@ -1,43 +0,0 @@ -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hmac -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives import constant_time - -def hkdf_derive(input_key, key_length, salt=None, info=None, hash=None, backend=None): - if hash is None: - hash = hashes.SHA256() - - if backend is None: - backend = default_backend() - - if info is None: - info = b"" - - if salt is None: - salt = b"\x00" * (hash.digest_size // 8) - - h = hmac.HMAC(salt, hash, backend=backend) - h.update(input_key) - PRK = h.finalize() - - output = [b''] - counter = 1 - - while (hash.digest_size // 8) * len(output) < key_length: - h = hmac.HMAC(PRK, hash, backend=backend) - h.update(output[-1]) - h.update(info) - h.update(chr(counter)) - output.append(h.finalize()) - counter += 1 - - return b"".join(output)[:key_length] - - -def hkdf_verify(expected, input_key, key_length, salt=None, info=None, - hash=None, backend=None): - derived = hkdf_derive(input_key, key_length, salt=salt, info=info, - hash=hash, backend=backend) - - return constant_time.bytes_eq(expected, derived) - diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py new file mode 100644 index 00000000..2595c956 --- /dev/null +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -0,0 +1,50 @@ +# 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 os + +import pytest + +from cryptography.hazmat.primitives import hashes + +from .utils import generate_hkdf_test +from ...utils import load_hkdf_vectors + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA1()), + skip_message="Does not support SHA1." +) +@pytest.mark.hash +class TestHKDFSHA1(object): + test_HKDFSHA1 = generate_hkdf_test( + load_hkdf_vectors, + os.path.join("kdf"), + ["rfc-5869-HKDF-SHA1.txt"], + hashes.SHA1() + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA256()), + skip_message="Does not support SHA256." +) +@pytest.mark.hash +class TestHKDFSHA256(object): + test_HKDFSHA1 = generate_hkdf_test( + load_hkdf_vectors, + os.path.join("kdf"), + ["rfc-5869-HKDF-SHA256.txt"], + hashes.SHA256() + ) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 6b1d055d..e546fa79 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -6,6 +6,8 @@ import pytest from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.kdf.hkdf import hkdf_derive + from cryptography.exceptions import ( AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, ) @@ -297,3 +299,32 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): ) with pytest.raises(ValueError): cipher.encryptor() + + +def hkdf_test(backend, algorithm, params): + ikm = params[0] + salt = params[1] + info = params[2] + length = params[3] + expected_okm = params[4] + + okm = hkdf_derive( + binascii.unhexlify(ikm), + length, + binascii.unhexlify(salt), + binascii.unhexlify(info), + algorithm, + backend=backend + ) + + assert binascii.hexlify(okm) == expected_okm + + +def generate_hkdf_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_hkdf(self, backend, params): + hkdf_test(backend, algorithm, params) + + return test_hkdf diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt new file mode 100644 index 00000000..885a5266 --- /dev/null +++ b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt @@ -0,0 +1,83 @@ +# A.1. Test Case 1 +# Basic test case with SHA-256 + +Hash = SHA-256 +IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b +salt = 000102030405060708090a0b0c +info = f0f1f2f3f4f5f6f7f8f9 +L = 42 +PRK = 077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5 +OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865 + +# A.2. Test Case 2 +# Test with SHA-256 and longer inputs/outputs + +Hash = SHA-256 +IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f +salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf +info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff +L = 82 + +PRK = 06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244 +OKM = b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87 + +# A.3. Test Case 3 +# Test with SHA-256 and zero-length salt/info + +Hash = SHA-256 +IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b +salt = +info = +L = 42 + +PRK = 19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04 +OKM = 8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8 + +# A.4. Test Case 4 +# Basic test case with SHA-1 + +Hash = SHA-1 +IKM = 0b0b0b0b0b0b0b0b0b0b0b +salt = 000102030405060708090a0b0c +info = f0f1f2f3f4f5f6f7f8f9 +L = 42 + +PRK = 9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243 +OKM = 085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896 + +# A.5. Test Case 5 +# Test with SHA-1 and longer inputs/outputs + +Hash = SHA-1 +IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f +salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf +info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff +L = 82 + +PRK = 8adae09a2a307059478d309b26c4115a224cfaf6 +OKM = 0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4 + +# A.6. Test Case 6 +# Test with SHA-1 and zero-length salt/info + +Hash = SHA-1 +IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b +salt = +info = +L = 42 + +PRK = da8c8a73c7fa77288ec6f5e7c297786aa0d32d01 +OKM = 0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918 + +# A.7. Test Case 7 +# Test with SHA-1, salt not provided (defaults to HashLen zero octets), +# zero-length info + +Hash = SHA-1 +IKM = 0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c +salt = not provided (defaults to HashLen zero octets) +info = +L = 42 + +PRK = 2adccada18779e7c2077ad2eb19d3f3e731385dd +OKM = 2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc diff --git a/tests/test_utils.py b/tests/test_utils.py index 8ecb33f9..3bc4bf65 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -20,8 +20,8 @@ import pytest from .utils import ( load_nist_vectors, load_vectors_from_file, load_cryptrec_vectors, - load_openssl_vectors, load_hash_vectors, check_for_iface, - check_backend_support, select_backends + load_openssl_vectors, load_hash_vectors, load_hkdf_vectors, + check_for_iface, check_backend_support, select_backends ) @@ -529,3 +529,56 @@ def test_load_nist_gcm_vectors(): 'ct': b'15c4db4cbb451211179d57017f', 'fail': True}, ] + + +def test_load_hkdf_vectors(): + vector_data = textwrap.dedent(""" + # A.1. Test Case 1 + # Basic test case with SHA-256 + + Hash = SHA-256 + IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b + salt = 000102030405060708090a0b0c + info = f0f1f2f3f4f5f6f7f8f9 + L = 42 + """ + "PRK = 077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2" + "b3e5\n" + "OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4" + "c5bf34007208d5b887185865\n" + """ + # A.2. Test Case 2 + # Test with SHA-256 and longer inputs/outputs + + Hash = SHA-256 + """ + "IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d" + "1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3" + "f404142434445464748494a4b4c4d4e4f\n" + "salt = \n" + "info = \n" + "L = 82\n" + "PRK = 06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15f" + "c244\n" + "OKM = b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19af" + "a97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db7" + "1cc30c58179ec3e87c14c01d5c1f3434f1d87\n" + ).splitlines() + + assert load_hkdf_vectors(vector_data) == [ + (b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + b"000102030405060708090a0b0c", + b"f0f1f2f3f4f5f6f7f8f9", + 42, + b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34" + b"007208d5b887185865"), + (b"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" + b"2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041" + b"42434445464748494a4b4c4d4e4f", + b"", + b"", + 82, + b"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59" + b"045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30" + b"c58179ec3e87c14c01d5c1f3434f1d87") + ] diff --git a/tests/utils.py b/tests/utils.py index 5c0e524f..5f2c6ff6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -191,3 +191,47 @@ def load_hash_vectors(vector_data): else: raise ValueError("Unknown line in hash vector") return vectors + + +def load_hkdf_vectors(vector_data): + vectors = [] + + ikm = None + salt = None + info = None + length = None + okm = None + + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + elif line.startswith("IKM"): + ikm = line.split(" = ")[1].encode("ascii") + elif line.startswith("salt"): + l = line.split(" =") + if len(l) == 1: + salt = b"" + else: + salt = l[1].strip().encode("ascii") + elif line.startswith("info"): + l = line.split(" =") + if len(l) == 1: + info = b"" + else: + info = l[1].strip().encode("ascii") + elif line.startswith("L"): + length = int(line.split(" = ")[1]) + elif line.startswith("OKM"): + okm = line.split(" = ")[1].encode("ascii") + + vectors.append((ikm, salt, info, length, okm)) + ikm = None + salt = None + info = None + length = None + okm = None + + return vectors -- cgit v1.2.3 From a187836004cd5e4bdc7d15fe54f1be91043110a6 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 21 Jan 2014 11:52:11 -0800 Subject: This got split into SHA1 and SHA256 --- .../primitives/vectors/KDF/rfc-5869-HKDF.txt | 83 ---------------------- 1 file changed, 83 deletions(-) delete mode 100644 tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt deleted file mode 100644 index 885a5266..00000000 --- a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF.txt +++ /dev/null @@ -1,83 +0,0 @@ -# A.1. Test Case 1 -# Basic test case with SHA-256 - -Hash = SHA-256 -IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b -salt = 000102030405060708090a0b0c -info = f0f1f2f3f4f5f6f7f8f9 -L = 42 -PRK = 077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5 -OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865 - -# A.2. Test Case 2 -# Test with SHA-256 and longer inputs/outputs - -Hash = SHA-256 -IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f -salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf -info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff -L = 82 - -PRK = 06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244 -OKM = b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87 - -# A.3. Test Case 3 -# Test with SHA-256 and zero-length salt/info - -Hash = SHA-256 -IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b -salt = -info = -L = 42 - -PRK = 19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04 -OKM = 8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8 - -# A.4. Test Case 4 -# Basic test case with SHA-1 - -Hash = SHA-1 -IKM = 0b0b0b0b0b0b0b0b0b0b0b -salt = 000102030405060708090a0b0c -info = f0f1f2f3f4f5f6f7f8f9 -L = 42 - -PRK = 9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243 -OKM = 085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896 - -# A.5. Test Case 5 -# Test with SHA-1 and longer inputs/outputs - -Hash = SHA-1 -IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f -salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf -info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff -L = 82 - -PRK = 8adae09a2a307059478d309b26c4115a224cfaf6 -OKM = 0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4 - -# A.6. Test Case 6 -# Test with SHA-1 and zero-length salt/info - -Hash = SHA-1 -IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b -salt = -info = -L = 42 - -PRK = da8c8a73c7fa77288ec6f5e7c297786aa0d32d01 -OKM = 0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918 - -# A.7. Test Case 7 -# Test with SHA-1, salt not provided (defaults to HashLen zero octets), -# zero-length info - -Hash = SHA-1 -IKM = 0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c -salt = not provided (defaults to HashLen zero octets) -info = -L = 42 - -PRK = 2adccada18779e7c2077ad2eb19d3f3e731385dd -OKM = 2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc -- cgit v1.2.3 From 5443e9d949a1b720642ac25c2a2eb712515e77b0 Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 22 Jan 2014 17:18:49 -0800 Subject: Break up hkdf_derive into hkdf_extract and hkdf_expand. Testing each individually against all the vectors and actually asserting about the intermediate state. hkdf_derive is now just a helper function which copes with the default arguments. --- cryptography/hazmat/primitives/kdf/hkdf.py | 47 ++++++++++++++-------------- tests/hazmat/primitives/utils.py | 50 ++++++++++++++++++++++++------ tests/utils.py | 28 +++++------------ 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index 8d36c80b..3f3897c1 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -11,42 +11,43 @@ # See the License for the specific language governing permissions and # limitations under the License. +import six + from cryptography.hazmat.primitives import hmac -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives import constant_time -def hkdf_derive(input_key, key_length, salt, info, hash, backend): - if hash is None: - hash = hashes.SHA256() - if info is None: - info = b"" - - if salt is None: - salt = b"\x00" * (hash.digest_size // 8) +def hkdf_extract(algorithm, ikm, salt, backend): + h = hmac.HMAC(salt, algorithm, backend=backend) + h.update(ikm) + return h.finalize() - h = hmac.HMAC(salt, hash, backend=backend) - h.update(input_key) - PRK = h.finalize() +def hkdf_expand(algorithm, prk, info, length, backend): output = [b''] counter = 1 - while (hash.digest_size // 8) * len(output) < key_length: - h = hmac.HMAC(PRK, hash, backend=backend) + while (algorithm.digest_size // 8) * len(output) < length: + h = hmac.HMAC(prk, algorithm, backend=backend) h.update(output[-1]) h.update(info) - h.update(chr(counter)) + h.update(six.int2byte(counter)) output.append(h.finalize()) counter += 1 - return b"".join(output)[:key_length] + return b"".join(output)[:length] -def hkdf_verify(expected, input_key, key_length, salt, info, hash, backend): - derived = hkdf_derive(input_key, key_length, salt=salt, info=info, - hash=hash, backend=backend) - - if not constant_time.bytes_eq(expected, derived): - raise ValueError("") +def hkdf_derive(key, length, salt, info, algorithm, backend): + if info is None: + info = b"" + if salt is None: + salt = b"\x00" * (algorithm.digest_size // 8) + + return hkdf_expand( + algorithm, + hkdf_extract(algorithm, key, salt, backend=backend), + info, + length, + backend=backend + ) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index e546fa79..963838eb 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -1,12 +1,16 @@ import binascii import os +import itertools + import pytest from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.ciphers import Cipher -from cryptography.hazmat.primitives.kdf.hkdf import hkdf_derive +from cryptography.hazmat.primitives.kdf.hkdf import ( + hkdf_derive, hkdf_extract, hkdf_expand +) from cryptography.exceptions import ( AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, @@ -301,12 +305,8 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): cipher.encryptor() -def hkdf_test(backend, algorithm, params): - ikm = params[0] - salt = params[1] - info = params[2] - length = params[3] - expected_okm = params[4] +def hkdf_derive_test(backend, algorithm, params): + ikm, salt, info, length, prk, expected_okm = params okm = hkdf_derive( binascii.unhexlify(ikm), @@ -320,11 +320,43 @@ def hkdf_test(backend, algorithm, params): assert binascii.hexlify(okm) == expected_okm +def hkdf_extract_test(backend, algorithm, params): + ikm, salt, info, length, expected_prk, okm = params + + prk = hkdf_extract( + algorithm, + binascii.unhexlify(ikm), + binascii.unhexlify(salt), + backend=backend + ) + + assert prk == binascii.unhexlify(expected_prk) + + +def hkdf_expand_test(backend, algorithm, params): + ikm, salt, info, length, prk, expected_okm = params + + okm = hkdf_expand( + algorithm, + binascii.unhexlify(prk), + binascii.unhexlify(info), + length, + backend=backend + ) + + assert okm == binascii.unhexlify(expected_okm) + + def generate_hkdf_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_hkdf(self, backend, params): + all_tests = [hkdf_extract_test, hkdf_expand_test, hkdf_derive_test] + + @pytest.mark.parametrize( + ("params", "hkdf_test"), + itertools.product(all_params, all_tests) + ) + def test_hkdf(self, backend, params, hkdf_test): hkdf_test(backend, algorithm, params) return test_hkdf diff --git a/tests/utils.py b/tests/utils.py index 5f2c6ff6..850d436f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -196,11 +196,7 @@ def load_hash_vectors(vector_data): def load_hkdf_vectors(vector_data): vectors = [] - ikm = None - salt = None - info = None - length = None - okm = None + ikm = salt = info = length = prk = okm = None for line in vector_data: line = line.strip() @@ -211,27 +207,17 @@ def load_hkdf_vectors(vector_data): elif line.startswith("IKM"): ikm = line.split(" = ")[1].encode("ascii") elif line.startswith("salt"): - l = line.split(" =") - if len(l) == 1: - salt = b"" - else: - salt = l[1].strip().encode("ascii") + salt = line.split(" =")[1].strip().encode("ascii") elif line.startswith("info"): - l = line.split(" =") - if len(l) == 1: - info = b"" - else: - info = l[1].strip().encode("ascii") + info = line.split(" =")[1].strip().encode("ascii") elif line.startswith("L"): length = int(line.split(" = ")[1]) + elif line.startswith("PRK"): + prk = line.split(" = ")[1].encode("ascii") elif line.startswith("OKM"): okm = line.split(" = ")[1].encode("ascii") - vectors.append((ikm, salt, info, length, okm)) - ikm = None - salt = None - info = None - length = None - okm = None + vectors.append((ikm, salt, info, length, prk, okm)) + ikm = salt = info = length = prk = okm = None return vectors -- cgit v1.2.3 From 41f12d769c8653e812d3979a3b87f1864fcec019 Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 22 Jan 2014 17:19:50 -0800 Subject: Fix pep8. --- tests/hazmat/primitives/test_hkdf_vectors.py | 1 + tests/test_utils.py | 54 +++++++++++----------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index 2595c956..2748e010 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -22,6 +22,7 @@ from cryptography.hazmat.primitives import hashes from .utils import generate_hkdf_test from ...utils import load_hkdf_vectors + @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1." diff --git a/tests/test_utils.py b/tests/test_utils.py index 3bc4bf65..d00302fd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -537,48 +537,36 @@ def test_load_hkdf_vectors(): # Basic test case with SHA-256 Hash = SHA-256 - IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b - salt = 000102030405060708090a0b0c - info = f0f1f2f3f4f5f6f7f8f9 + IKM = 000000 + salt = 111111 + info = 222222 L = 42 - """ - "PRK = 077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2" - "b3e5\n" - "OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4" - "c5bf34007208d5b887185865\n" - """ + PRK = 333333 + OKM = 444444 + # A.2. Test Case 2 # Test with SHA-256 and longer inputs/outputs Hash = SHA-256 - """ - "IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d" - "1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3" - "f404142434445464748494a4b4c4d4e4f\n" - "salt = \n" - "info = \n" - "L = 82\n" - "PRK = 06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15f" - "c244\n" - "OKM = b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19af" - "a97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db7" - "1cc30c58179ec3e87c14c01d5c1f3434f1d87\n" - ).splitlines() + IKM = 000000 + salt = + info = + L = 82 + PRK = 333333 + OKM = 444444 + """).splitlines() assert load_hkdf_vectors(vector_data) == [ - (b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - b"000102030405060708090a0b0c", - b"f0f1f2f3f4f5f6f7f8f9", + (b"000000", + b"111111", + b"222222", 42, - b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34" - b"007208d5b887185865"), - (b"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" - b"2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041" - b"42434445464748494a4b4c4d4e4f", + b"333333", + b"444444"), + (b"000000", b"", b"", 82, - b"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59" - b"045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30" - b"c58179ec3e87c14c01d5c1f3434f1d87") + b"333333", + b"444444") ] -- cgit v1.2.3 From 9b24cb8a2c7324696adc7a595e4a143f23e5f2bc Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 22 Jan 2014 22:13:35 -0800 Subject: Fix case. --- tests/hazmat/primitives/test_hkdf_vectors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index 2748e010..d2ac79a8 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -31,7 +31,7 @@ from ...utils import load_hkdf_vectors class TestHKDFSHA1(object): test_HKDFSHA1 = generate_hkdf_test( load_hkdf_vectors, - os.path.join("kdf"), + os.path.join("KDF"), ["rfc-5869-HKDF-SHA1.txt"], hashes.SHA1() ) @@ -45,7 +45,7 @@ class TestHKDFSHA1(object): class TestHKDFSHA256(object): test_HKDFSHA1 = generate_hkdf_test( load_hkdf_vectors, - os.path.join("kdf"), + os.path.join("KDF"), ["rfc-5869-HKDF-SHA256.txt"], hashes.SHA256() ) -- cgit v1.2.3 From 14367303f16bc271f4a8f11f09b02342f44c3a7e Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 27 Jan 2014 16:33:31 -0800 Subject: Use the nist vector loader. --- tests/hazmat/primitives/test_hkdf_vectors.py | 6 +++--- tests/hazmat/primitives/utils.py | 30 +++++++++++----------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index d2ac79a8..5af4c14e 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -20,7 +20,7 @@ import pytest from cryptography.hazmat.primitives import hashes from .utils import generate_hkdf_test -from ...utils import load_hkdf_vectors +from ...utils import load_nist_vectors @pytest.mark.supported( @@ -30,7 +30,7 @@ from ...utils import load_hkdf_vectors @pytest.mark.hash class TestHKDFSHA1(object): test_HKDFSHA1 = generate_hkdf_test( - load_hkdf_vectors, + load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA1.txt"], hashes.SHA1() @@ -44,7 +44,7 @@ class TestHKDFSHA1(object): @pytest.mark.hash class TestHKDFSHA256(object): test_HKDFSHA1 = generate_hkdf_test( - load_hkdf_vectors, + load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA256.txt"], hashes.SHA256() diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 963838eb..9e9088a3 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -306,45 +306,39 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): def hkdf_derive_test(backend, algorithm, params): - ikm, salt, info, length, prk, expected_okm = params - okm = hkdf_derive( - binascii.unhexlify(ikm), - length, - binascii.unhexlify(salt), - binascii.unhexlify(info), + binascii.unhexlify(params["ikm"]), + int(params["l"]), + binascii.unhexlify(params["salt"]), + binascii.unhexlify(params["info"]), algorithm, backend=backend ) - assert binascii.hexlify(okm) == expected_okm + assert okm == binascii.unhexlify(params["okm"]) def hkdf_extract_test(backend, algorithm, params): - ikm, salt, info, length, expected_prk, okm = params - prk = hkdf_extract( algorithm, - binascii.unhexlify(ikm), - binascii.unhexlify(salt), + binascii.unhexlify(params["ikm"]), + binascii.unhexlify(params["salt"]), backend=backend ) - assert prk == binascii.unhexlify(expected_prk) + assert prk == binascii.unhexlify(params["prk"]) def hkdf_expand_test(backend, algorithm, params): - ikm, salt, info, length, prk, expected_okm = params - okm = hkdf_expand( algorithm, - binascii.unhexlify(prk), - binascii.unhexlify(info), - length, + binascii.unhexlify(params["prk"]), + binascii.unhexlify(params["info"]), + int(params["l"]), backend=backend ) - assert okm == binascii.unhexlify(expected_okm) + assert okm == binascii.unhexlify(params["okm"]) def generate_hkdf_test(param_loader, path, file_names, algorithm): -- cgit v1.2.3 From 0d492db1be3e287b5f49a5ce408196401bdd0a2b Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 27 Jan 2014 17:05:49 -0800 Subject: Closer to proposed interface in #513. --- cryptography/hazmat/primitives/kdf/hkdf.py | 56 ++++++++++++++++-------------- tests/hazmat/primitives/utils.py | 32 +++++++++-------- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index 3f3897c1..f2ea114b 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -16,38 +16,40 @@ import six from cryptography.hazmat.primitives import hmac -def hkdf_extract(algorithm, ikm, salt, backend): - h = hmac.HMAC(salt, algorithm, backend=backend) - h.update(ikm) - return h.finalize() +class HKDF(object): + def __init__(self, algorithm, length, salt, info, backend): + self._algorithm = algorithm + self._length = length + if salt is None: + salt = b"\x00" * (self._algorithm.digest_size // 8) -def hkdf_expand(algorithm, prk, info, length, backend): - output = [b''] - counter = 1 + self._salt = salt - while (algorithm.digest_size // 8) * len(output) < length: - h = hmac.HMAC(prk, algorithm, backend=backend) - h.update(output[-1]) - h.update(info) - h.update(six.int2byte(counter)) - output.append(h.finalize()) - counter += 1 + if info is None: + info = b"" - return b"".join(output)[:length] + self._info = info + self._backend = backend + def extract(self, key_material): + h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) + h.update(key_material) + return h.finalize() -def hkdf_derive(key, length, salt, info, algorithm, backend): - if info is None: - info = b"" + def expand(self, key_material): + output = [b''] + counter = 1 - if salt is None: - salt = b"\x00" * (algorithm.digest_size // 8) + while (self._algorithm.digest_size // 8) * len(output) < self._length: + h = hmac.HMAC(key_material, self._algorithm, backend=self._backend) + h.update(output[-1]) + h.update(self._info) + h.update(six.int2byte(counter)) + output.append(h.finalize()) + counter += 1 - return hkdf_expand( - algorithm, - hkdf_extract(algorithm, key, salt, backend=backend), - info, - length, - backend=backend - ) + return b"".join(output)[:self._length] + + def derive(self, key_material): + return self.expand(self.extract(key_material)) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 9e9088a3..2584272a 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -8,9 +8,7 @@ import pytest from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.ciphers import Cipher -from cryptography.hazmat.primitives.kdf.hkdf import ( - hkdf_derive, hkdf_extract, hkdf_expand -) +from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.exceptions import ( AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, @@ -306,38 +304,44 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): def hkdf_derive_test(backend, algorithm, params): - okm = hkdf_derive( - binascii.unhexlify(params["ikm"]), - int(params["l"]), - binascii.unhexlify(params["salt"]), - binascii.unhexlify(params["info"]), + hkdf = HKDF( algorithm, + int(params["l"]), + salt=binascii.unhexlify(params["salt"]) or None, + info=binascii.unhexlify(params["info"]) or None, backend=backend ) + okm = hkdf.derive(binascii.unhexlify(params["ikm"])) + assert okm == binascii.unhexlify(params["okm"]) def hkdf_extract_test(backend, algorithm, params): - prk = hkdf_extract( + hkdf = HKDF( algorithm, - binascii.unhexlify(params["ikm"]), - binascii.unhexlify(params["salt"]), + int(params["l"]), + salt=binascii.unhexlify(params["salt"]) or None, + info=binascii.unhexlify(params["info"]) or None, backend=backend ) + prk = hkdf.extract(binascii.unhexlify(params["ikm"])) + assert prk == binascii.unhexlify(params["prk"]) def hkdf_expand_test(backend, algorithm, params): - okm = hkdf_expand( + hkdf = HKDF( algorithm, - binascii.unhexlify(params["prk"]), - binascii.unhexlify(params["info"]), int(params["l"]), + salt=binascii.unhexlify(params["salt"]) or None, + info=binascii.unhexlify(params["info"]) or None, backend=backend ) + okm = hkdf.expand(binascii.unhexlify(params["prk"])) + assert okm == binascii.unhexlify(params["okm"]) -- cgit v1.2.3 From 3ddf989934884a4ca02358332b6e81ebb6727fbf Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 10:17:46 -0800 Subject: Remove load_hkdf_vectors which snuck in with a rebase. --- tests/utils.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 850d436f..5c0e524f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -191,33 +191,3 @@ def load_hash_vectors(vector_data): else: raise ValueError("Unknown line in hash vector") return vectors - - -def load_hkdf_vectors(vector_data): - vectors = [] - - ikm = salt = info = length = prk = okm = None - - for line in vector_data: - line = line.strip() - - if not line or line.startswith("#"): - continue - - elif line.startswith("IKM"): - ikm = line.split(" = ")[1].encode("ascii") - elif line.startswith("salt"): - salt = line.split(" =")[1].strip().encode("ascii") - elif line.startswith("info"): - info = line.split(" =")[1].strip().encode("ascii") - elif line.startswith("L"): - length = int(line.split(" = ")[1]) - elif line.startswith("PRK"): - prk = line.split(" = ")[1].encode("ascii") - elif line.startswith("OKM"): - okm = line.split(" = ")[1].encode("ascii") - - vectors.append((ikm, salt, info, length, prk, okm)) - ikm = salt = info = length = prk = okm = None - - return vectors -- cgit v1.2.3 From c4e7563ded0963097ce836cd701880b770f50ab9 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 10:58:37 -0800 Subject: Add test cases for length checking and already finalized. --- cryptography/hazmat/primitives/kdf/hkdf.py | 28 ++++++++-- tests/hazmat/primitives/test_hkdf.py | 85 ++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 tests/hazmat/primitives/test_hkdf.py diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index f2ea114b..c7999f10 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -13,12 +13,22 @@ import six +from cryptography import exceptions from cryptography.hazmat.primitives import hmac class HKDF(object): def __init__(self, algorithm, length, salt, info, backend): self._algorithm = algorithm + + max_length = 255 * (algorithm.digest_size // 8) + + if length > max_length: + raise ValueError( + "Can not derive keys larger than {0} octets.".format( + max_length + )) + self._length = length if salt is None: @@ -32,12 +42,14 @@ class HKDF(object): self._info = info self._backend = backend - def extract(self, key_material): + self._used = False + + def _extract(self, key_material): h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) h.update(key_material) return h.finalize() - def expand(self, key_material): + def _expand(self, key_material): output = [b''] counter = 1 @@ -52,4 +64,14 @@ class HKDF(object): return b"".join(output)[:self._length] def derive(self, key_material): - return self.expand(self.extract(key_material)) + if self._used: + raise exceptions.AlreadyFinalized + + self._used = True + return self._expand(self._extract(key_material)) + + def verify(self, key_material, expected_key): + if self._used: + raise exceptions.AlreadyFinalized + + self._used = True diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py new file mode 100644 index 00000000..3d9dffda --- /dev/null +++ b/tests/hazmat/primitives/test_hkdf.py @@ -0,0 +1,85 @@ +# 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 exceptions +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF + + +@pytest.mark.hash +class TestHKDF(object): + def test_length_limit(self, backend): + big_length = 255 * (hashes.SHA256().digest_size // 8) + 1 + + with pytest.raises(ValueError): + HKDF( + hashes.SHA256(), + big_length, + salt=None, + info=None, + backend=backend + ) + + def test_already_finalized(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.derive('\x01' * 16) + + with pytest.raises(exceptions.AlreadyFinalized): + hkdf.derive('\x02' * 16) + + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify('\x01' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + + with pytest.raises(exceptions.AlreadyFinalized): + hkdf.verify('\x02' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + + def test_verify(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify('\x01' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + + def test_verify_invalid(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + with pytest.raises(exceptions.InvalidKey): + hkdf.verify('\x02' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') -- cgit v1.2.3 From 69d16c2c6e0fb6a90ac392cc09cae0baa0c5b692 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 13:33:23 -0800 Subject: Expand, extract, verify. --- cryptography/hazmat/primitives/kdf/hkdf.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index c7999f10..71c277f4 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -15,6 +15,7 @@ import six from cryptography import exceptions from cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import constant_time class HKDF(object): @@ -44,11 +45,27 @@ class HKDF(object): self._used = False + def extract(self, key_material): + if self._used: + raise exceptions.AlreadyFinalized + + self._used = True + + return self._extract(key_material) + def _extract(self, key_material): h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) h.update(key_material) return h.finalize() + def expand(self, key_material): + if self._used: + raise exceptions.AlreadyFinalized + + self._used = True + + return self._expand(key_material) + def _expand(self, key_material): output = [b''] counter = 1 @@ -71,7 +88,5 @@ class HKDF(object): return self._expand(self._extract(key_material)) def verify(self, key_material, expected_key): - if self._used: - raise exceptions.AlreadyFinalized - - self._used = True + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise exceptions.InvalidKey -- cgit v1.2.3 From bddcb34bdd000d75a1259d0308f60ffd9b4b3375 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 13:33:40 -0800 Subject: Remove more leftovers from rebase. --- tests/test_utils.py | 45 ++------------------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index d00302fd..8ecb33f9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -20,8 +20,8 @@ import pytest from .utils import ( load_nist_vectors, load_vectors_from_file, load_cryptrec_vectors, - load_openssl_vectors, load_hash_vectors, load_hkdf_vectors, - check_for_iface, check_backend_support, select_backends + load_openssl_vectors, load_hash_vectors, check_for_iface, + check_backend_support, select_backends ) @@ -529,44 +529,3 @@ def test_load_nist_gcm_vectors(): 'ct': b'15c4db4cbb451211179d57017f', 'fail': True}, ] - - -def test_load_hkdf_vectors(): - vector_data = textwrap.dedent(""" - # A.1. Test Case 1 - # Basic test case with SHA-256 - - Hash = SHA-256 - IKM = 000000 - salt = 111111 - info = 222222 - L = 42 - PRK = 333333 - OKM = 444444 - - # A.2. Test Case 2 - # Test with SHA-256 and longer inputs/outputs - - Hash = SHA-256 - IKM = 000000 - salt = - info = - L = 82 - PRK = 333333 - OKM = 444444 - """).splitlines() - - assert load_hkdf_vectors(vector_data) == [ - (b"000000", - b"111111", - b"222222", - 42, - b"333333", - b"444444"), - (b"000000", - b"", - b"", - 82, - b"333333", - b"444444") - ] -- cgit v1.2.3 From aa9b9f1942f8c6bd5cfaa3de46e5ed64f816f49c Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 13:38:13 -0800 Subject: Actually register the interface. --- cryptography/hazmat/primitives/kdf/hkdf.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index 71c277f4..ce264f9b 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -14,10 +14,14 @@ import six from cryptography import exceptions -from cryptography.hazmat.primitives import hmac +from cryptography import utils + from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import interfaces +@utils.register_interface(interfaces.KeyDerivationFunction) class HKDF(object): def __init__(self, algorithm, length, salt, info, backend): self._algorithm = algorithm -- cgit v1.2.3 From fec6d147eef3ada5b8d3c11714216f9438db58e3 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 14:46:44 -0800 Subject: bytes all the things. --- tests/hazmat/primitives/test_hkdf.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 3d9dffda..53bc098d 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -43,10 +43,10 @@ class TestHKDF(object): backend=backend ) - hkdf.derive('\x01' * 16) + hkdf.derive(b'\x01' * 16) with pytest.raises(exceptions.AlreadyFinalized): - hkdf.derive('\x02' * 16) + hkdf.derive(b'\x02' * 16) hkdf = HKDF( hashes.SHA256(), @@ -56,10 +56,10 @@ class TestHKDF(object): backend=backend ) - hkdf.verify('\x01' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b'\x01' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') with pytest.raises(exceptions.AlreadyFinalized): - hkdf.verify('\x02' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b'\x02' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') def test_verify(self, backend): hkdf = HKDF( @@ -70,7 +70,7 @@ class TestHKDF(object): backend=backend ) - hkdf.verify('\x01' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b'\x01' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') def test_verify_invalid(self, backend): hkdf = HKDF( @@ -82,4 +82,4 @@ class TestHKDF(object): ) with pytest.raises(exceptions.InvalidKey): - hkdf.verify('\x02' * 16, 'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b'\x02' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') -- cgit v1.2.3 From ec4155a4035736011d8e3857965c60a766dd48f3 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 14:51:25 -0800 Subject: Aggressively type-check for text. --- cryptography/hazmat/primitives/kdf/hkdf.py | 26 +++++++++++++++++++ tests/hazmat/primitives/test_hkdf.py | 40 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index ce264f9b..959e8c9b 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -36,11 +36,19 @@ class HKDF(object): self._length = length + if isinstance(salt, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as a salt.") + if salt is None: salt = b"\x00" * (self._algorithm.digest_size // 8) self._salt = salt + if isinstance(info, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as info.") + if info is None: info = b"" @@ -85,6 +93,12 @@ class HKDF(object): return b"".join(output)[:self._length] def derive(self, key_material): + if isinstance(key_material, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) + if self._used: raise exceptions.AlreadyFinalized @@ -92,5 +106,17 @@ class HKDF(object): return self._expand(self._extract(key_material)) def verify(self, key_material, expected_key): + if isinstance(key_material, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) + + if isinstance(expected_key, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as " + "expected key material." + ) + if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise exceptions.InvalidKey diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 53bc098d..bd896cef 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -83,3 +83,43 @@ class TestHKDF(object): with pytest.raises(exceptions.InvalidKey): hkdf.verify(b'\x02' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + + def test_unicode_typeerror(self, backend): + with pytest.raises(TypeError): + HKDF(hashes.SHA256(), 16, salt=u'foo', info=None, backend=backend) + + with pytest.raises(TypeError): + HKDF(hashes.SHA256(), 16, salt=None, info=u'foo', backend=backend) + + with pytest.raises(TypeError): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.derive(u'foo') + + with pytest.raises(TypeError): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify(u'foo', b'bar') + + with pytest.raises(TypeError): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify(b'foo', u'bar') -- cgit v1.2.3 From 98c182f81bf37c800bb7140c207ecbd71e1982a6 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 14:53:55 -0800 Subject: Consistently use double quotes. --- cryptography/hazmat/primitives/kdf/hkdf.py | 2 +- tests/hazmat/primitives/test_hkdf.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index 959e8c9b..7dae2b6c 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -79,7 +79,7 @@ class HKDF(object): return self._expand(key_material) def _expand(self, key_material): - output = [b''] + output = [b""] counter = 1 while (self._algorithm.digest_size // 8) * len(output) < self._length: diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index bd896cef..028a08af 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -43,10 +43,10 @@ class TestHKDF(object): backend=backend ) - hkdf.derive(b'\x01' * 16) + hkdf.derive(b"\x01" * 16) with pytest.raises(exceptions.AlreadyFinalized): - hkdf.derive(b'\x02' * 16) + hkdf.derive(b"\x02" * 16) hkdf = HKDF( hashes.SHA256(), @@ -56,10 +56,10 @@ class TestHKDF(object): backend=backend ) - hkdf.verify(b'\x01' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") with pytest.raises(exceptions.AlreadyFinalized): - hkdf.verify(b'\x02' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") def test_verify(self, backend): hkdf = HKDF( @@ -70,7 +70,7 @@ class TestHKDF(object): backend=backend ) - hkdf.verify(b'\x01' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") def test_verify_invalid(self, backend): hkdf = HKDF( @@ -82,14 +82,14 @@ class TestHKDF(object): ) with pytest.raises(exceptions.InvalidKey): - hkdf.verify(b'\x02' * 16, b'gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u') + hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") def test_unicode_typeerror(self, backend): with pytest.raises(TypeError): - HKDF(hashes.SHA256(), 16, salt=u'foo', info=None, backend=backend) + HKDF(hashes.SHA256(), 16, salt=u"foo", info=None, backend=backend) with pytest.raises(TypeError): - HKDF(hashes.SHA256(), 16, salt=None, info=u'foo', backend=backend) + HKDF(hashes.SHA256(), 16, salt=None, info=u"foo", backend=backend) with pytest.raises(TypeError): hkdf = HKDF( @@ -100,7 +100,7 @@ class TestHKDF(object): backend=backend ) - hkdf.derive(u'foo') + hkdf.derive(u"foo") with pytest.raises(TypeError): hkdf = HKDF( @@ -111,7 +111,7 @@ class TestHKDF(object): backend=backend ) - hkdf.verify(u'foo', b'bar') + hkdf.verify(u"foo", b"bar") with pytest.raises(TypeError): hkdf = HKDF( @@ -122,4 +122,4 @@ class TestHKDF(object): backend=backend ) - hkdf.verify(b'foo', u'bar') + hkdf.verify(b"foo", u"bar") -- cgit v1.2.3 From e03a289ee46347a4765ff486d58f0c04f56af21b Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 15:20:41 -0800 Subject: Use six.u for great good. --- tests/hazmat/primitives/test_hkdf.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 028a08af..171cf552 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +import six + import pytest from cryptography import exceptions @@ -86,10 +88,22 @@ class TestHKDF(object): def test_unicode_typeerror(self, backend): with pytest.raises(TypeError): - HKDF(hashes.SHA256(), 16, salt=u"foo", info=None, backend=backend) + HKDF( + hashes.SHA256(), + 16, + salt=six.u("foo"), + info=None, + backend=backend + ) with pytest.raises(TypeError): - HKDF(hashes.SHA256(), 16, salt=None, info=u"foo", backend=backend) + HKDF( + hashes.SHA256(), + 16, + salt=None, + info=six.u("foo"), + backend=backend + ) with pytest.raises(TypeError): hkdf = HKDF( @@ -100,7 +114,7 @@ class TestHKDF(object): backend=backend ) - hkdf.derive(u"foo") + hkdf.derive(six.u("foo")) with pytest.raises(TypeError): hkdf = HKDF( @@ -111,7 +125,7 @@ class TestHKDF(object): backend=backend ) - hkdf.verify(u"foo", b"bar") + hkdf.verify(six.u("foo"), b"bar") with pytest.raises(TypeError): hkdf = HKDF( @@ -122,4 +136,4 @@ class TestHKDF(object): backend=backend ) - hkdf.verify(b"foo", u"bar") + hkdf.verify(b"foo", six.u("bar")) -- cgit v1.2.3 From f3f8df349f78b38a9a713030e25b7a238e32cb21 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Jan 2014 16:51:49 -0800 Subject: Complete test coverage --- tests/hazmat/primitives/test_hkdf.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 171cf552..f3345b05 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -63,6 +63,32 @@ class TestHKDF(object): with pytest.raises(exceptions.AlreadyFinalized): hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.extract(b"\x01" * 16) + + with pytest.raises(exceptions.AlreadyFinalized): + hkdf.extract(b"\x02" * 16) + + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.expand(b"\x01" * 16) + + with pytest.raises(exceptions.AlreadyFinalized): + hkdf.expand(b"\x02" * 16) + def test_verify(self, backend): hkdf = HKDF( hashes.SHA256(), -- cgit v1.2.3 From 368894cd81934d617a8b177bc6d2e73d6d45e8a9 Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 29 Jan 2014 10:46:13 -0800 Subject: Remove redundant type checks per @alex and @reaperhulk. --- cryptography/hazmat/primitives/kdf/hkdf.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index 7dae2b6c..2b5ba815 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -106,17 +106,5 @@ class HKDF(object): return self._expand(self._extract(key_material)) def verify(self, key_material, expected_key): - if isinstance(key_material, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before using them as key " - "material." - ) - - if isinstance(expected_key, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before using them as " - "expected key material." - ) - if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise exceptions.InvalidKey -- cgit v1.2.3 From c0248b9be0a207fe1b27690d819bd79ac3e1aa84 Mon Sep 17 00:00:00 2001 From: David Reid Date: Thu, 30 Jan 2014 15:23:33 -0800 Subject: HKDF docs --- .../hazmat/primitives/key-derivation-functions.rst | 66 +++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index f96eae06..678d13bf 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -13,7 +13,8 @@ Different KDFs are suitable for different tasks such as: 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.pbkdf2.PBKDF2HMAC` or HKDF. + such as :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC` or + :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. This process is typically known as `key stretching`_. * Password storage @@ -118,8 +119,71 @@ Different KDFs are suitable for different tasks such as: checking whether the password a user provides matches the stored derived key. + +.. currentmodule:: cryptography.hazmat.primitives.kdf.hkdf + +.. class:: HKDF(algorithm, length, salt, info, backend) + + .. versionadded:: 0.2 + + `HKDF`_ (HMAC-based Extract-and-Expand Key Derivation Function) suitable + for deriving keys of a fixed size used for other cryptographic operations. + + It consists of two distinct phases "Extract" and "Expand". The "Extract" + stage takes a low-entropy key and extracts from it a fixed size + psuedorandom key. The "Expand" stage derives a large key of a user + determined size from the psuedorandom 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 + 255 * (``algorithm.digest_size`` // 8). + + :param bytes salt: A salt. If ``None`` is explicitly passed a default salt + of ``algorithm.digest_size // 8`` null bytes. + + :param bytes info: Application specific context information. If ``None`` + is explicitly passed an empty byte string will be used. + + :params backend: A + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + provider. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :retunr bytes: The derived key. + + Derives a new key from the input key material by performing both the + extract and expand operations. + + .. 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 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 .. _`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 +.. _`HKDF`: http://tools.ietf.org/html/rfc5869 -- cgit v1.2.3 From 15fd6433ea357fc6d06052db85c0d0140a9c1d13 Mon Sep 17 00:00:00 2001 From: David Reid Date: Thu, 30 Jan 2014 15:28:09 -0800 Subject: Don't expose extract and expand on this class yet because we don't know how best to expose verify functionality, continue testing the stages using the private methods. --- cryptography/hazmat/primitives/kdf/hkdf.py | 16 ---------------- tests/hazmat/primitives/test_hkdf.py | 18 ------------------ tests/hazmat/primitives/utils.py | 4 ++-- 3 files changed, 2 insertions(+), 36 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index 2b5ba815..ae24f676 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -57,27 +57,11 @@ class HKDF(object): self._used = False - def extract(self, key_material): - if self._used: - raise exceptions.AlreadyFinalized - - self._used = True - - return self._extract(key_material) - def _extract(self, key_material): h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) h.update(key_material) return h.finalize() - def expand(self, key_material): - if self._used: - raise exceptions.AlreadyFinalized - - self._used = True - - return self._expand(key_material) - def _expand(self, key_material): output = [b""] counter = 1 diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index f3345b05..66993f0e 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -71,24 +71,6 @@ class TestHKDF(object): backend=backend ) - hkdf.extract(b"\x01" * 16) - - with pytest.raises(exceptions.AlreadyFinalized): - hkdf.extract(b"\x02" * 16) - - hkdf = HKDF( - hashes.SHA256(), - 16, - salt=None, - info=None, - backend=backend - ) - - hkdf.expand(b"\x01" * 16) - - with pytest.raises(exceptions.AlreadyFinalized): - hkdf.expand(b"\x02" * 16) - def test_verify(self, backend): hkdf = HKDF( hashes.SHA256(), diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 2584272a..5a8dc3ab 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -326,7 +326,7 @@ def hkdf_extract_test(backend, algorithm, params): backend=backend ) - prk = hkdf.extract(binascii.unhexlify(params["ikm"])) + prk = hkdf._extract(binascii.unhexlify(params["ikm"])) assert prk == binascii.unhexlify(params["prk"]) @@ -340,7 +340,7 @@ def hkdf_expand_test(backend, algorithm, params): backend=backend ) - okm = hkdf.expand(binascii.unhexlify(params["prk"])) + okm = hkdf._expand(binascii.unhexlify(params["prk"])) assert okm == binascii.unhexlify(params["okm"]) -- cgit v1.2.3 From 2ad94ab70b03a8edc21163a6c66fbe6a49e80715 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 10:01:15 -0800 Subject: Clarify salt language and link to the paper in addition to the RFC. --- docs/hazmat/primitives/key-derivation-functions.rst | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 678d13bf..df956326 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -126,7 +126,7 @@ Different KDFs are suitable for different tasks such as: .. versionadded:: 0.2 - `HKDF`_ (HMAC-based Extract-and-Expand Key Derivation Function) suitable + `HKDF`_ (HMAC-based Extract-and-Expand Key Derivation Function) is suitable for deriving keys of a fixed size used for other cryptographic operations. It consists of two distinct phases "Extract" and "Expand". The "Extract" @@ -141,8 +141,15 @@ Different KDFs are suitable for different tasks such as: :param int length: The desired length of the derived key. Maximum is 255 * (``algorithm.digest_size`` // 8). - :param bytes salt: A salt. If ``None`` is explicitly passed a default salt - of ``algorithm.digest_size // 8`` null bytes. + :param bytes salt: A salt. Randomizes the KDF's output. Optional, but + highly recommended. Ideally as many bits of entropy as the security + level of the hash: often that means cryptographically random and as + long as the hash output. Worse (shorter, less entropy) salt values can + still meaningfully contribute to security. May be reused. Does not have + to be secret, but may cause stronger security guarantees if secret; see + `RFC 5869`_ and the `HKDF paper`_ for more details. If ``None`` is + explicitly passed a default salt of ``algorithm.digest_size // 8`` null + bytes will be used. :param bytes info: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. @@ -186,4 +193,6 @@ Different KDFs are suitable for different tasks such as: .. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 .. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt .. _`key stretching`: http://en.wikipedia.org/wiki/Key_stretching -.. _`HKDF`: http://tools.ietf.org/html/rfc5869 +.. _`HKDF`: +.. _`RFC 5869`: http://tools.ietf.org/html/rfc5869 +.. _`HKDF paper`: http://eprint.iacr.org/2010/264 -- cgit v1.2.3 From b89f34cea6e568860ea85a3f715d04e21123d5b2 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 10:01:42 -0800 Subject: Backtick the entire equation. --- 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 df956326..325a60b3 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -139,7 +139,7 @@ Different KDFs are suitable for different tasks such as: provider. :param int length: The desired length of the derived key. Maximum is - 255 * (``algorithm.digest_size`` // 8). + ``255 * (algorithm.digest_size // 8)``. :param bytes salt: A salt. Randomizes the KDF's output. Optional, but highly recommended. Ideally as many bits of entropy as the security -- cgit v1.2.3 From 34ed26f3f4a1d53a885ed1d7b56cae92b4a6b7a8 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 10:03:58 -0800 Subject: Pseudorandom is a word. --- docs/hazmat/primitives/key-derivation-functions.rst | 4 ++-- docs/spelling_wordlist.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 325a60b3..3c9b501e 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -131,8 +131,8 @@ Different KDFs are suitable for different tasks such as: It consists of two distinct phases "Extract" and "Expand". The "Extract" stage takes a low-entropy key and extracts from it a fixed size - psuedorandom key. The "Expand" stage derives a large key of a user - determined size from the psuedorandom key. + pseudorandom key. The "Expand" stage derives a large key of a user + determined size from the pseudorandom key. :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 75628ba5..cf421ea6 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -17,6 +17,7 @@ invariants iOS pickleable plaintext +pseudorandom testability unencrypted unpadded -- cgit v1.2.3 From b80deea4bf341e2c4a283f24fec1958824195ef7 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 10:33:16 -0800 Subject: https a bunch of links. --- docs/hazmat/primitives/key-derivation-functions.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 3c9b501e..b74dc41a 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -190,9 +190,9 @@ Different KDFs are suitable for different tasks such as: .. _`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 -.. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 -.. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt -.. _`key stretching`: http://en.wikipedia.org/wiki/Key_stretching +.. _`PBKDF2`: https://en.wikipedia.org/wiki/PBKDF2 +.. _`scrypt`: https://en.wikipedia.org/wiki/Scrypt +.. _`key stretching`: https://en.wikipedia.org/wiki/Key_stretching .. _`HKDF`: -.. _`RFC 5869`: http://tools.ietf.org/html/rfc5869 -.. _`HKDF paper`: http://eprint.iacr.org/2010/264 +.. _`RFC 5869`: https://tools.ietf.org/html/rfc5869 +.. _`HKDF paper`: https://eprint.iacr.org/2010/264 -- cgit v1.2.3 From b9fa7712a751c4b54dd4b9ba54552a66cc89a34e Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 10:45:11 -0800 Subject: Lose the bit about passwords. --- docs/hazmat/primitives/key-derivation-functions.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index b74dc41a..5c3485cc 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -184,9 +184,7 @@ Different KDFs are suitable for different tasks such as: 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. + raises an exception if they do not match. .. _`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 6385561ee16742da28dacd1b4dce27baf7f0bb48 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 10:49:15 -0800 Subject: Import exception classes instead of the exceptions module. --- cryptography/hazmat/primitives/kdf/hkdf.py | 11 ++++------- tests/hazmat/primitives/test_hkdf.py | 8 ++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index ae24f676..c98a31c2 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -13,12 +13,9 @@ import six -from cryptography import exceptions from cryptography import utils - -from cryptography.hazmat.primitives import constant_time -from cryptography.hazmat.primitives import hmac -from cryptography.hazmat.primitives import interfaces +from cryptogrpahy.exceptions import AlreadyFinalized, InvalidKey +from cryptography.hazmat.primitives import constant_time, hmac, interfaces @utils.register_interface(interfaces.KeyDerivationFunction) @@ -84,11 +81,11 @@ class HKDF(object): ) if self._used: - raise exceptions.AlreadyFinalized + raise AlreadyFinalized self._used = True return self._expand(self._extract(key_material)) def verify(self, key_material, expected_key): if not constant_time.bytes_eq(self.derive(key_material), expected_key): - raise exceptions.InvalidKey + raise InvalidKey diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 66993f0e..0b7fa9b5 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -17,7 +17,7 @@ import six import pytest -from cryptography import exceptions +from cryptography.exceptions import AlreadyFinalized, from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF @@ -47,7 +47,7 @@ class TestHKDF(object): hkdf.derive(b"\x01" * 16) - with pytest.raises(exceptions.AlreadyFinalized): + with pytest.raises(AlreadyFinalized): hkdf.derive(b"\x02" * 16) hkdf = HKDF( @@ -60,7 +60,7 @@ class TestHKDF(object): hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") - with pytest.raises(exceptions.AlreadyFinalized): + with pytest.raises(AlreadyFinalized): hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") hkdf = HKDF( @@ -91,7 +91,7 @@ class TestHKDF(object): backend=backend ) - with pytest.raises(exceptions.InvalidKey): + with pytest.raises(InvalidKey): hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") def test_unicode_typeerror(self, backend): -- cgit v1.2.3 From aaf23026695eb404d435ca4f5a0c2222b804438e Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 11:12:53 -0800 Subject: Fix typo --- cryptography/hazmat/primitives/kdf/hkdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index c98a31c2..af15b64d 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -14,7 +14,7 @@ import six from cryptography import utils -from cryptogrpahy.exceptions import AlreadyFinalized, InvalidKey +from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hmac, interfaces -- cgit v1.2.3 From d69e950e3f2fb8b87c23ade50d61d9d89f3954ba Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 11:14:06 -0800 Subject: Don't forget InvalidKey. --- tests/hazmat/primitives/test_hkdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 0b7fa9b5..0497c66b 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -17,7 +17,7 @@ import six import pytest -from cryptography.exceptions import AlreadyFinalized, +from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF -- cgit v1.2.3 From 26339c580801b1e893f48c3b23eb14da8655dfbb Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 13:14:21 -0800 Subject: Remove language about the separate stages of HKDF until we expose multiple stages of HKDF. --- docs/hazmat/primitives/key-derivation-functions.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 5c3485cc..48a066c9 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -129,11 +129,6 @@ Different KDFs are suitable for different tasks such as: `HKDF`_ (HMAC-based Extract-and-Expand Key Derivation Function) is suitable for deriving keys of a fixed size used for other cryptographic operations. - It consists of two distinct phases "Extract" and "Expand". The "Extract" - stage takes a low-entropy key and extracts from it a fixed size - pseudorandom key. The "Expand" stage derives a large key of a user - determined size from the pseudorandom key. - :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. -- cgit v1.2.3 From 15ac790cfee3bf317f4d7a8c831daa7a694e6d7a Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 13:21:54 -0800 Subject: Properly mark all test cases as dependant on HMAC. --- tests/hazmat/primitives/test_hkdf.py | 2 +- tests/hazmat/primitives/test_hkdf_vectors.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 0497c66b..e3e2a9df 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF -@pytest.mark.hash +@pytest.mark.hmac class TestHKDF(object): def test_length_limit(self, backend): big_length = 255 * (hashes.SHA256().digest_size // 8) + 1 diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index 5af4c14e..1e67234f 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -24,10 +24,10 @@ from ...utils import load_nist_vectors @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA1()), + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1." ) -@pytest.mark.hash +@pytest.mark.hmac class TestHKDFSHA1(object): test_HKDFSHA1 = generate_hkdf_test( load_nist_vectors, @@ -38,10 +38,10 @@ class TestHKDFSHA1(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA256()), + only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), skip_message="Does not support SHA256." ) -@pytest.mark.hash +@pytest.mark.hmac class TestHKDFSHA256(object): test_HKDFSHA1 = generate_hkdf_test( load_nist_vectors, -- cgit v1.2.3 From 5df929ce2dea053626af4f8b3c3b98b81b359bda Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 13:26:15 -0800 Subject: HKDF example. --- .../hazmat/primitives/key-derivation-functions.rst | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 48a066c9..a91d8ca9 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -129,6 +129,32 @@ Different KDFs are suitable for different tasks such as: `HKDF`_ (HMAC-based Extract-and-Expand Key Derivation Function) is suitable for deriving keys of a fixed size used for other cryptographic operations. + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> info = b"hkdf-example" + >>> hkdf = HKDF( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... info=info, + ... backend=backend + ... ) + >>> key = hkdf.derive(b"input key) + >>> hkdf = HKDF( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... info=info, + ... backend=backend + ... ) + >>> hkdf.verify(b"input key", key) + :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. -- cgit v1.2.3 From 134f1f4acf423c3546b9552a169d10d40dd5fc84 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 3 Feb 2014 13:54:30 -0800 Subject: Strings have quote marks at both ends. --- 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 a91d8ca9..1937c2ec 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -145,7 +145,7 @@ Different KDFs are suitable for different tasks such as: ... info=info, ... backend=backend ... ) - >>> key = hkdf.derive(b"input key) + >>> key = hkdf.derive(b"input key") >>> hkdf = HKDF( ... algorithm=hashes.SHA256(), ... length=32, -- cgit v1.2.3