"""HMAC generators — HMAC-SHA256 computation. Ported from net.i2p.crypto.HMACGenerator and HMAC256Generator. Wraps Python's hmac module. """ import hmac as _hmac import hashlib from i2p_crypto.hash_data import Hash class HMACGenerator: """Abstract base for HMAC computation.""" def calculate(self, key: bytes, data: bytes, offset: int, length: int, target: bytearray, target_offset: int) -> None: raise NotImplementedError def verify(self, key: bytes, data: bytes, offset: int, length: int, orig_mac: bytes, orig_mac_offset: int, orig_mac_length: int) -> bool: raise NotImplementedError class HMAC256Generator(HMACGenerator): """HMAC-SHA256 generator. Thread-safe. Ported from net.i2p.crypto.HMAC256Generator. """ _instance: "HMAC256Generator | None" = None def __init__(self) -> None: super().__init__() @classmethod def get_instance(cls) -> "HMAC256Generator": if cls._instance is None: cls._instance = HMAC256Generator() return cls._instance def calculate(self, key: bytes, data: bytes, offset: int = 0, length: int = -1, # type: ignore[override] target: bytearray | None = None, target_offset: int = 0) -> bytes: """Calculate HMAC-SHA256. If target is provided, writes 32 bytes into target at target_offset. Always returns the 32-byte MAC. """ if length < 0: length = len(data) - offset # Use first 32 bytes of key k = key[:32] mac = _hmac.new(k, data[offset : offset + length], hashlib.sha256).digest() if target is not None: target[target_offset : target_offset + 32] = mac return mac def verify(self, key: bytes, data: bytes, offset: int, length: int, orig_mac: bytes, orig_mac_offset: int = 0, orig_mac_length: int = 32) -> bool: """Verify HMAC-SHA256 in constant time.""" calc = self.calculate(key, data, offset, length) return _hmac.compare_digest( calc[:orig_mac_length], orig_mac[orig_mac_offset : orig_mac_offset + orig_mac_length], )