Source code for ecc.kdf

# Copyright (c) The btclib developers
# Distributed under the MIT software license, see the accompanying
# LICENSE file or https://opensource.org/license/mit for the full text.

"""Key derivation functions: SEC 1's ANSI-X9.63-KDF and RFC 5869's HKDF.

A KDF stretches one secret into keying data of the length a protocol
asks for, under a hash function the caller chooses. `ansi_x9_63_kdf` is
SEC 1's, one hash per block over a four-octet counter, and
`ecc.ecc.dh.diffie_hellman` is the agreement built on it. `hkdf` is RFC
5869's, extract then expand, and it is the one piece of BIP324's key
schedule this package implements: a construction over a hash rather than over
a cipher, which is the near side of the line `ecc.ecc.ellswift` draws and
`ecc.ecc.ecies` argues.

Both are `hashlib` and `hmac` and nothing else: no elliptic curve, and
no bitcoin.

**Why they are here rather than beside the agreement that uses one** (issue
btclib-org/btclib#1080). SEC 1 defines ANSI-X9.63-KDF in section 3.6.1 for the
agreement of section 6.1, so `ecc.ecc.dh` is where it reads naturally, and with
one KDF in the tree that costs nothing. HKDF has no such tie -- RFC 5869 has no
Diffie-Hellman in it, and nothing that wants HKDF here holds a public key -- so
a drawer under `ecc.ecc` leaves either a KDF reached through a package of
signature schemes or two KDFs in two places for the next one to have to choose
between. One drawer, at the layer its contents belong to, is the answer to both;
btclib-org/btclib's RELEASE_NOTES.md has the spellings it costs.
"""

from __future__ import annotations

import hmac
from math import ceil

from ecc._utils import assert_type, is_integer
from ecc.alias import HashF
from ecc.exceptions import EccTypeError, EccValueError
from ecc.hashes import _assert_valid_hf

__all__ = [
    "ansi_x9_63_kdf",
    "hkdf",
    "hkdf_expand",
    "hkdf_extract",
]


def _assert_valid_keying_data_size(size: int, max_size: int) -> None:
    """Refuse an output length no key derivation function can answer.

    `max_size` is what the caller's KDF can derive under the caller's
    hash function: SEC 1's 2**32 - 1 blocks, RFC 5869's 255.
    """
    # the range is checked before the loop bound is computed from it, and the
    # type before the range. Unchecked, a negative size made the loop empty and
    # the final negative slice returned b"" -- an empty key where the caller
    # asked for keying material, which is the one answer a key derivation
    # function must never invent (issue btclib-org/btclib#321). A float went
    # further still and reached that slice, leaving through a bare TypeError
    # from underneath the library rather than through its own exception contract
    if not is_integer(size):
        raise EccTypeError(f"non-integer keying data size: {size}")
    # zero and not merely negative: SEC 1 3.6.1 states keydatalen as a
    # positive integer, RFC 5869 states L as at most 255 * HashLen, and a
    # caller asking for no octets of key is a caller with a bug rather
    # than one with an empty key
    if size <= 0:
        raise EccValueError(f"invalid keying data size: {size}")
    if size > max_size:
        raise EccValueError(f"cannot derive a key larger than {max_size} bytes")


