diff --git a/AUTHORS.rst b/AUTHORS.rst index f110c81a..64aaca04 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -49,3 +49,4 @@ Hannes van Niekerk Stefan Seering Koki Takahashi Lauro de Lima +Emmanuel Konan \ No newline at end of file diff --git a/Changelog.rst b/Changelog.rst index 55ec8731..2c9e3903 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -4,6 +4,15 @@ Changelog 3.24.0 (under development) ++++++++++++++++++++++++++ +New features +--------------- +* Improvements to ``SP800_108_Counter``: + * Updated function description: Updated reference from NIST SP 800-108r1 to NIST SP 800-108r1-upd1. + * Renamed variables for clarity and reuse. + * Avoided redundant operations. + * Allowed null bytes in the context, as they are not prohibited by the standard. +* Added ``SP800_108_Feedback`` and ``SP800_108_Double_Pipeline`` from NIST SP 800-108r1-upd1 + Resolved issues --------------- * GH#875: Fixed the Object Identifiers (OID) for BLAKE2. diff --git a/Doc/src/protocol/kdf.rst b/Doc/src/protocol/kdf.rst index 7083f5ff..a7e890a8 100644 --- a/Doc/src/protocol/kdf.rst +++ b/Doc/src/protocol/kdf.rst @@ -135,8 +135,8 @@ If the PRF has a fixed-length output, you can evaluate the PRF multiple times and concatenate the results until you collect enough derived keying material. This function implements such type of KDF, where a counter contributes to each invokation of the PRF, as defined in -`NIST SP 800-108 Rev 1 `_. -The NIST standard only allows the use of HMAC (recommended) and CMAC (not recommended) as PRF. +`NIST SP 800-108r1-upd1 `_. +According to the NIST standard, only HMAC, CMAC, and KMAC are permitted as PRFs. In general, HMAC and KMAC should be preferred over CMAC (see details in the standard). This KDF is not suitable for deriving keys from a password. @@ -170,6 +170,86 @@ Example 3 (CMAC as PRF, two AES256 keys to derive):: .. autofunction:: Crypto.Protocol.KDF.SP800_108_Counter +.. _sp800-108-feeedback: + +SP 800-108 Feedback Mode +++++++++++++++++++++++++ + +This function implements a KDF, where the output of one invocation of the PRF is used as input for the next invocation of the PRF, as defined in +`NIST SP 800-108r1-upd1 `_. A counter may also be included, as in Counter Mode, but its use is optional. An initial value (IV) can optionally be provided as input to the first invocation of the PRF. + +This KDF is not suitable for deriving keys from a password. + +Example 1 (HMAC as PRF, one AES128 key to derive):: + + >> from Crypto.Hash import SHA256, HMAC + >> + >> def prf(s, x): + >> return HMAC.new(s, x, SHA256).digest() + >> + >> key_derived = SP800_108_Feedback(secret, 16, prf, label=b'Key A', context=b"Context 1", iv=b"IV", with_counter=True) + +Example 2 (HMAC as PRF, two AES128 keys to derive):: + + >> from Crypto.Hash import SHA256, HMAC + >> + >> def prf(s, x): + >> return HMAC.new(s, x, SHA256).digest() + >> + >> key_A, key_B = SP800_108_Feedback(secret, 16, prf, num_keys=2, label=b'Key AB', context=b"Context 2", iv=b"IV", with_counter=False) + +Example 3 (CMAC as PRF, two AES256 keys to derive):: + + >> from Crypto.Cipher import AES + >> from Crypto.Hash import SHA256, CMAC + >> + >> def prf(s, x): + >> return CMAC.new(s, x, AES).digest() + >> + >> key_A, key_B = SP800_108_Feedback(secret, 32, prf, num_keys=2, label=b'Key AB') + +.. autofunction:: Crypto.Protocol.KDF.SP800_108_Feedback + +.. _sp800-108-double-pipeline: + +SP 800-108 Double Pipeline Mode +++++++++++++++++++++++++ + +This function implements a KDF that combines aspects of both Counter Mode and Feedback Mode, as defined in +`NIST SP 800-108r1-upd1 `_. A counter can also be used as in the counter mode, it is optional. + +This KDF is not suitable for deriving keys from a password. + +Example 1 (HMAC as PRF, one AES128 key to derive):: + + >> from Crypto.Hash import SHA256, HMAC + >> + >> def prf(s, x): + >> return HMAC.new(s, x, SHA256).digest() + >> + >> key_derived = SP800_108_Double_Pipeline(secret, 16, prf, label=b'Key A', context=b"Context 1", with_counter=True) + +Example 2 (HMAC as PRF, two AES128 keys to derive):: + + >> from Crypto.Hash import SHA256, HMAC + >> + >> def prf(s, x): + >> return HMAC.new(s, x, SHA256).digest() + >> + >> key_A, key_B = SP800_108_Double_Pipeline(secret, 16, prf, num_keys=2, label=b'Key AB', context=b"Context 2", with_counter=False) + +Example 3 (CMAC as PRF, two AES256 keys to derive):: + + >> from Crypto.Cipher import AES + >> from Crypto.Hash import SHA256, CMAC + >> + >> def prf(s, x): + >> return CMAC.new(s, x, AES).digest() + >> + >> key_A, key_B = SP800_108_Double_Pipeline(secret, 32, prf, num_keys=2, label=b'Key AB') + +.. autofunction:: Crypto.Protocol.KDF.SP800_108_Double_Pipeline + PBKDF1 +++++++ diff --git a/lib/Crypto/Protocol/KDF.py b/lib/Crypto/Protocol/KDF.py index cca00694..c84dd7e2 100644 --- a/lib/Crypto/Protocol/KDF.py +++ b/lib/Crypto/Protocol/KDF.py @@ -588,17 +588,16 @@ def bcrypt_check(password, bcrypt_hash): raise ValueError("Incorrect bcrypt hash") -def SP800_108_Counter(master, key_len, prf, num_keys=None, label=b'', context=b''): - """Derive one or more keys from a master secret using - a pseudorandom function in Counter Mode, as specified in - `NIST SP 800-108r1 `_. +def SP800_108_Counter(master_key, key_len, prf, num_keys=None, label=b"", context=b""): + """Derive one or more keys from a master secret key using + a pseudorandom function in counter mode, as specified in + `NIST SP 800-108r1-upd1 `_. Args: - master (byte string): - The secret value used by the KDF to derive the other keys. + master_key (byte string): + The master secret value used by the KDF to derive the other keys. It must not be a password. - The length on the secret must be consistent with the input expected by - the :data:`prf` function. + The length of the secret must be consistent with the input expected by the :data:`prf` function. key_len (integer): The length in bytes of each derived key. prf (function): @@ -617,31 +616,169 @@ def SP800_108_Counter(master, key_len, prf, num_keys=None, label=b'', context=b' It must not contain zero bytes. Return: - - a byte string (if ``num_keys`` is not specified), or - - a tuple of byte strings (if ``num_key`` is specified). + bytes: + A single derived key if ``num_keys`` is not specified. + tuple[bytes]: + A tuple of derived keys if ``num_keys`` is specified. + + Raises: + ValueError: + If the number of keys to generate exceeds ``0xFFFFFFFF``. """ if num_keys is None: num_keys = 1 + + output_len = key_len * num_keys + output_len_encoding = long_to_bytes(output_len * 8, 4) + + i = 1 + dk = b"" + fixed_input_data = label + b"\x00" + context + output_len_encoding + while len(dk) < output_len: + info = long_to_bytes(i, 4) + fixed_input_data + dk += prf(master_key, info) + i += 1 + if i > 0xFFFFFFFF: + raise ValueError("Interger overflow not allowed in SP800 108 counter mode") + if num_keys == 1: + return dk[:key_len] + else: + kol = [dk[idx:idx + key_len] + for idx in iter_range(0, output_len, key_len)] + return kol + + +def SP800_108_Feedback( + master_key, key_len, prf, num_keys=None, label=b"", context=b"", iv=b"", with_counter=True +): + """Derive one or more keys from a master secret key using + a pseudorandom function in feedback mode, as specified in + `NIST SP 800-108r1-upd1 `_. + + Args: + master_key (byte string): + The master secret value used by the KDF to derive the other keys. + It must not be a password. + The length of the secret must be consistent with the input expected by the :data:`prf` function. + key_len (integer): + The length in bytes of each derived key. + prf (function): + A pseudorandom function that takes two byte strings as parameters: + the secret and an input. It returns another byte string. + num_keys (integer): + The number of keys to derive. Every key is :data:`key_len` bytes long. + By default, only 1 key is derived. + label (byte string): + Optional description of the purpose of the derived keys. + It must not contain zero bytes. + context (byte string): + Optional information pertaining to + the protocol that uses the keys, such as the identity of the + participants, nonces, session IDs, etc. + It must not contain zero bytes. + iv (bytes, optional): + Optional initialization vector. Defaults to an empty string if not specified. + with_counter (bool, optional): + If True (default), the counter is included in the input to the PRF. If False, the counter is omitted. + + Returns: + bytes: A single derived key if ``num_keys`` is not specified. + tuple[bytes]: A tuple of derived keys if ``num_keys`` is specified. + + Raises: + If the number of keys to generate exceeds ``0xFFFFFFFF``. + + """ - if context.find(b'\x00') != -1: - raise ValueError("Null byte found in context") + if num_keys is None: + num_keys = 1 - key_len_enc = long_to_bytes(key_len * num_keys * 8, 4) output_len = key_len * num_keys + output_len_encoding = long_to_bytes(output_len * 8, 4) i = 1 dk = b"" + fixed_input_data = label + b"\x00" + context + output_len_encoding + k_input = iv while len(dk) < output_len: - info = long_to_bytes(i, 4) + label + b'\x00' + context + key_len_enc - dk += prf(master, info) + info = k_input + (long_to_bytes(i, 4) * with_counter) + fixed_input_data + k_input = prf(master_key, info) + dk += k_input i += 1 if i > 0xFFFFFFFF: - raise ValueError("Overflow in SP800 108 counter") + raise ValueError("Interger overflow not allowed in SP800 108 feedback mode") + if num_keys == 1: + return dk[:key_len] + else: + kol = [dk[idx:idx + key_len] + for idx in iter_range(0, output_len, key_len)] + return kol + + +def SP800_108_Double_Pipeline( + master_key, key_len, prf, num_keys=None, label=b"", context=b"", with_counter=True +): + """Derive one or more keys from a master secret key using + a pseudorandom function in double pipeline mode, as specified in + `NIST SP 800-108r1-upd1 `_. + + Args: + master_key (byte string): + The master secret value used by the KDF to derive the other keys. + It must not be a password. + The length of the secret must be consistent with the input expected by the :data:`prf` function. + key_len (integer): + The length in bytes of each derived key. + prf (function): + A pseudorandom function that takes two byte strings as parameters: + the secret and an input. It returns another byte string. + num_keys (integer): + The number of keys to derive. Every key is :data:`key_len` bytes long. + By default, only 1 key is derived. + label (byte string): + Optional description of the purpose of the derived keys. + It must not contain zero bytes. + context (byte string): + Optional information pertaining to + the protocol that uses the keys, such as the identity of the + participants, nonces, session IDs, etc. + It must not contain zero bytes. + with_counter (bool, optional): If True (default), the counter is + included in the input to the PRF. If False, the counter is omitted. + + Returns: + bytes: + A single derived key if ``num_keys`` is not specified. + tuple[bytes]: + A tuple of derived keys if ``num_keys`` is specified. + + Raises: + ValueError: + If the number of keys to generate exceeds ``0xFFFFFFFF``. + + """ + if num_keys is None: + num_keys = 1 + + output_len = key_len * num_keys + output_len_encoding = long_to_bytes(output_len * 8, 4) + + i = 1 + dk=b"" + fixed_input_data = label + b"\x00" + context + output_len_encoding + a_i = fixed_input_data + while len(dk) < output_len: + a_i = prf(master_key, a_i) + info = a_i + (long_to_bytes(i, 4) * with_counter) + fixed_input_data + dk += prf(master_key, info) + i += 1 + if i > 0xFFFFFFFF: + raise ValueError("Interger overflow not allowed in SP800 108 double pipeline mode") if num_keys == 1: return dk[:key_len] else: kol = [dk[idx:idx + key_len] for idx in iter_range(0, output_len, key_len)] - return kol + return kol \ No newline at end of file diff --git a/lib/Crypto/Protocol/KDF.pyi b/lib/Crypto/Protocol/KDF.pyi index 80691e0f..610eceda 100644 --- a/lib/Crypto/Protocol/KDF.pyi +++ b/lib/Crypto/Protocol/KDF.pyi @@ -30,15 +30,49 @@ def bcrypt(password: Union[bytes, str], cost: int, salt: Optional[bytes]=None) - def bcrypt_check(password: Union[bytes, str], bcrypt_hash: Union[bytes, bytearray, str]) -> None: ... @overload -def SP800_108_Counter(master: Buffer, +def SP800_108_Counter(master_key: Buffer, key_len: int, prf: PRF, num_keys: Literal[None] = None, label: Buffer = b'', context: Buffer = b'') -> bytes: ... @overload -def SP800_108_Counter(master: Buffer, +def SP800_108_Counter(master_key: Buffer, key_len: int, prf: PRF, num_keys: int, label: Buffer = b'', context: Buffer = b'') -> Tuple[bytes]: ... + +@overload +def SP800_108_Feedback(master_key: Buffer, + key_len: int, + prf: PRF, + num_keys: Literal[None] = None, + label: Buffer = b'', + context: Buffer = b'', + iv: Buffer = b"", with_counter: bool = True) -> bytes: ... + +@overload +def SP800_108_Feedback(master_key: Buffer, + key_len: int, + prf: PRF, + num_keys: int, + label: Buffer = b'', + context: Buffer = b'', + iv: Buffer = b"", with_counter: bool = True) -> Tuple[bytes]: ... + +@overload +def SP800_108_Double_Pipeline(master_key: Buffer, + key_len: int, + prf: PRF, + num_keys: Literal[None] = None, + label: Buffer = b'', + context: Buffer = b'', with_counter: bool = True) -> bytes: ... + +@overload +def SP800_108_Double_Pipeline(master_key: Buffer, + key_len: int, + prf: PRF, + num_keys: int, + label: Buffer = b'', + context: Buffer = b'', with_counter: bool = True) -> Tuple[bytes]: ... diff --git a/lib/Crypto/SelfTest/Protocol/test_KDF.py b/lib/Crypto/SelfTest/Protocol/test_KDF.py index 1b2aa54c..580d82b3 100644 --- a/lib/Crypto/SelfTest/Protocol/test_KDF.py +++ b/lib/Crypto/SelfTest/Protocol/test_KDF.py @@ -33,7 +33,7 @@ from Crypto.Protocol.KDF import (PBKDF1, PBKDF2, _S2V, HKDF, scrypt, bcrypt, bcrypt_check, - SP800_108_Counter) + SP800_108_Counter, SP800_108_Feedback, SP800_108_Double_Pipeline) from Crypto.Protocol.KDF import _bcrypt_decode @@ -724,8 +724,10 @@ def prf(s, x): _ = SP800_108_Counter(b'0' * 16, 1, prf, label=b'A\x00B') except ValueError: self.fail('SP800_108_Counter failed with zero in label') - self.assertRaises(ValueError, SP800_108_Counter, b'0' * 16, 1, prf, - context=b'A\x00B') + try: + _ = SP800_108_Counter(b'0' * 16, 1, prf, context=b'A\x00B') + except ValueError: + self.fail('SP800_108_Counter failed with zero in context') def test_multiple_keys(self): def prf(s, x): @@ -784,6 +786,72 @@ def kdf_test(self, prf=prf, kin=tv.kin, label=tv.label, add_tests_sp800_108_counter(SP800_108_Counter_Tests) +class SP800_108_Feedback_Tests(unittest.TestCase): + + def test_multiple_keys_with_iv(self): + def prf(s, x): + return HMAC.new(s, x, SHA256).digest() + + key = b'0' * 16 + expected = SP800_108_Feedback(key, 2*3*23, prf, iv=b'IV') + for r in (1, 2, 3, 23): + dks = SP800_108_Feedback(key, r, prf, 138//r, iv=b'IV') + self.assertEqual(len(dks), 138//r) + self.assertEqual(len(dks[0]), r) + self.assertEqual(b''.join(dks), expected) + + def test_multiple_keys_with_counter(self): + def prf(s, x): + return HMAC.new(s, x, SHA256).digest() + + key = b'0' * 16 + expected = SP800_108_Feedback(key, 2*3*23, prf, label=b'A\x00B', context=b'B\x00A') + for r in (1, 2, 3, 23): + dks = SP800_108_Feedback(key, r, prf, 138//r, label=b'A\x00B', context=b'B\x00A') + self.assertEqual(len(dks), 138//r) + self.assertEqual(len(dks[0]), r) + self.assertEqual(b''.join(dks), expected) + + def test_multiple_keys_without_counter(self): + def prf(s, x): + return HMAC.new(s, x, SHA256).digest() + + key = b'0' * 16 + expected = SP800_108_Feedback(key, 2*3*23, prf, label=b'A\x00B', context=b'B\x00A', with_counter=False) + for r in (1, 2, 3, 23): + dks = SP800_108_Feedback(key, r, prf, 138//r, label=b'A\x00B', context=b'B\x00A', with_counter=False) + self.assertEqual(len(dks), 138//r) + self.assertEqual(len(dks[0]), r) + self.assertEqual(b''.join(dks), expected) + + +class SP800_108_Double_Pipeline_Tests(unittest.TestCase): + + def test_multiple_keys_with_counter(self): + def prf(s, x): + return HMAC.new(s, x, SHA256).digest() + + key = b'0' * 16 + expected = SP800_108_Double_Pipeline(key, 2*3*23, prf, label=b'A\x00B', context=b'B\x00A') + for r in (1, 2, 3, 23): + dks = SP800_108_Double_Pipeline(key, r, prf, 138//r, label=b'A\x00B', context=b'B\x00A') + self.assertEqual(len(dks), 138//r) + self.assertEqual(len(dks[0]), r) + self.assertEqual(b''.join(dks), expected) + + def test_multiple_keys_without_counter(self): + def prf(s, x): + return HMAC.new(s, x, SHA256).digest() + + key = b'0' * 16 + expected = SP800_108_Double_Pipeline(key, 2*3*23, prf, label=b'A\x00B', context=b'B\x00A', with_counter=False) + for r in (1, 2, 3, 23): + dks = SP800_108_Double_Pipeline(key, r, prf, 138//r, label=b'A\x00B', context=b'B\x00A', with_counter=False) + self.assertEqual(len(dks), 138//r) + self.assertEqual(len(dks[0]), r) + self.assertEqual(b''.join(dks), expected) + + def get_tests(config={}): wycheproof_warnings = config.get('wycheproof_warnings')