about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf')
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__init__.py23
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/argon2.py13
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py124
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py101
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py302
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py62
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py19
-rw-r--r--.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py61
8 files changed, 705 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__init__.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__init__.py
new file mode 100644
index 00000000..79bb459f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__init__.py
@@ -0,0 +1,23 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import abc
+
+
+class KeyDerivationFunction(metaclass=abc.ABCMeta):
+    @abc.abstractmethod
+    def derive(self, key_material: bytes) -> bytes:
+        """
+        Deterministically generates and returns a new key based on the existing
+        key material.
+        """
+
+    @abc.abstractmethod
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        """
+        Checks whether the key generated by the key material matches the
+        expected derived key. Raises an exception if they do not match.
+        """
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/argon2.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/argon2.py
new file mode 100644
index 00000000..405fc8df
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/argon2.py
@@ -0,0 +1,13 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+from cryptography.hazmat.bindings._rust import openssl as rust_openssl
+from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
+
+Argon2id = rust_openssl.kdf.Argon2id
+KeyDerivationFunction.register(Argon2id)
+
+__all__ = ["Argon2id"]
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py
new file mode 100644
index 00000000..96d9d4c0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py
@@ -0,0 +1,124 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import typing
+
+from cryptography import utils
+from cryptography.exceptions import AlreadyFinalized, InvalidKey
+from cryptography.hazmat.primitives import constant_time, hashes, hmac
+from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
+
+
+def _int_to_u32be(n: int) -> bytes:
+    return n.to_bytes(length=4, byteorder="big")
+
+
+def _common_args_checks(
+    algorithm: hashes.HashAlgorithm,
+    length: int,
+    otherinfo: bytes | None,
+) -> None:
+    max_length = algorithm.digest_size * (2**32 - 1)
+    if length > max_length:
+        raise ValueError(f"Cannot derive keys larger than {max_length} bits.")
+    if otherinfo is not None:
+        utils._check_bytes("otherinfo", otherinfo)
+
+
+def _concatkdf_derive(
+    key_material: bytes,
+    length: int,
+    auxfn: typing.Callable[[], hashes.HashContext],
+    otherinfo: bytes,
+) -> bytes:
+    utils._check_byteslike("key_material", key_material)
+    output = [b""]
+    outlen = 0
+    counter = 1
+
+    while length > outlen:
+        h = auxfn()
+        h.update(_int_to_u32be(counter))
+        h.update(key_material)
+        h.update(otherinfo)
+        output.append(h.finalize())
+        outlen += len(output[-1])
+        counter += 1
+
+    return b"".join(output)[:length]
+
+
+class ConcatKDFHash(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm: hashes.HashAlgorithm,
+        length: int,
+        otherinfo: bytes | None,
+        backend: typing.Any = None,
+    ):
+        _common_args_checks(algorithm, length, otherinfo)
+        self._algorithm = algorithm
+        self._length = length
+        self._otherinfo: bytes = otherinfo if otherinfo is not None else b""
+
+        self._used = False
+
+    def _hash(self) -> hashes.Hash:
+        return hashes.Hash(self._algorithm)
+
+    def derive(self, key_material: bytes) -> bytes:
+        if self._used:
+            raise AlreadyFinalized
+        self._used = True
+        return _concatkdf_derive(
+            key_material, self._length, self._hash, self._otherinfo
+        )
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        if not constant_time.bytes_eq(self.derive(key_material), expected_key):
+            raise InvalidKey
+
+
+class ConcatKDFHMAC(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm: hashes.HashAlgorithm,
+        length: int,
+        salt: bytes | None,
+        otherinfo: bytes | None,
+        backend: typing.Any = None,
+    ):
+        _common_args_checks(algorithm, length, otherinfo)
+        self._algorithm = algorithm
+        self._length = length
+        self._otherinfo: bytes = otherinfo if otherinfo is not None else b""
+
+        if algorithm.block_size is None:
+            raise TypeError(f"{algorithm.name} is unsupported for ConcatKDF")
+
+        if salt is None:
+            salt = b"\x00" * algorithm.block_size
+        else:
+            utils._check_bytes("salt", salt)
+
+        self._salt = salt
+
+        self._used = False
+
+    def _hmac(self) -> hmac.HMAC:
+        return hmac.HMAC(self._salt, self._algorithm)
+
+    def derive(self, key_material: bytes) -> bytes:
+        if self._used:
+            raise AlreadyFinalized
+        self._used = True
+        return _concatkdf_derive(
+            key_material, self._length, self._hmac, self._otherinfo
+        )
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        if not constant_time.bytes_eq(self.derive(key_material), expected_key):
+            raise InvalidKey
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py
new file mode 100644
index 00000000..ee562d2f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py
@@ -0,0 +1,101 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import typing
+
+from cryptography import utils
+from cryptography.exceptions import AlreadyFinalized, InvalidKey
+from cryptography.hazmat.primitives import constant_time, hashes, hmac
+from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
+
+
+class HKDF(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm: hashes.HashAlgorithm,
+        length: int,
+        salt: bytes | None,
+        info: bytes | None,
+        backend: typing.Any = None,
+    ):
+        self._algorithm = algorithm
+
+        if salt is None:
+            salt = b"\x00" * self._algorithm.digest_size
+        else:
+            utils._check_bytes("salt", salt)
+
+        self._salt = salt
+
+        self._hkdf_expand = HKDFExpand(self._algorithm, length, info)
+
+    def _extract(self, key_material: bytes) -> bytes:
+        h = hmac.HMAC(self._salt, self._algorithm)
+        h.update(key_material)
+        return h.finalize()
+
+    def derive(self, key_material: bytes) -> bytes:
+        utils._check_byteslike("key_material", key_material)
+        return self._hkdf_expand.derive(self._extract(key_material))
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        if not constant_time.bytes_eq(self.derive(key_material), expected_key):
+            raise InvalidKey
+
+
+class HKDFExpand(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm: hashes.HashAlgorithm,
+        length: int,
+        info: bytes | None,
+        backend: typing.Any = None,
+    ):
+        self._algorithm = algorithm
+
+        max_length = 255 * algorithm.digest_size
+
+        if length > max_length:
+            raise ValueError(
+                f"Cannot derive keys larger than {max_length} octets."
+            )
+
+        self._length = length
+
+        if info is None:
+            info = b""
+        else:
+            utils._check_bytes("info", info)
+
+        self._info = info
+
+        self._used = False
+
+    def _expand(self, key_material: bytes) -> bytes:
+        output = [b""]
+        counter = 1
+
+        while self._algorithm.digest_size * (len(output) - 1) < self._length:
+            h = hmac.HMAC(key_material, self._algorithm)
+            h.update(output[-1])
+            h.update(self._info)
+            h.update(bytes([counter]))
+            output.append(h.finalize())
+            counter += 1
+
+        return b"".join(output)[: self._length]
+
+    def derive(self, key_material: bytes) -> bytes:
+        utils._check_byteslike("key_material", key_material)
+        if self._used:
+            raise AlreadyFinalized
+
+        self._used = True
+        return self._expand(key_material)
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        if not constant_time.bytes_eq(self.derive(key_material), expected_key):
+            raise InvalidKey
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py
new file mode 100644
index 00000000..802b484c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py
@@ -0,0 +1,302 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import typing
+
+from cryptography import utils
+from cryptography.exceptions import (
+    AlreadyFinalized,
+    InvalidKey,
+    UnsupportedAlgorithm,
+    _Reasons,
+)
+from cryptography.hazmat.primitives import (
+    ciphers,
+    cmac,
+    constant_time,
+    hashes,
+    hmac,
+)
+from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
+
+
+class Mode(utils.Enum):
+    CounterMode = "ctr"
+
+
+class CounterLocation(utils.Enum):
+    BeforeFixed = "before_fixed"
+    AfterFixed = "after_fixed"
+    MiddleFixed = "middle_fixed"
+
+
+class _KBKDFDeriver:
+    def __init__(
+        self,
+        prf: typing.Callable,
+        mode: Mode,
+        length: int,
+        rlen: int,
+        llen: int | None,
+        location: CounterLocation,
+        break_location: int | None,
+        label: bytes | None,
+        context: bytes | None,
+        fixed: bytes | None,
+    ):
+        assert callable(prf)
+
+        if not isinstance(mode, Mode):
+            raise TypeError("mode must be of type Mode")
+
+        if not isinstance(location, CounterLocation):
+            raise TypeError("location must be of type CounterLocation")
+
+        if break_location is None and location is CounterLocation.MiddleFixed:
+            raise ValueError("Please specify a break_location")
+
+        if (
+            break_location is not None
+            and location != CounterLocation.MiddleFixed
+        ):
+            raise ValueError(
+                "break_location is ignored when location is not"
+                " CounterLocation.MiddleFixed"
+            )
+
+        if break_location is not None and not isinstance(break_location, int):
+            raise TypeError("break_location must be an integer")
+
+        if break_location is not None and break_location < 0:
+            raise ValueError("break_location must be a positive integer")
+
+        if (label or context) and fixed:
+            raise ValueError(
+                "When supplying fixed data, label and context are ignored."
+            )
+
+        if rlen is None or not self._valid_byte_length(rlen):
+            raise ValueError("rlen must be between 1 and 4")
+
+        if llen is None and fixed is None:
+            raise ValueError("Please specify an llen")
+
+        if llen is not None and not isinstance(llen, int):
+            raise TypeError("llen must be an integer")
+
+        if llen == 0:
+            raise ValueError("llen must be non-zero")
+
+        if label is None:
+            label = b""
+
+        if context is None:
+            context = b""
+
+        utils._check_bytes("label", label)
+        utils._check_bytes("context", context)
+        self._prf = prf
+        self._mode = mode
+        self._length = length
+        self._rlen = rlen
+        self._llen = llen
+        self._location = location
+        self._break_location = break_location
+        self._label = label
+        self._context = context
+        self._used = False
+        self._fixed_data = fixed
+
+    @staticmethod
+    def _valid_byte_length(value: int) -> bool:
+        if not isinstance(value, int):
+            raise TypeError("value must be of type int")
+
+        value_bin = utils.int_to_bytes(1, value)
+        if not 1 <= len(value_bin) <= 4:
+            return False
+        return True
+
+    def derive(self, key_material: bytes, prf_output_size: int) -> bytes:
+        if self._used:
+            raise AlreadyFinalized
+
+        utils._check_byteslike("key_material", key_material)
+        self._used = True
+
+        # inverse floor division (equivalent to ceiling)
+        rounds = -(-self._length // prf_output_size)
+
+        output = [b""]
+
+        # For counter mode, the number of iterations shall not be
+        # larger than 2^r-1, where r <= 32 is the binary length of the counter
+        # This ensures that the counter values used as an input to the
+        # PRF will not repeat during a particular call to the KDF function.
+        r_bin = utils.int_to_bytes(1, self._rlen)
+        if rounds > pow(2, len(r_bin) * 8) - 1:
+            raise ValueError("There are too many iterations.")
+
+        fixed = self._generate_fixed_input()
+
+        if self._location == CounterLocation.BeforeFixed:
+            data_before_ctr = b""
+            data_after_ctr = fixed
+        elif self._location == CounterLocation.AfterFixed:
+            data_before_ctr = fixed
+            data_after_ctr = b""
+        else:
+            if isinstance(
+                self._break_location, int
+            ) and self._break_location > len(fixed):
+                raise ValueError("break_location offset > len(fixed)")
+            data_before_ctr = fixed[: self._break_location]
+            data_after_ctr = fixed[self._break_location :]
+
+        for i in range(1, rounds + 1):
+            h = self._prf(key_material)
+
+            counter = utils.int_to_bytes(i, self._rlen)
+            input_data = data_before_ctr + counter + data_after_ctr
+
+            h.update(input_data)
+
+            output.append(h.finalize())
+
+        return b"".join(output)[: self._length]
+
+    def _generate_fixed_input(self) -> bytes:
+        if self._fixed_data and isinstance(self._fixed_data, bytes):
+            return self._fixed_data
+
+        l_val = utils.int_to_bytes(self._length * 8, self._llen)
+
+        return b"".join([self._label, b"\x00", self._context, l_val])
+
+
+class KBKDFHMAC(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm: hashes.HashAlgorithm,
+        mode: Mode,
+        length: int,
+        rlen: int,
+        llen: int | None,
+        location: CounterLocation,
+        label: bytes | None,
+        context: bytes | None,
+        fixed: bytes | None,
+        backend: typing.Any = None,
+        *,
+        break_location: int | None = None,
+    ):
+        if not isinstance(algorithm, hashes.HashAlgorithm):
+            raise UnsupportedAlgorithm(
+                "Algorithm supplied is not a supported hash algorithm.",
+                _Reasons.UNSUPPORTED_HASH,
+            )
+
+        from cryptography.hazmat.backends.openssl.backend import (
+            backend as ossl,
+        )
+
+        if not ossl.hmac_supported(algorithm):
+            raise UnsupportedAlgorithm(
+                "Algorithm supplied is not a supported hmac algorithm.",
+                _Reasons.UNSUPPORTED_HASH,
+            )
+
+        self._algorithm = algorithm
+
+        self._deriver = _KBKDFDeriver(
+            self._prf,
+            mode,
+            length,
+            rlen,
+            llen,
+            location,
+            break_location,
+            label,
+            context,
+            fixed,
+        )
+
+    def _prf(self, key_material: bytes) -> hmac.HMAC:
+        return hmac.HMAC(key_material, self._algorithm)
+
+    def derive(self, key_material: bytes) -> bytes:
+        return self._deriver.derive(key_material, self._algorithm.digest_size)
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        if not constant_time.bytes_eq(self.derive(key_material), expected_key):
+            raise InvalidKey
+
+
+class KBKDFCMAC(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm,
+        mode: Mode,
+        length: int,
+        rlen: int,
+        llen: int | None,
+        location: CounterLocation,
+        label: bytes | None,
+        context: bytes | None,
+        fixed: bytes | None,
+        backend: typing.Any = None,
+        *,
+        break_location: int | None = None,
+    ):
+        if not issubclass(
+            algorithm, ciphers.BlockCipherAlgorithm
+        ) or not issubclass(algorithm, ciphers.CipherAlgorithm):
+            raise UnsupportedAlgorithm(
+                "Algorithm supplied is not a supported cipher algorithm.",
+                _Reasons.UNSUPPORTED_CIPHER,
+            )
+
+        self._algorithm = algorithm
+        self._cipher: ciphers.BlockCipherAlgorithm | None = None
+
+        self._deriver = _KBKDFDeriver(
+            self._prf,
+            mode,
+            length,
+            rlen,
+            llen,
+            location,
+            break_location,
+            label,
+            context,
+            fixed,
+        )
+
+    def _prf(self, _: bytes) -> cmac.CMAC:
+        assert self._cipher is not None
+
+        return cmac.CMAC(self._cipher)
+
+    def derive(self, key_material: bytes) -> bytes:
+        self._cipher = self._algorithm(key_material)
+
+        assert self._cipher is not None
+
+        from cryptography.hazmat.backends.openssl.backend import (
+            backend as ossl,
+        )
+
+        if not ossl.cmac_algorithm_supported(self._cipher):
+            raise UnsupportedAlgorithm(
+                "Algorithm supplied is not a supported cipher algorithm.",
+                _Reasons.UNSUPPORTED_CIPHER,
+            )
+
+        return self._deriver.derive(key_material, self._cipher.block_size // 8)
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        if not constant_time.bytes_eq(self.derive(key_material), expected_key):
+            raise InvalidKey
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py
new file mode 100644
index 00000000..82689ebc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py
@@ -0,0 +1,62 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import typing
+
+from cryptography import utils
+from cryptography.exceptions import (
+    AlreadyFinalized,
+    InvalidKey,
+    UnsupportedAlgorithm,
+    _Reasons,
+)
+from cryptography.hazmat.bindings._rust import openssl as rust_openssl
+from cryptography.hazmat.primitives import constant_time, hashes
+from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
+
+
+class PBKDF2HMAC(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm: hashes.HashAlgorithm,
+        length: int,
+        salt: bytes,
+        iterations: int,
+        backend: typing.Any = None,
+    ):
+        from cryptography.hazmat.backends.openssl.backend import (
+            backend as ossl,
+        )
+
+        if not ossl.pbkdf2_hmac_supported(algorithm):
+            raise UnsupportedAlgorithm(
+                f"{algorithm.name} is not supported for PBKDF2.",
+                _Reasons.UNSUPPORTED_HASH,
+            )
+        self._used = False
+        self._algorithm = algorithm
+        self._length = length
+        utils._check_bytes("salt", salt)
+        self._salt = salt
+        self._iterations = iterations
+
+    def derive(self, key_material: bytes) -> bytes:
+        if self._used:
+            raise AlreadyFinalized("PBKDF2 instances can only be used once.")
+        self._used = True
+
+        return rust_openssl.kdf.derive_pbkdf2_hmac(
+            key_material,
+            self._algorithm,
+            self._salt,
+            self._iterations,
+            self._length,
+        )
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        derived_key = self.derive(key_material)
+        if not constant_time.bytes_eq(derived_key, expected_key):
+            raise InvalidKey("Keys do not match.")
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py
new file mode 100644
index 00000000..f791ceea
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py
@@ -0,0 +1,19 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import sys
+
+from cryptography.hazmat.bindings._rust import openssl as rust_openssl
+from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
+
+# This is used by the scrypt tests to skip tests that require more memory
+# than the MEM_LIMIT
+_MEM_LIMIT = sys.maxsize // 2
+
+Scrypt = rust_openssl.kdf.Scrypt
+KeyDerivationFunction.register(Scrypt)
+
+__all__ = ["Scrypt"]
diff --git a/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py
new file mode 100644
index 00000000..6e38366a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py
@@ -0,0 +1,61 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import typing
+
+from cryptography import utils
+from cryptography.exceptions import AlreadyFinalized, InvalidKey
+from cryptography.hazmat.primitives import constant_time, hashes
+from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
+
+
+def _int_to_u32be(n: int) -> bytes:
+    return n.to_bytes(length=4, byteorder="big")
+
+
+class X963KDF(KeyDerivationFunction):
+    def __init__(
+        self,
+        algorithm: hashes.HashAlgorithm,
+        length: int,
+        sharedinfo: bytes | None,
+        backend: typing.Any = None,
+    ):
+        max_len = algorithm.digest_size * (2**32 - 1)
+        if length > max_len:
+            raise ValueError(f"Cannot derive keys larger than {max_len} bits.")
+        if sharedinfo is not None:
+            utils._check_bytes("sharedinfo", sharedinfo)
+
+        self._algorithm = algorithm
+        self._length = length
+        self._sharedinfo = sharedinfo
+        self._used = False
+
+    def derive(self, key_material: bytes) -> bytes:
+        if self._used:
+            raise AlreadyFinalized
+        self._used = True
+        utils._check_byteslike("key_material", key_material)
+        output = [b""]
+        outlen = 0
+        counter = 1
+
+        while self._length > outlen:
+            h = hashes.Hash(self._algorithm)
+            h.update(key_material)
+            h.update(_int_to_u32be(counter))
+            if self._sharedinfo is not None:
+                h.update(self._sharedinfo)
+            output.append(h.finalize())
+            outlen += len(output[-1])
+            counter += 1
+
+        return b"".join(output)[: self._length]
+
+    def verify(self, key_material: bytes, expected_key: bytes) -> None:
+        if not constant_time.bytes_eq(self.derive(key_material), expected_key):
+            raise InvalidKey