[docs] def ansi_x9_63_kdf(z: bytes, size: int, hf: HashF, shared_info: bytes | None) -> bytes: """Return keying data according to ANSI-X9.63-KDF. Return a keying data octet sequence of the requested size according to ANSI-X9.63-KDF specifications for the key derivation function. `size` is a positive number of octets, SEC 1's keydatalen: an EccTypeError if it is no integer, an EccValueError if it is zero, negative, or above what the hash function can derive. http://www.secg.org/sec1-v2.pdf, section 3.6.1 `z` and `shared_info` are `bytes` and not `Octets`: `assert_type` is what refuses a caller's mistake here, `hf` never reaching a bare `hf()` on something that is not callable (issue btclib-org/ecc#11). """ _assert_valid_hf(hf) assert_type(z, bytes, "z") if shared_info is not None: assert_type(shared_info, bytes, "shared_info") hf_size = hf().digest_size # the counter is four octets, so 2**32 - 1 blocks is what the # construction can number _assert_valid_keying_data_size(size, hf_size * (2**32 - 1)) # the whole buffer is asked for once, and the blocks are written into # it. A list of digests joined and then sliced holds the keying data # three times over -- the digests, the join's copy, the slice's -- # where this holds it twice: deriving 64 MiB peaks at 2.00x the # output against 5.79x. What the writing costs at the one or two # blocks `diffie_hellman` asks for is around one percent of the # multiplication it arrives through -- and a tenth of the derivation # at sizes far beyond that # # the `memoryview` is what makes each write exact: a bytearray slice # assignment of the wrong length resizes the buffer and shifts every # block after it, where this raises. At the end `bytes(view[:size])` # copies once, where `bytes(keying_data[:size])` would copy twice n_blocks = ceil(size / hf_size) keying_data = bytearray(n_blocks * hf_size) view = memoryview(keying_data) for counter in range(1, n_blocks + 1): h = hf() hash_input = ( z + counter.to_bytes(4, byteorder="big", signed=False) + (b"" if shared_info is None else shared_info) ) h.update(hash_input) start = (counter - 1) * hf_size view[start : start + hf_size] = h.digest() return bytes(view[:size])
[docs] def hkdf_extract(ikm: bytes, salt: bytes | None, hf: HashF) -> bytes: """Return the pseudorandom key of HKDF's extract step. `ikm` is the input keying material, which need not be uniformly distributed -- concentrating whatever entropy it has into one digest is what this step is for. `salt` is an optional non-secret value, which may be reused. The answer is one digest of `hf`, and is what `hkdf_expand` takes as its `prk`. https://www.rfc-editor.org/rfc/rfc5869.html, section 2.2 An `ikm` that is already a uniformly random key of full length does not need this step: RFC 5869 section 3.3 says to skip it and expand that key directly, which `hkdf_expand` is separately public for. `ikm` and `salt` are `bytes` and not `Octets`, for the reason `ansi_x9_63_kdf`'s docstring gives (issue btclib-org/ecc#11). """ _assert_valid_hf(hf) assert_type(ikm, bytes, "ikm") if salt is not None: assert_type(salt, bytes, "salt") # RFC 5869 sets an absent salt to HashLen zero octets, and b"" is the # same HMAC key: a key shorter than the block size is zero-padded to # it, so both spellings hash the same first block. Appendix A carries # a vector for each of them, and they arrive here as one return hmac.new(b"" if salt is None else salt, ikm, hf).digest()
[docs] def hkdf_expand(prk: bytes, size: int, hf: HashF, info: bytes | None) -> bytes: """Return output keying material of the requested size. `prk` is a pseudorandom key, `hkdf_extract`'s answer or a uniformly random key of at least a digest's length. `info` is optional context -- a protocol label, a party's identity -- which binds the output to an application, so that one `prk` yields independent keys. `size` is a positive number of octets, RFC 5869's L: an EccTypeError if it is no integer, an EccValueError if it is zero, negative, or above 255 digests. A `prk` below a digest is an EccValueError too. https://www.rfc-editor.org/rfc/rfc5869.html, section 2.3 `prk` and `info` are `bytes` and not `Octets`, for the reason `ansi_x9_63_kdf`'s docstring gives (issue btclib-org/ecc#11). """ _assert_valid_hf(hf) assert_type(prk, bytes, "prk") if info is not None: assert_type(info, bytes, "info") hf_size = hf().digest_size # the counter is a single octet, from 1, so 255 blocks is what the # construction can number -- 8160 octets under sha256 _assert_valid_keying_data_size(size, 255 * hf_size) # section 2.3 states the prk as at least HashLen octets, and the # length is worth refusing rather than deriving from: HMAC pads a # short key with zeros instead of stretching it, so every octet # expanded out of one is only as strong as the key was. `hkdf` cannot # reach this, `hkdf_extract` answering one digest exactly; a # caller skipping the extract step per section 3.3 can if len(prk) < hf_size: raise EccValueError(f"pseudorandom key shorter than {hf_size} bytes") context = b"" if info is None else info # a list joined, where `ansi_x9_63_kdf` writes into one buffer: that # one has no ceiling short of gigabytes, so the copies the join makes # are worth avoiding there, while 255 blocks put a few kilobytes at # the top of this one. Each block is also the next block's input, so # the sequence is held either way blocks: list[bytes] = [] block = b"" for counter in range(1, ceil(size / hf_size) + 1): block = hmac.new(prk, block + context + bytes([counter]), hf).digest() blocks.append(block) return b"".join(blocks)[:size]
[docs] def hkdf( ikm: bytes, size: int, hf: HashF, salt: bytes | None, info: bytes | None ) -> bytes: """Return keying data according to HKDF, extract then expand. The composition of the two steps above, which is how RFC 5869 is used unless the caller already holds a uniformly random key: extract concentrates the entropy of `ikm` under `salt`, expand stretches the result to `size` octets under `info`. https://www.rfc-editor.org/rfc/rfc5869.html, section 2 """ return hkdf_expand(hkdf_extract(ikm, salt, hf), size, hf, info)