Source code for anza.crypto.hkdf

"""HKDF-SHA256 key derivation (available as ``anza.hkdf``)."""

import hashlib
from typing import Union, Optional
from ..errors import AnzaError


[docs] def derive_key( ikm: Union[str, bytes], salt: Optional[Union[str, bytes]] = None, info: Union[str, bytes] = "anza-stui-session-v1", keylen: int = 32, ) -> bytes: """Derive a key with HKDF-SHA256 (RFC 5869). Use it to turn one master secret into separate keys per purpose (for example an HMAC envelope secret per service). Args: ikm: Input keying material (the master secret). salt: Optional salt; empty when omitted. info: Context string that separates the derived keys. keylen: Length of the derived key, in bytes. Returns: bytes: ``keylen`` bytes of key material. Raises: AnzaError: ``CRYPTO`` when derivation fails (e.g. ``keylen`` too large). Example: >>> from anza import hkdf >>> key = hkdf.derive_key("master secret", info="feed-envelopes") >>> len(key) 32 """ try: ikm_bytes = ikm.encode("utf-8") if isinstance(ikm, str) else ikm salt_bytes = (salt.encode("utf-8") if isinstance(salt, str) else salt) if salt else b"" info_bytes = info.encode("utf-8") if isinstance(info, str) else info # Python 3.8+ native hashlib.hkdf if hasattr(hashlib, "hkdf"): return hashlib.hkdf( "sha256", ikm=ikm_bytes, salt=salt_bytes, info=info_bytes, length=keylen, ) # Fallback manual RFC 5869 extract and expand using hmac import hmac if not salt_bytes: salt_bytes = b"\x00" * 32 prk = hmac.new(salt_bytes, ikm_bytes, hashlib.sha256).digest() t = b"" okm = b"" i = 1 while len(okm) < keylen: t = hmac.new(prk, t + info_bytes + bytes([i]), hashlib.sha256).digest() okm += t i += 1 return okm[:keylen] except Exception as e: raise AnzaError.crypto(f"HKDF key derivation failed: {e}")