diff --git a/src/bindings/crypto_onetimeauth.h b/src/bindings/crypto_onetimeauth.h new file mode 100644 index 00000000..224a7e4e --- /dev/null +++ b/src/bindings/crypto_onetimeauth.h @@ -0,0 +1,12 @@ +int crypto_onetimeauth(unsigned char *out, + const unsigned char *in, + unsigned long long inlen, + const unsigned char *k); + +int crypto_onetimeauth_verify(const unsigned char *h, + const unsigned char *in, + unsigned long long inlen, + const unsigned char *k); + +size_t crypto_onetimeauth_bytes(void); +size_t crypto_onetimeauth_keybytes(void); diff --git a/src/nacl/bindings/__init__.py b/src/nacl/bindings/__init__.py index 2e07ba1e..83d8d023 100644 --- a/src/nacl/bindings/__init__.py +++ b/src/nacl/bindings/__init__.py @@ -130,6 +130,12 @@ crypto_kx_seed_keypair, crypto_kx_server_session_keys, ) +from nacl.bindings.crypto_onetimeauth import ( + crypto_onetimeauth, + crypto_onetimeauth_verify, + crypto_onetimeauth_BYTES, + crypto_onetimeauth_KEYBYTES, +) from nacl.bindings.crypto_pwhash import ( crypto_pwhash_ALG_ARGON2I13, crypto_pwhash_ALG_ARGON2ID13, @@ -378,6 +384,10 @@ "crypto_kx_SECRET_KEY_BYTES", "crypto_kx_SEED_BYTES", "crypto_kx_SESSION_KEY_BYTES", + "crypto_onetimeauth", + "crypto_onetimeauth_verify", + "crypto_onetimeauth_BYTES", + "crypto_onetimeauth_KEYBYTES", "has_crypto_scalarmult_ed25519", "crypto_scalarmult_BYTES", "crypto_scalarmult_SCALARBYTES", diff --git a/src/nacl/bindings/crypto_onetimeauth.py b/src/nacl/bindings/crypto_onetimeauth.py new file mode 100644 index 00000000..b5eec6b2 --- /dev/null +++ b/src/nacl/bindings/crypto_onetimeauth.py @@ -0,0 +1,39 @@ +from nacl._sodium import ffi, lib + + +crypto_onetimeauth_BYTES = lib.crypto_onetimeauth_bytes() +crypto_onetimeauth_KEYBYTES = lib.crypto_onetimeauth_keybytes() + + +def crypto_onetimeauth(message: bytes, key: bytes) -> bytes: + """ + Generate Poly1305 MAC over message with key. + + :param message: data to authenticate + :param key: 32-bytes Poly1305 key + :return: 16-bytes MAC (tag) + """ + if len(key) != crypto_onetimeauth_KEYBYTES: + raise ValueError(f"Key must be {crypto_onetimeauth_KEYBYTES} bytes") + + mac = ffi.new(f"unsigned char[{crypto_onetimeauth_BYTES}]") + lib.crypto_onetimeauth(mac, message, len(message), key) + return ffi.buffer(mac, crypto_onetimeauth_BYTES)[:] + + +def crypto_onetimeauth_verify(mac: bytes, message: bytes, key: bytes) -> bool: + """ + Verify if mac is valid for message and key. + + :param mac: 16-bytes MAC/tag to check + :param message: data + :param key: 32-bytes key + :return: True on valid MAC, else False + """ + if len(mac) != crypto_onetimeauth_BYTES: + raise ValueError(f"MAC must be {crypto_onetimeauth_BYTES} bytes") + if len(key) != crypto_onetimeauth_KEYBYTES: + raise ValueError(f"Key must be {crypto_onetimeauth_KEYBYTES} bytes") + + rc = lib.crypto_onetimeauth_verify(mac, message, len(message), key) + return rc == 0 diff --git a/tests/test_bindings.py b/tests/test_bindings.py index a89c361c..61069016 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -1020,3 +1020,30 @@ def test_scalarmult_ed25519_unavailable(): c.crypto_scalarmult_ed25519(zero, zero) with pytest.raises(UnavailableError): c.crypto_scalarmult_ed25519_noclamp(zero, zero) + + +def test_onetimeauth_wrong_length(): + with pytest.raises(ValueError): + c.crypto_onetimeauth(b"", b"") + with pytest.raises(ValueError): + c.crypto_onetimeauth(b"message", b"") + with pytest.raises(ValueError): + c.crypto_onetimeauth_verify(b"", b"", b"") + with pytest.raises(ValueError): + c.crypto_onetimeauth_verify( + b"\x00" * c.crypto_onetimeauth_BYTES, b"message", b"" + ) + with pytest.raises(ValueError): + c.crypto_onetimeauth_verify( + b"", b"message", b"\x00" * c.crypto_onetimeauth_KEYBYTES + ) + + +def test_onetimeauth(): + key = b"\x01" * c.crypto_onetimeauth_KEYBYTES + msg = b"message" + + mac = c.crypto_onetimeauth(msg, key) + assert tohex(mac) == "4cee137430de76761b1d11751b1d1175" + + assert c.crypto_onetimeauth_verify(mac, msg, key)