Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ Hannes van Niekerk
Stefan Seering
Koki Takahashi
Lauro de Lima
Emmanuel Konan
9 changes: 9 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
84 changes: 82 additions & 2 deletions Doc/src/protocol/kdf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1.pdf>`_.
The NIST standard only allows the use of HMAC (recommended) and CMAC (not recommended) as PRF.
`NIST SP 800-108r1-upd1 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf>`_.
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.

Expand Down Expand Up @@ -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 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf>`_. 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 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf>`_. 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
+++++++

Expand Down
171 changes: 154 additions & 17 deletions lib/Crypto/Protocol/KDF.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1.pdf>`_.
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 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf>`_.

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):
Expand All @@ -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 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf>`_.

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 <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf>`_.

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
38 changes: 36 additions & 2 deletions lib/Crypto/Protocol/KDF.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ...
Loading
Loading