Revision control

Copy as Markdown

Other Tools

"""
(C) 2026 Jack Lloyd
(C) 2026 René Meusel, Rohde & Schwarz Cybersecurity
Botan is released under the Simplified BSD License (see license.txt)
"""
import binascii
import unittest
import botan3 as botan
from .wycheproof import WycheproofTests
_MAC_ALGORITHMS = {
"HMACSHA1": "HMAC(SHA-1)",
"HMACSHA224": "HMAC(SHA-224)",
"HMACSHA256": "HMAC(SHA-256)",
"HMACSHA384": "HMAC(SHA-384)",
"HMACSHA512": "HMAC(SHA-512)",
"HMACSHA3-224": "HMAC(SHA-3(224))",
"HMACSHA3-256": "HMAC(SHA-3(256))",
"HMACSHA3-384": "HMAC(SHA-3(384))",
"HMACSHA3-512": "HMAC(SHA-3(512))",
"HMACSM3": "HMAC(SM3)",
"SipHash-1-3": "SipHash(1,3)",
"SipHash-2-4": "SipHash(2,4)",
"SipHash-4-8": "SipHash(4,8)",
"KMAC128": "KMAC-128",
"KMAC256": "KMAC-256",
}
def _mac_algorithm(
algorithm: str, key_size_bits: int | None, tag_size_bits: int | None
) -> str:
if algorithm == "AES-GMAC":
if key_size_bits is None:
raise ValueError("AES-GMAC requires a key size")
return f"GMAC(AES-{key_size_bits})"
if algorithm.startswith("KMAC"):
if tag_size_bits is None:
raise ValueError("KMAC requires a tag size")
return f"{_MAC_ALGORITHMS[algorithm]}({tag_size_bits})"
return _MAC_ALGORITHMS[algorithm]
class TestMAC(WycheproofTests, unittest.TestCase):
def input_files(self) -> list[str]:
return [
"hmac_sha1_test.json",
"hmac_sha224_test.json",
"hmac_sha256_test.json",
"hmac_sha384_test.json",
"hmac_sha3_224_test.json",
"hmac_sha3_256_test.json",
"hmac_sha3_384_test.json",
"hmac_sha3_512_test.json",
"hmac_sha512_test.json",
"hmac_sm3_test.json",
"siphash_1_3_test.json",
"siphash_2_4_test.json",
"siphash_4_8_test.json",
"aes_gmac_test.json",
"kmac128_no_customization_test.json",
"kmac256_no_customization_test.json",
]
def run_test(self, data: dict, group: dict, test: dict) -> None:
key_size_bits = group.get("keySize")
iv_size_bits = group.get("ivSize")
tag_size_bits = group.get("tagSize")
algo = _mac_algorithm(data["algorithm"], key_size_bits, tag_size_bits)
mac = botan.MsgAuthCode(algo)
mac.set_key(binascii.unhexlify(test["key"]))
if iv_size_bits is not None:
mac.set_nonce(binascii.unhexlify(test["iv"]))
mac.update(binascii.unhexlify(test["msg"]))
final_mac = mac.final()
actual_mac = (
final_mac[: tag_size_bits // 8] if tag_size_bits is not None else final_mac
)
expected = binascii.unhexlify(test["tag"])
if test["result"] == "valid":
self.assertEqual(actual_mac, expected)
elif test["result"] == "invalid":
self.assertNotEqual(actual_mac, expected)
elif test["result"] == "acceptable":
pass
else:
self.fail(f"Unknown test result: {test['result']}")