Source code for anza.crypto.hmac
"""HMAC-SHA256 signatures in lowercase hex (available as ``anza.hmac``)."""
import hmac
import hashlib
from typing import Union
from .hex import decode_length
[docs]
def sign(secret: Union[str, bytes], data: Union[str, bytes]) -> str:
"""HMAC-SHA256 of ``data`` under ``secret``, as lowercase hex.
This low-level helper does not enforce a key length; envelope signing
(:class:`~anza.SignOptions`) requires a secret of at least 32 bytes.
Args:
secret: The key; a ``str`` is encoded as UTF-8.
data: The message; a ``str`` is encoded as UTF-8.
Returns:
str: 64 lowercase hex characters.
Example:
>>> from anza import hmac
>>> sig = hmac.sign("k" * 32, "hi")
>>> sig
'1afd7d962ebfcf74a55488bdd29cb42e6da37404b5ed234e30ca6d8831252a84'
>>> hmac.verify("k" * 32, "hi", sig), hmac.verify("k" * 32, "hi", sig.upper())
(True, False)
"""
secret_bytes = secret.encode("utf-8") if isinstance(secret, str) else secret
data_bytes = data.encode("utf-8") if isinstance(data, str) else data
return hmac.new(secret_bytes, data_bytes, hashlib.sha256).hexdigest()
[docs]
def verify(secret: Union[str, bytes], data: Union[str, bytes], signature_hex: str) -> bool:
"""Verify a lowercase-hex HMAC-SHA256 signature in constant time.
Args:
secret: The key; a ``str`` is encoded as UTF-8.
data: The signed message.
signature_hex: The signature to check.
Returns:
bool: ``True`` when it matches. Malformed signatures (wrong length,
non-hex, uppercase) return ``False`` rather than raising.
"""
actual = decode_length(signature_hex, 32)
if actual is None:
return False
try:
secret_bytes = secret.encode("utf-8") if isinstance(secret, str) else secret
data_bytes = data.encode("utf-8") if isinstance(data, str) else data
expected = hmac.new(secret_bytes, data_bytes, hashlib.sha256).digest()
return hmac.compare_digest(expected, actual)
except Exception:
return False