Project renamed to aeg, preparing for PyPI release.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
"""Loader for libaegis CFFI extension module."""
|
||||
|
||||
from aeg._aegis import ffi, lib
|
||||
|
||||
__all__ = ["ffi", "lib"]
|
||||
|
||||
lib.aegis_init()
|
||||
@@ -0,0 +1,921 @@
|
||||
"""AEGIS-128L"""
|
||||
# All modules are generated from aegis256x4.py by tools/generate.py!
|
||||
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
|
||||
|
||||
import errno
|
||||
import secrets
|
||||
from typing import Literal
|
||||
|
||||
from ._loader import ffi
|
||||
from ._loader import lib as _lib
|
||||
from .util import Buffer, new_aligned_struct, nonce_increment, wipe
|
||||
|
||||
NAME = "AEGIS-128L" #: Algorithm display name
|
||||
KEYBYTES = 16 #: Key size in bytes (varies by algorithm)
|
||||
NONCEBYTES = 16 #: Nonce size in bytes (varies by algorithm)
|
||||
MACBYTES = 16 #: Normal MAC size (always 16)
|
||||
MACBYTES_LONG = 32 #: Long MAC size (always 32)
|
||||
ALIGNMENT = 64 #: Required alignment for internal structures
|
||||
RATE = 64 #: Byte chunk size in internal processing
|
||||
|
||||
|
||||
def random_key() -> bytearray:
|
||||
"""
|
||||
Generate a secret key using cryptographically secure random bytes.
|
||||
|
||||
It is recommended to wipe() the key after no longer needed.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(KEYBYTES))
|
||||
|
||||
|
||||
def random_nonce() -> bytearray:
|
||||
"""
|
||||
Generate a public nonce using cryptographically secure random bytes.
|
||||
|
||||
Nonces (a number used once) are public data that may be sent together
|
||||
with the ciphertext, but they need to be unique for each use.
|
||||
|
||||
See also: nonce_increment() can be used to derive sequential nonces.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(NONCEBYTES))
|
||||
|
||||
|
||||
def _ptr(buf):
|
||||
return ffi.NULL if buf is None else ffi.from_buffer(buf)
|
||||
|
||||
|
||||
def encrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
ct_into: Buffer | None = None,
|
||||
mac_into: Buffer | None = None,
|
||||
) -> tuple[bytearray | memoryview, bytearray | memoryview]:
|
||||
"""Encrypt message with associated data, returning ciphertext and MAC separately.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
ct_into: Buffer to write ciphertext into (default: bytearray created).
|
||||
mac_into: Buffer to write MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Tuple of (ciphertext, mac)
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if ct_into is not None:
|
||||
ct_into = memoryview(ct_into)
|
||||
if mac_into is not None:
|
||||
mac_into = memoryview(mac_into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
if ct_into is None:
|
||||
c = bytearray(message.nbytes)
|
||||
else:
|
||||
if ct_into.nbytes < message.nbytes:
|
||||
raise TypeError("ct_into length must be at least message.nbytes")
|
||||
c = ct_into
|
||||
if mac_into is None:
|
||||
mac = bytearray(maclen)
|
||||
else:
|
||||
if mac_into.nbytes < maclen:
|
||||
raise TypeError("mac_into length must be at least maclen")
|
||||
mac = mac_into
|
||||
|
||||
rc = _lib.aegis128l_encrypt_detached(
|
||||
ffi.from_buffer(c),
|
||||
ffi.from_buffer(mac),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt detached failed: {err_name}")
|
||||
return (
|
||||
c if ct_into is None else memoryview(c)[: message.nbytes],
|
||||
mac if mac_into is None else memoryview(mac)[:maclen],
|
||||
) # type: ignore
|
||||
|
||||
|
||||
def decrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
mac: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with detached MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
mac: The MAC to verify.
|
||||
ad: Associated data (optional).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
mac = memoryview(mac)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128l_decrypt_detached(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(mac),
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message with associated data, returning ciphertext with appended MAC.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write ciphertext+MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes + maclen)
|
||||
else:
|
||||
if into.nbytes < message.nbytes + maclen:
|
||||
raise TypeError("into length must be at least message.nbytes + maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128l_encrypt(
|
||||
ffi.from_buffer(out),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt failed: {err_name}")
|
||||
return out if into is None else memoryview(out)[: message.nbytes + maclen] # type: ignore
|
||||
|
||||
|
||||
def decrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with appended MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext with MAC to decrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if ct.nbytes < maclen:
|
||||
raise TypeError("ciphertext too short for tag")
|
||||
expected_out = ct.nbytes - maclen
|
||||
if into is None:
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128l_decrypt(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
||||
|
||||
|
||||
def stream(
|
||||
key: Buffer,
|
||||
nonce: Buffer | None,
|
||||
length: int | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | Buffer:
|
||||
"""Generate a stream of pseudorandom bytes.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce(), uses zeroes for nonce if None).
|
||||
length: Number of bytes to generate (required if into is None).
|
||||
into: Buffer to write stream into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Pseudorandom bytes as bytearray, or into returned directly.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid or neither length nor into provided.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
if nonce is not None:
|
||||
nonce = memoryview(nonce)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce is not None and nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
if length is None:
|
||||
raise TypeError("provide either into or length")
|
||||
out = bytearray(length)
|
||||
else:
|
||||
if length is not None and into.nbytes < length:
|
||||
raise TypeError("into length must be at least length")
|
||||
out = into
|
||||
_lib.aegis128l_stream(
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
into: Buffer to write ciphertext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes)
|
||||
else:
|
||||
if into.nbytes < message.nbytes:
|
||||
raise TypeError("into length must be at least message.nbytes")
|
||||
out = into
|
||||
_lib.aegis128l_encrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: message.nbytes] # type: ignore
|
||||
|
||||
|
||||
def decrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
_lib.aegis128l_decrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
# This is missing from C API but convenient to have here
|
||||
def mac(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
data: Buffer,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Compute a MAC for the given data in one shot.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key())
|
||||
nonce: Public nonce (generate with random_nonce())
|
||||
data: Data to MAC
|
||||
maclen: MAC length (16 or 32, default 16)
|
||||
into: Buffer to write MAC into (default: bytearray created)
|
||||
|
||||
Returns:
|
||||
MAC bytes as bytearray if into not provided, memoryview of into otherwise
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
data = memoryview(data)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
mac_state = Mac(key, nonce, maclen)
|
||||
mac_state.update(data)
|
||||
return mac_state.final(into)
|
||||
|
||||
|
||||
class Mac:
|
||||
"""MAC calculation and verification with incremental updates.
|
||||
|
||||
Example:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
...
|
||||
mac = a.final()
|
||||
|
||||
Hashlib compatible interface:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
bytes_mac = a.digest()
|
||||
hex_mac = a.hexdigest()
|
||||
"""
|
||||
|
||||
__slots__ = ("_proxy", "_maclen", "_cached_digest")
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer, maclen: int = MACBYTES) -> None:
|
||||
"""Create a MAC with the given key, nonce, and tag length.
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
self._maclen = maclen
|
||||
self._proxy = new_aligned_struct("aegis128l_mac_state", ALIGNMENT)
|
||||
_lib.aegis128l_mac_init(self._proxy.ptr, _ptr(key), _ptr(nonce))
|
||||
self._cached_digest: None | Literal[False] | bytes = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset back to the original state, prior to any updates."""
|
||||
_lib.aegis128l_mac_reset(self._proxy.ptr)
|
||||
self._cached_digest = None
|
||||
|
||||
def clone(self) -> "Mac":
|
||||
"""Return a clone of current MAC state."""
|
||||
clone = object.__new__(Mac)
|
||||
clone._maclen = self._maclen
|
||||
clone._proxy = new_aligned_struct("aegis128l_mac_state", ALIGNMENT)
|
||||
_lib.aegis128l_mac_state_clone(clone._proxy.ptr, self._proxy.ptr)
|
||||
clone._cached_digest = self._cached_digest
|
||||
return clone
|
||||
|
||||
__deepcopy__ = clone
|
||||
|
||||
def update(self, data: Buffer) -> None:
|
||||
"""Update the MAC state with more data.
|
||||
|
||||
Repeated calls to update() are equivalent to a single call with the concatenated data.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError("Cannot update after final()")
|
||||
data = memoryview(data)
|
||||
rc = _lib.aegis128l_mac_update(self._proxy.ptr, _ptr(data), data.nbytes)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac update failed: {err_name}")
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Calculate and return the MAC tag for the currently input data.
|
||||
|
||||
This method can only be called once. After calling it, the MAC becomes unusable
|
||||
for further updates or calls to final().
|
||||
|
||||
Args:
|
||||
into: Optional buffer to write the tag into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
The tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If finalization fails in the C library or if already finalized.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError(
|
||||
"The MAC can only be calculated once. Use reset() to start over, or clone() before finalizing to continue."
|
||||
)
|
||||
maclen = self._maclen
|
||||
if into is None:
|
||||
out = bytearray(maclen)
|
||||
else:
|
||||
into = memoryview(into)
|
||||
if into.nbytes < maclen:
|
||||
raise TypeError("into length must be at least maclen")
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis128l_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac final failed: {err_name}")
|
||||
self._cached_digest = False
|
||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
||||
|
||||
def digest(self) -> bytes:
|
||||
"""Calculate and return the MAC tag as bytes.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
Can be called after final() to get the cached digest.
|
||||
"""
|
||||
if self._cached_digest:
|
||||
return self._cached_digest
|
||||
self._cached_digest = bytes(self.final()) # Overrides the False set by final()
|
||||
return self._cached_digest
|
||||
|
||||
def hexdigest(self) -> str:
|
||||
"""Calculate and return the MAC tag as a hex string.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
"""
|
||||
return self.digest().hex()
|
||||
|
||||
def verify(self, mac: Buffer):
|
||||
"""Verify that the data entered so far matches the given MAC tag.
|
||||
|
||||
Unlike the C library, this method does not alter the current state.
|
||||
|
||||
Args:
|
||||
mac: The tag to verify against (16 or 32 bytes).
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length is invalid.
|
||||
ValueError: If verification fails.
|
||||
"""
|
||||
mac = memoryview(mac)
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
|
||||
cloned = self.clone()
|
||||
rc = _lib.aegis128l_mac_verify(cloned._proxy.ptr, _ptr(mac), maclen)
|
||||
if rc != 0:
|
||||
raise ValueError("mac verification failed")
|
||||
|
||||
|
||||
class Encryptor:
|
||||
"""Incremental encryptor.
|
||||
|
||||
- update(message[, into]) -> returns produced ciphertext bytes
|
||||
- final([into]) -> returns MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental encryptor.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
ad: Associated data to bind to the encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis128l_state", ALIGNMENT)
|
||||
_lib.aegis128l_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(
|
||||
self, message: Buffer, into: Buffer | None = None
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt a chunk of the message.
|
||||
|
||||
Args:
|
||||
message: Plaintext bytes to encrypt.
|
||||
into: Optional destination buffer; must be >= len(message).
|
||||
|
||||
Returns:
|
||||
The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = message.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be >= expected output size for this update"
|
||||
)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128l_state_encrypt_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(
|
||||
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||
)
|
||||
w = int(written[0])
|
||||
assert w == expected_out
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Finalize encryption and return the authentication tag.
|
||||
|
||||
Args:
|
||||
into: Optional destination buffer for the tag.
|
||||
|
||||
Returns:
|
||||
The authentication tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the C final call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
# Only the authentication tag is produced here; allocate exactly maclen
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
out = into if into is not None else bytearray(maclen)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128l_state_encrypt_final(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
written,
|
||||
maclen,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||
w = int(written[0])
|
||||
if into is None:
|
||||
# Only the tag bytes are returned when we allocate the buffer
|
||||
assert w == maclen
|
||||
self._state = None
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
|
||||
class Decryptor:
|
||||
"""Incremental decryptor.
|
||||
|
||||
- update(ciphertext[, into]) -> returns plaintext bytes
|
||||
- final(mac) -> verifies the MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental decryptor for detached tags.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ad: Associated data used during encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis128l_state", ALIGNMENT)
|
||||
_lib.aegis128l_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Process a chunk of ciphertext.
|
||||
|
||||
Args:
|
||||
ct: Ciphertext bytes (without MAC).
|
||||
into: Optional destination buffer; must be >= len(ciphertext).
|
||||
|
||||
Returns:
|
||||
A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = ct.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError("into length must be >= required capacity for this update")
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128l_state_decrypt_detached_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
"""Finalize decryption by verifying the MAC tag.
|
||||
|
||||
Args:
|
||||
mac: Tag to verify.
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length doesn't match the expected maclen.
|
||||
ValueError: If authentication fails.
|
||||
RuntimeError: If called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
mac = memoryview(mac)
|
||||
if mac.nbytes != maclen:
|
||||
raise TypeError(f"mac length must be {maclen}")
|
||||
rc = _lib.aegis128l_state_decrypt_detached_final(
|
||||
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
self._state = None
|
||||
|
||||
|
||||
def new_state():
|
||||
"""Allocate and return a new aegis128l_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis128l_state", ALIGNMENT)
|
||||
|
||||
|
||||
def new_mac_state():
|
||||
"""Allocate and return a new aegis128l_mac_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis128l_mac_state", ALIGNMENT)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
"NAME",
|
||||
"KEYBYTES",
|
||||
"NONCEBYTES",
|
||||
"MACBYTES",
|
||||
"MACBYTES_LONG",
|
||||
"ALIGNMENT",
|
||||
"RATE",
|
||||
# utility functions
|
||||
"random_key",
|
||||
"random_nonce",
|
||||
"nonce_increment",
|
||||
"wipe",
|
||||
# one-shot functions
|
||||
"encrypt_detached",
|
||||
"decrypt_detached",
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"stream",
|
||||
"encrypt_unauthenticated",
|
||||
"decrypt_unauthenticated",
|
||||
"mac",
|
||||
# incremental classes
|
||||
"Encryptor",
|
||||
"Decryptor",
|
||||
"Mac",
|
||||
]
|
||||
@@ -0,0 +1,921 @@
|
||||
"""AEGIS-128X2"""
|
||||
# All modules are generated from aegis256x4.py by tools/generate.py!
|
||||
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
|
||||
|
||||
import errno
|
||||
import secrets
|
||||
from typing import Literal
|
||||
|
||||
from ._loader import ffi
|
||||
from ._loader import lib as _lib
|
||||
from .util import Buffer, new_aligned_struct, nonce_increment, wipe
|
||||
|
||||
NAME = "AEGIS-128X2" #: Algorithm display name
|
||||
KEYBYTES = 16 #: Key size in bytes (varies by algorithm)
|
||||
NONCEBYTES = 16 #: Nonce size in bytes (varies by algorithm)
|
||||
MACBYTES = 16 #: Normal MAC size (always 16)
|
||||
MACBYTES_LONG = 32 #: Long MAC size (always 32)
|
||||
ALIGNMENT = 64 #: Required alignment for internal structures
|
||||
RATE = 64 #: Byte chunk size in internal processing
|
||||
|
||||
|
||||
def random_key() -> bytearray:
|
||||
"""
|
||||
Generate a secret key using cryptographically secure random bytes.
|
||||
|
||||
It is recommended to wipe() the key after no longer needed.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(KEYBYTES))
|
||||
|
||||
|
||||
def random_nonce() -> bytearray:
|
||||
"""
|
||||
Generate a public nonce using cryptographically secure random bytes.
|
||||
|
||||
Nonces (a number used once) are public data that may be sent together
|
||||
with the ciphertext, but they need to be unique for each use.
|
||||
|
||||
See also: nonce_increment() can be used to derive sequential nonces.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(NONCEBYTES))
|
||||
|
||||
|
||||
def _ptr(buf):
|
||||
return ffi.NULL if buf is None else ffi.from_buffer(buf)
|
||||
|
||||
|
||||
def encrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
ct_into: Buffer | None = None,
|
||||
mac_into: Buffer | None = None,
|
||||
) -> tuple[bytearray | memoryview, bytearray | memoryview]:
|
||||
"""Encrypt message with associated data, returning ciphertext and MAC separately.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
ct_into: Buffer to write ciphertext into (default: bytearray created).
|
||||
mac_into: Buffer to write MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Tuple of (ciphertext, mac)
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if ct_into is not None:
|
||||
ct_into = memoryview(ct_into)
|
||||
if mac_into is not None:
|
||||
mac_into = memoryview(mac_into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
if ct_into is None:
|
||||
c = bytearray(message.nbytes)
|
||||
else:
|
||||
if ct_into.nbytes < message.nbytes:
|
||||
raise TypeError("ct_into length must be at least message.nbytes")
|
||||
c = ct_into
|
||||
if mac_into is None:
|
||||
mac = bytearray(maclen)
|
||||
else:
|
||||
if mac_into.nbytes < maclen:
|
||||
raise TypeError("mac_into length must be at least maclen")
|
||||
mac = mac_into
|
||||
|
||||
rc = _lib.aegis128x2_encrypt_detached(
|
||||
ffi.from_buffer(c),
|
||||
ffi.from_buffer(mac),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt detached failed: {err_name}")
|
||||
return (
|
||||
c if ct_into is None else memoryview(c)[: message.nbytes],
|
||||
mac if mac_into is None else memoryview(mac)[:maclen],
|
||||
) # type: ignore
|
||||
|
||||
|
||||
def decrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
mac: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with detached MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
mac: The MAC to verify.
|
||||
ad: Associated data (optional).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
mac = memoryview(mac)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x2_decrypt_detached(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(mac),
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message with associated data, returning ciphertext with appended MAC.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write ciphertext+MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes + maclen)
|
||||
else:
|
||||
if into.nbytes < message.nbytes + maclen:
|
||||
raise TypeError("into length must be at least message.nbytes + maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x2_encrypt(
|
||||
ffi.from_buffer(out),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt failed: {err_name}")
|
||||
return out if into is None else memoryview(out)[: message.nbytes + maclen] # type: ignore
|
||||
|
||||
|
||||
def decrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with appended MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext with MAC to decrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if ct.nbytes < maclen:
|
||||
raise TypeError("ciphertext too short for tag")
|
||||
expected_out = ct.nbytes - maclen
|
||||
if into is None:
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x2_decrypt(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
||||
|
||||
|
||||
def stream(
|
||||
key: Buffer,
|
||||
nonce: Buffer | None,
|
||||
length: int | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | Buffer:
|
||||
"""Generate a stream of pseudorandom bytes.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce(), uses zeroes for nonce if None).
|
||||
length: Number of bytes to generate (required if into is None).
|
||||
into: Buffer to write stream into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Pseudorandom bytes as bytearray, or into returned directly.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid or neither length nor into provided.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
if nonce is not None:
|
||||
nonce = memoryview(nonce)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce is not None and nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
if length is None:
|
||||
raise TypeError("provide either into or length")
|
||||
out = bytearray(length)
|
||||
else:
|
||||
if length is not None and into.nbytes < length:
|
||||
raise TypeError("into length must be at least length")
|
||||
out = into
|
||||
_lib.aegis128x2_stream(
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
into: Buffer to write ciphertext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes)
|
||||
else:
|
||||
if into.nbytes < message.nbytes:
|
||||
raise TypeError("into length must be at least message.nbytes")
|
||||
out = into
|
||||
_lib.aegis128x2_encrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: message.nbytes] # type: ignore
|
||||
|
||||
|
||||
def decrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
_lib.aegis128x2_decrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
# This is missing from C API but convenient to have here
|
||||
def mac(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
data: Buffer,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Compute a MAC for the given data in one shot.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key())
|
||||
nonce: Public nonce (generate with random_nonce())
|
||||
data: Data to MAC
|
||||
maclen: MAC length (16 or 32, default 16)
|
||||
into: Buffer to write MAC into (default: bytearray created)
|
||||
|
||||
Returns:
|
||||
MAC bytes as bytearray if into not provided, memoryview of into otherwise
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
data = memoryview(data)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
mac_state = Mac(key, nonce, maclen)
|
||||
mac_state.update(data)
|
||||
return mac_state.final(into)
|
||||
|
||||
|
||||
class Mac:
|
||||
"""MAC calculation and verification with incremental updates.
|
||||
|
||||
Example:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
...
|
||||
mac = a.final()
|
||||
|
||||
Hashlib compatible interface:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
bytes_mac = a.digest()
|
||||
hex_mac = a.hexdigest()
|
||||
"""
|
||||
|
||||
__slots__ = ("_proxy", "_maclen", "_cached_digest")
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer, maclen: int = MACBYTES) -> None:
|
||||
"""Create a MAC with the given key, nonce, and tag length.
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
self._maclen = maclen
|
||||
self._proxy = new_aligned_struct("aegis128x2_mac_state", ALIGNMENT)
|
||||
_lib.aegis128x2_mac_init(self._proxy.ptr, _ptr(key), _ptr(nonce))
|
||||
self._cached_digest: None | Literal[False] | bytes = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset back to the original state, prior to any updates."""
|
||||
_lib.aegis128x2_mac_reset(self._proxy.ptr)
|
||||
self._cached_digest = None
|
||||
|
||||
def clone(self) -> "Mac":
|
||||
"""Return a clone of current MAC state."""
|
||||
clone = object.__new__(Mac)
|
||||
clone._maclen = self._maclen
|
||||
clone._proxy = new_aligned_struct("aegis128x2_mac_state", ALIGNMENT)
|
||||
_lib.aegis128x2_mac_state_clone(clone._proxy.ptr, self._proxy.ptr)
|
||||
clone._cached_digest = self._cached_digest
|
||||
return clone
|
||||
|
||||
__deepcopy__ = clone
|
||||
|
||||
def update(self, data: Buffer) -> None:
|
||||
"""Update the MAC state with more data.
|
||||
|
||||
Repeated calls to update() are equivalent to a single call with the concatenated data.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError("Cannot update after final()")
|
||||
data = memoryview(data)
|
||||
rc = _lib.aegis128x2_mac_update(self._proxy.ptr, _ptr(data), data.nbytes)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac update failed: {err_name}")
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Calculate and return the MAC tag for the currently input data.
|
||||
|
||||
This method can only be called once. After calling it, the MAC becomes unusable
|
||||
for further updates or calls to final().
|
||||
|
||||
Args:
|
||||
into: Optional buffer to write the tag into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
The tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If finalization fails in the C library or if already finalized.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError(
|
||||
"The MAC can only be calculated once. Use reset() to start over, or clone() before finalizing to continue."
|
||||
)
|
||||
maclen = self._maclen
|
||||
if into is None:
|
||||
out = bytearray(maclen)
|
||||
else:
|
||||
into = memoryview(into)
|
||||
if into.nbytes < maclen:
|
||||
raise TypeError("into length must be at least maclen")
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis128x2_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac final failed: {err_name}")
|
||||
self._cached_digest = False
|
||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
||||
|
||||
def digest(self) -> bytes:
|
||||
"""Calculate and return the MAC tag as bytes.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
Can be called after final() to get the cached digest.
|
||||
"""
|
||||
if self._cached_digest:
|
||||
return self._cached_digest
|
||||
self._cached_digest = bytes(self.final()) # Overrides the False set by final()
|
||||
return self._cached_digest
|
||||
|
||||
def hexdigest(self) -> str:
|
||||
"""Calculate and return the MAC tag as a hex string.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
"""
|
||||
return self.digest().hex()
|
||||
|
||||
def verify(self, mac: Buffer):
|
||||
"""Verify that the data entered so far matches the given MAC tag.
|
||||
|
||||
Unlike the C library, this method does not alter the current state.
|
||||
|
||||
Args:
|
||||
mac: The tag to verify against (16 or 32 bytes).
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length is invalid.
|
||||
ValueError: If verification fails.
|
||||
"""
|
||||
mac = memoryview(mac)
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
|
||||
cloned = self.clone()
|
||||
rc = _lib.aegis128x2_mac_verify(cloned._proxy.ptr, _ptr(mac), maclen)
|
||||
if rc != 0:
|
||||
raise ValueError("mac verification failed")
|
||||
|
||||
|
||||
class Encryptor:
|
||||
"""Incremental encryptor.
|
||||
|
||||
- update(message[, into]) -> returns produced ciphertext bytes
|
||||
- final([into]) -> returns MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental encryptor.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
ad: Associated data to bind to the encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis128x2_state", ALIGNMENT)
|
||||
_lib.aegis128x2_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(
|
||||
self, message: Buffer, into: Buffer | None = None
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt a chunk of the message.
|
||||
|
||||
Args:
|
||||
message: Plaintext bytes to encrypt.
|
||||
into: Optional destination buffer; must be >= len(message).
|
||||
|
||||
Returns:
|
||||
The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = message.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be >= expected output size for this update"
|
||||
)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128x2_state_encrypt_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(
|
||||
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||
)
|
||||
w = int(written[0])
|
||||
assert w == expected_out
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Finalize encryption and return the authentication tag.
|
||||
|
||||
Args:
|
||||
into: Optional destination buffer for the tag.
|
||||
|
||||
Returns:
|
||||
The authentication tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the C final call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
# Only the authentication tag is produced here; allocate exactly maclen
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
out = into if into is not None else bytearray(maclen)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128x2_state_encrypt_final(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
written,
|
||||
maclen,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||
w = int(written[0])
|
||||
if into is None:
|
||||
# Only the tag bytes are returned when we allocate the buffer
|
||||
assert w == maclen
|
||||
self._state = None
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
|
||||
class Decryptor:
|
||||
"""Incremental decryptor.
|
||||
|
||||
- update(ciphertext[, into]) -> returns plaintext bytes
|
||||
- final(mac) -> verifies the MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental decryptor for detached tags.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ad: Associated data used during encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis128x2_state", ALIGNMENT)
|
||||
_lib.aegis128x2_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Process a chunk of ciphertext.
|
||||
|
||||
Args:
|
||||
ct: Ciphertext bytes (without MAC).
|
||||
into: Optional destination buffer; must be >= len(ciphertext).
|
||||
|
||||
Returns:
|
||||
A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = ct.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError("into length must be >= required capacity for this update")
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128x2_state_decrypt_detached_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
"""Finalize decryption by verifying the MAC tag.
|
||||
|
||||
Args:
|
||||
mac: Tag to verify.
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length doesn't match the expected maclen.
|
||||
ValueError: If authentication fails.
|
||||
RuntimeError: If called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
mac = memoryview(mac)
|
||||
if mac.nbytes != maclen:
|
||||
raise TypeError(f"mac length must be {maclen}")
|
||||
rc = _lib.aegis128x2_state_decrypt_detached_final(
|
||||
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
self._state = None
|
||||
|
||||
|
||||
def new_state():
|
||||
"""Allocate and return a new aegis128x2_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis128x2_state", ALIGNMENT)
|
||||
|
||||
|
||||
def new_mac_state():
|
||||
"""Allocate and return a new aegis128x2_mac_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis128x2_mac_state", ALIGNMENT)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
"NAME",
|
||||
"KEYBYTES",
|
||||
"NONCEBYTES",
|
||||
"MACBYTES",
|
||||
"MACBYTES_LONG",
|
||||
"ALIGNMENT",
|
||||
"RATE",
|
||||
# utility functions
|
||||
"random_key",
|
||||
"random_nonce",
|
||||
"nonce_increment",
|
||||
"wipe",
|
||||
# one-shot functions
|
||||
"encrypt_detached",
|
||||
"decrypt_detached",
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"stream",
|
||||
"encrypt_unauthenticated",
|
||||
"decrypt_unauthenticated",
|
||||
"mac",
|
||||
# incremental classes
|
||||
"Encryptor",
|
||||
"Decryptor",
|
||||
"Mac",
|
||||
]
|
||||
@@ -0,0 +1,921 @@
|
||||
"""AEGIS-128X4"""
|
||||
# All modules are generated from aegis256x4.py by tools/generate.py!
|
||||
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
|
||||
|
||||
import errno
|
||||
import secrets
|
||||
from typing import Literal
|
||||
|
||||
from ._loader import ffi
|
||||
from ._loader import lib as _lib
|
||||
from .util import Buffer, new_aligned_struct, nonce_increment, wipe
|
||||
|
||||
NAME = "AEGIS-128X4" #: Algorithm display name
|
||||
KEYBYTES = 16 #: Key size in bytes (varies by algorithm)
|
||||
NONCEBYTES = 16 #: Nonce size in bytes (varies by algorithm)
|
||||
MACBYTES = 16 #: Normal MAC size (always 16)
|
||||
MACBYTES_LONG = 32 #: Long MAC size (always 32)
|
||||
ALIGNMENT = 64 #: Required alignment for internal structures
|
||||
RATE = 64 #: Byte chunk size in internal processing
|
||||
|
||||
|
||||
def random_key() -> bytearray:
|
||||
"""
|
||||
Generate a secret key using cryptographically secure random bytes.
|
||||
|
||||
It is recommended to wipe() the key after no longer needed.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(KEYBYTES))
|
||||
|
||||
|
||||
def random_nonce() -> bytearray:
|
||||
"""
|
||||
Generate a public nonce using cryptographically secure random bytes.
|
||||
|
||||
Nonces (a number used once) are public data that may be sent together
|
||||
with the ciphertext, but they need to be unique for each use.
|
||||
|
||||
See also: nonce_increment() can be used to derive sequential nonces.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(NONCEBYTES))
|
||||
|
||||
|
||||
def _ptr(buf):
|
||||
return ffi.NULL if buf is None else ffi.from_buffer(buf)
|
||||
|
||||
|
||||
def encrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
ct_into: Buffer | None = None,
|
||||
mac_into: Buffer | None = None,
|
||||
) -> tuple[bytearray | memoryview, bytearray | memoryview]:
|
||||
"""Encrypt message with associated data, returning ciphertext and MAC separately.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
ct_into: Buffer to write ciphertext into (default: bytearray created).
|
||||
mac_into: Buffer to write MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Tuple of (ciphertext, mac)
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if ct_into is not None:
|
||||
ct_into = memoryview(ct_into)
|
||||
if mac_into is not None:
|
||||
mac_into = memoryview(mac_into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
if ct_into is None:
|
||||
c = bytearray(message.nbytes)
|
||||
else:
|
||||
if ct_into.nbytes < message.nbytes:
|
||||
raise TypeError("ct_into length must be at least message.nbytes")
|
||||
c = ct_into
|
||||
if mac_into is None:
|
||||
mac = bytearray(maclen)
|
||||
else:
|
||||
if mac_into.nbytes < maclen:
|
||||
raise TypeError("mac_into length must be at least maclen")
|
||||
mac = mac_into
|
||||
|
||||
rc = _lib.aegis128x4_encrypt_detached(
|
||||
ffi.from_buffer(c),
|
||||
ffi.from_buffer(mac),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt detached failed: {err_name}")
|
||||
return (
|
||||
c if ct_into is None else memoryview(c)[: message.nbytes],
|
||||
mac if mac_into is None else memoryview(mac)[:maclen],
|
||||
) # type: ignore
|
||||
|
||||
|
||||
def decrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
mac: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with detached MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
mac: The MAC to verify.
|
||||
ad: Associated data (optional).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
mac = memoryview(mac)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x4_decrypt_detached(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(mac),
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message with associated data, returning ciphertext with appended MAC.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write ciphertext+MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes + maclen)
|
||||
else:
|
||||
if into.nbytes < message.nbytes + maclen:
|
||||
raise TypeError("into length must be at least message.nbytes + maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x4_encrypt(
|
||||
ffi.from_buffer(out),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt failed: {err_name}")
|
||||
return out if into is None else memoryview(out)[: message.nbytes + maclen] # type: ignore
|
||||
|
||||
|
||||
def decrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with appended MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext with MAC to decrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if ct.nbytes < maclen:
|
||||
raise TypeError("ciphertext too short for tag")
|
||||
expected_out = ct.nbytes - maclen
|
||||
if into is None:
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x4_decrypt(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
||||
|
||||
|
||||
def stream(
|
||||
key: Buffer,
|
||||
nonce: Buffer | None,
|
||||
length: int | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | Buffer:
|
||||
"""Generate a stream of pseudorandom bytes.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce(), uses zeroes for nonce if None).
|
||||
length: Number of bytes to generate (required if into is None).
|
||||
into: Buffer to write stream into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Pseudorandom bytes as bytearray, or into returned directly.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid or neither length nor into provided.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
if nonce is not None:
|
||||
nonce = memoryview(nonce)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce is not None and nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
if length is None:
|
||||
raise TypeError("provide either into or length")
|
||||
out = bytearray(length)
|
||||
else:
|
||||
if length is not None and into.nbytes < length:
|
||||
raise TypeError("into length must be at least length")
|
||||
out = into
|
||||
_lib.aegis128x4_stream(
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
into: Buffer to write ciphertext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes)
|
||||
else:
|
||||
if into.nbytes < message.nbytes:
|
||||
raise TypeError("into length must be at least message.nbytes")
|
||||
out = into
|
||||
_lib.aegis128x4_encrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: message.nbytes] # type: ignore
|
||||
|
||||
|
||||
def decrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
_lib.aegis128x4_decrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
# This is missing from C API but convenient to have here
|
||||
def mac(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
data: Buffer,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Compute a MAC for the given data in one shot.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key())
|
||||
nonce: Public nonce (generate with random_nonce())
|
||||
data: Data to MAC
|
||||
maclen: MAC length (16 or 32, default 16)
|
||||
into: Buffer to write MAC into (default: bytearray created)
|
||||
|
||||
Returns:
|
||||
MAC bytes as bytearray if into not provided, memoryview of into otherwise
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
data = memoryview(data)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
mac_state = Mac(key, nonce, maclen)
|
||||
mac_state.update(data)
|
||||
return mac_state.final(into)
|
||||
|
||||
|
||||
class Mac:
|
||||
"""MAC calculation and verification with incremental updates.
|
||||
|
||||
Example:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
...
|
||||
mac = a.final()
|
||||
|
||||
Hashlib compatible interface:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
bytes_mac = a.digest()
|
||||
hex_mac = a.hexdigest()
|
||||
"""
|
||||
|
||||
__slots__ = ("_proxy", "_maclen", "_cached_digest")
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer, maclen: int = MACBYTES) -> None:
|
||||
"""Create a MAC with the given key, nonce, and tag length.
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
self._maclen = maclen
|
||||
self._proxy = new_aligned_struct("aegis128x4_mac_state", ALIGNMENT)
|
||||
_lib.aegis128x4_mac_init(self._proxy.ptr, _ptr(key), _ptr(nonce))
|
||||
self._cached_digest: None | Literal[False] | bytes = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset back to the original state, prior to any updates."""
|
||||
_lib.aegis128x4_mac_reset(self._proxy.ptr)
|
||||
self._cached_digest = None
|
||||
|
||||
def clone(self) -> "Mac":
|
||||
"""Return a clone of current MAC state."""
|
||||
clone = object.__new__(Mac)
|
||||
clone._maclen = self._maclen
|
||||
clone._proxy = new_aligned_struct("aegis128x4_mac_state", ALIGNMENT)
|
||||
_lib.aegis128x4_mac_state_clone(clone._proxy.ptr, self._proxy.ptr)
|
||||
clone._cached_digest = self._cached_digest
|
||||
return clone
|
||||
|
||||
__deepcopy__ = clone
|
||||
|
||||
def update(self, data: Buffer) -> None:
|
||||
"""Update the MAC state with more data.
|
||||
|
||||
Repeated calls to update() are equivalent to a single call with the concatenated data.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError("Cannot update after final()")
|
||||
data = memoryview(data)
|
||||
rc = _lib.aegis128x4_mac_update(self._proxy.ptr, _ptr(data), data.nbytes)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac update failed: {err_name}")
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Calculate and return the MAC tag for the currently input data.
|
||||
|
||||
This method can only be called once. After calling it, the MAC becomes unusable
|
||||
for further updates or calls to final().
|
||||
|
||||
Args:
|
||||
into: Optional buffer to write the tag into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
The tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If finalization fails in the C library or if already finalized.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError(
|
||||
"The MAC can only be calculated once. Use reset() to start over, or clone() before finalizing to continue."
|
||||
)
|
||||
maclen = self._maclen
|
||||
if into is None:
|
||||
out = bytearray(maclen)
|
||||
else:
|
||||
into = memoryview(into)
|
||||
if into.nbytes < maclen:
|
||||
raise TypeError("into length must be at least maclen")
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis128x4_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac final failed: {err_name}")
|
||||
self._cached_digest = False
|
||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
||||
|
||||
def digest(self) -> bytes:
|
||||
"""Calculate and return the MAC tag as bytes.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
Can be called after final() to get the cached digest.
|
||||
"""
|
||||
if self._cached_digest:
|
||||
return self._cached_digest
|
||||
self._cached_digest = bytes(self.final()) # Overrides the False set by final()
|
||||
return self._cached_digest
|
||||
|
||||
def hexdigest(self) -> str:
|
||||
"""Calculate and return the MAC tag as a hex string.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
"""
|
||||
return self.digest().hex()
|
||||
|
||||
def verify(self, mac: Buffer):
|
||||
"""Verify that the data entered so far matches the given MAC tag.
|
||||
|
||||
Unlike the C library, this method does not alter the current state.
|
||||
|
||||
Args:
|
||||
mac: The tag to verify against (16 or 32 bytes).
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length is invalid.
|
||||
ValueError: If verification fails.
|
||||
"""
|
||||
mac = memoryview(mac)
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
|
||||
cloned = self.clone()
|
||||
rc = _lib.aegis128x4_mac_verify(cloned._proxy.ptr, _ptr(mac), maclen)
|
||||
if rc != 0:
|
||||
raise ValueError("mac verification failed")
|
||||
|
||||
|
||||
class Encryptor:
|
||||
"""Incremental encryptor.
|
||||
|
||||
- update(message[, into]) -> returns produced ciphertext bytes
|
||||
- final([into]) -> returns MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental encryptor.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
ad: Associated data to bind to the encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis128x4_state", ALIGNMENT)
|
||||
_lib.aegis128x4_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(
|
||||
self, message: Buffer, into: Buffer | None = None
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt a chunk of the message.
|
||||
|
||||
Args:
|
||||
message: Plaintext bytes to encrypt.
|
||||
into: Optional destination buffer; must be >= len(message).
|
||||
|
||||
Returns:
|
||||
The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = message.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be >= expected output size for this update"
|
||||
)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128x4_state_encrypt_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(
|
||||
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||
)
|
||||
w = int(written[0])
|
||||
assert w == expected_out
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Finalize encryption and return the authentication tag.
|
||||
|
||||
Args:
|
||||
into: Optional destination buffer for the tag.
|
||||
|
||||
Returns:
|
||||
The authentication tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the C final call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
# Only the authentication tag is produced here; allocate exactly maclen
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
out = into if into is not None else bytearray(maclen)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128x4_state_encrypt_final(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
written,
|
||||
maclen,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||
w = int(written[0])
|
||||
if into is None:
|
||||
# Only the tag bytes are returned when we allocate the buffer
|
||||
assert w == maclen
|
||||
self._state = None
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
|
||||
class Decryptor:
|
||||
"""Incremental decryptor.
|
||||
|
||||
- update(ciphertext[, into]) -> returns plaintext bytes
|
||||
- final(mac) -> verifies the MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental decryptor for detached tags.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ad: Associated data used during encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis128x4_state", ALIGNMENT)
|
||||
_lib.aegis128x4_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Process a chunk of ciphertext.
|
||||
|
||||
Args:
|
||||
ct: Ciphertext bytes (without MAC).
|
||||
into: Optional destination buffer; must be >= len(ciphertext).
|
||||
|
||||
Returns:
|
||||
A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = ct.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError("into length must be >= required capacity for this update")
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis128x4_state_decrypt_detached_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
"""Finalize decryption by verifying the MAC tag.
|
||||
|
||||
Args:
|
||||
mac: Tag to verify.
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length doesn't match the expected maclen.
|
||||
ValueError: If authentication fails.
|
||||
RuntimeError: If called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
mac = memoryview(mac)
|
||||
if mac.nbytes != maclen:
|
||||
raise TypeError(f"mac length must be {maclen}")
|
||||
rc = _lib.aegis128x4_state_decrypt_detached_final(
|
||||
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
self._state = None
|
||||
|
||||
|
||||
def new_state():
|
||||
"""Allocate and return a new aegis128x4_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis128x4_state", ALIGNMENT)
|
||||
|
||||
|
||||
def new_mac_state():
|
||||
"""Allocate and return a new aegis128x4_mac_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis128x4_mac_state", ALIGNMENT)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
"NAME",
|
||||
"KEYBYTES",
|
||||
"NONCEBYTES",
|
||||
"MACBYTES",
|
||||
"MACBYTES_LONG",
|
||||
"ALIGNMENT",
|
||||
"RATE",
|
||||
# utility functions
|
||||
"random_key",
|
||||
"random_nonce",
|
||||
"nonce_increment",
|
||||
"wipe",
|
||||
# one-shot functions
|
||||
"encrypt_detached",
|
||||
"decrypt_detached",
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"stream",
|
||||
"encrypt_unauthenticated",
|
||||
"decrypt_unauthenticated",
|
||||
"mac",
|
||||
# incremental classes
|
||||
"Encryptor",
|
||||
"Decryptor",
|
||||
"Mac",
|
||||
]
|
||||
@@ -0,0 +1,921 @@
|
||||
"""AEGIS-256"""
|
||||
# All modules are generated from aegis256x4.py by tools/generate.py!
|
||||
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
|
||||
|
||||
import errno
|
||||
import secrets
|
||||
from typing import Literal
|
||||
|
||||
from ._loader import ffi
|
||||
from ._loader import lib as _lib
|
||||
from .util import Buffer, new_aligned_struct, nonce_increment, wipe
|
||||
|
||||
NAME = "AEGIS-256" #: Algorithm display name
|
||||
KEYBYTES = 32 #: Key size in bytes (varies by algorithm)
|
||||
NONCEBYTES = 32 #: Nonce size in bytes (varies by algorithm)
|
||||
MACBYTES = 16 #: Normal MAC size (always 16)
|
||||
MACBYTES_LONG = 32 #: Long MAC size (always 32)
|
||||
ALIGNMENT = 64 #: Required alignment for internal structures
|
||||
RATE = 64 #: Byte chunk size in internal processing
|
||||
|
||||
|
||||
def random_key() -> bytearray:
|
||||
"""
|
||||
Generate a secret key using cryptographically secure random bytes.
|
||||
|
||||
It is recommended to wipe() the key after no longer needed.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(KEYBYTES))
|
||||
|
||||
|
||||
def random_nonce() -> bytearray:
|
||||
"""
|
||||
Generate a public nonce using cryptographically secure random bytes.
|
||||
|
||||
Nonces (a number used once) are public data that may be sent together
|
||||
with the ciphertext, but they need to be unique for each use.
|
||||
|
||||
See also: nonce_increment() can be used to derive sequential nonces.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(NONCEBYTES))
|
||||
|
||||
|
||||
def _ptr(buf):
|
||||
return ffi.NULL if buf is None else ffi.from_buffer(buf)
|
||||
|
||||
|
||||
def encrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
ct_into: Buffer | None = None,
|
||||
mac_into: Buffer | None = None,
|
||||
) -> tuple[bytearray | memoryview, bytearray | memoryview]:
|
||||
"""Encrypt message with associated data, returning ciphertext and MAC separately.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
ct_into: Buffer to write ciphertext into (default: bytearray created).
|
||||
mac_into: Buffer to write MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Tuple of (ciphertext, mac)
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if ct_into is not None:
|
||||
ct_into = memoryview(ct_into)
|
||||
if mac_into is not None:
|
||||
mac_into = memoryview(mac_into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
if ct_into is None:
|
||||
c = bytearray(message.nbytes)
|
||||
else:
|
||||
if ct_into.nbytes < message.nbytes:
|
||||
raise TypeError("ct_into length must be at least message.nbytes")
|
||||
c = ct_into
|
||||
if mac_into is None:
|
||||
mac = bytearray(maclen)
|
||||
else:
|
||||
if mac_into.nbytes < maclen:
|
||||
raise TypeError("mac_into length must be at least maclen")
|
||||
mac = mac_into
|
||||
|
||||
rc = _lib.aegis256_encrypt_detached(
|
||||
ffi.from_buffer(c),
|
||||
ffi.from_buffer(mac),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt detached failed: {err_name}")
|
||||
return (
|
||||
c if ct_into is None else memoryview(c)[: message.nbytes],
|
||||
mac if mac_into is None else memoryview(mac)[:maclen],
|
||||
) # type: ignore
|
||||
|
||||
|
||||
def decrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
mac: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with detached MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
mac: The MAC to verify.
|
||||
ad: Associated data (optional).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
mac = memoryview(mac)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256_decrypt_detached(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(mac),
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message with associated data, returning ciphertext with appended MAC.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write ciphertext+MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes + maclen)
|
||||
else:
|
||||
if into.nbytes < message.nbytes + maclen:
|
||||
raise TypeError("into length must be at least message.nbytes + maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256_encrypt(
|
||||
ffi.from_buffer(out),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt failed: {err_name}")
|
||||
return out if into is None else memoryview(out)[: message.nbytes + maclen] # type: ignore
|
||||
|
||||
|
||||
def decrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with appended MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext with MAC to decrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if ct.nbytes < maclen:
|
||||
raise TypeError("ciphertext too short for tag")
|
||||
expected_out = ct.nbytes - maclen
|
||||
if into is None:
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256_decrypt(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
||||
|
||||
|
||||
def stream(
|
||||
key: Buffer,
|
||||
nonce: Buffer | None,
|
||||
length: int | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | Buffer:
|
||||
"""Generate a stream of pseudorandom bytes.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce(), uses zeroes for nonce if None).
|
||||
length: Number of bytes to generate (required if into is None).
|
||||
into: Buffer to write stream into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Pseudorandom bytes as bytearray, or into returned directly.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid or neither length nor into provided.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
if nonce is not None:
|
||||
nonce = memoryview(nonce)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce is not None and nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
if length is None:
|
||||
raise TypeError("provide either into or length")
|
||||
out = bytearray(length)
|
||||
else:
|
||||
if length is not None and into.nbytes < length:
|
||||
raise TypeError("into length must be at least length")
|
||||
out = into
|
||||
_lib.aegis256_stream(
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
into: Buffer to write ciphertext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes)
|
||||
else:
|
||||
if into.nbytes < message.nbytes:
|
||||
raise TypeError("into length must be at least message.nbytes")
|
||||
out = into
|
||||
_lib.aegis256_encrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: message.nbytes] # type: ignore
|
||||
|
||||
|
||||
def decrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
_lib.aegis256_decrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
# This is missing from C API but convenient to have here
|
||||
def mac(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
data: Buffer,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Compute a MAC for the given data in one shot.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key())
|
||||
nonce: Public nonce (generate with random_nonce())
|
||||
data: Data to MAC
|
||||
maclen: MAC length (16 or 32, default 16)
|
||||
into: Buffer to write MAC into (default: bytearray created)
|
||||
|
||||
Returns:
|
||||
MAC bytes as bytearray if into not provided, memoryview of into otherwise
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
data = memoryview(data)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
mac_state = Mac(key, nonce, maclen)
|
||||
mac_state.update(data)
|
||||
return mac_state.final(into)
|
||||
|
||||
|
||||
class Mac:
|
||||
"""MAC calculation and verification with incremental updates.
|
||||
|
||||
Example:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
...
|
||||
mac = a.final()
|
||||
|
||||
Hashlib compatible interface:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
bytes_mac = a.digest()
|
||||
hex_mac = a.hexdigest()
|
||||
"""
|
||||
|
||||
__slots__ = ("_proxy", "_maclen", "_cached_digest")
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer, maclen: int = MACBYTES) -> None:
|
||||
"""Create a MAC with the given key, nonce, and tag length.
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
self._maclen = maclen
|
||||
self._proxy = new_aligned_struct("aegis256_mac_state", ALIGNMENT)
|
||||
_lib.aegis256_mac_init(self._proxy.ptr, _ptr(key), _ptr(nonce))
|
||||
self._cached_digest: None | Literal[False] | bytes = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset back to the original state, prior to any updates."""
|
||||
_lib.aegis256_mac_reset(self._proxy.ptr)
|
||||
self._cached_digest = None
|
||||
|
||||
def clone(self) -> "Mac":
|
||||
"""Return a clone of current MAC state."""
|
||||
clone = object.__new__(Mac)
|
||||
clone._maclen = self._maclen
|
||||
clone._proxy = new_aligned_struct("aegis256_mac_state", ALIGNMENT)
|
||||
_lib.aegis256_mac_state_clone(clone._proxy.ptr, self._proxy.ptr)
|
||||
clone._cached_digest = self._cached_digest
|
||||
return clone
|
||||
|
||||
__deepcopy__ = clone
|
||||
|
||||
def update(self, data: Buffer) -> None:
|
||||
"""Update the MAC state with more data.
|
||||
|
||||
Repeated calls to update() are equivalent to a single call with the concatenated data.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError("Cannot update after final()")
|
||||
data = memoryview(data)
|
||||
rc = _lib.aegis256_mac_update(self._proxy.ptr, _ptr(data), data.nbytes)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac update failed: {err_name}")
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Calculate and return the MAC tag for the currently input data.
|
||||
|
||||
This method can only be called once. After calling it, the MAC becomes unusable
|
||||
for further updates or calls to final().
|
||||
|
||||
Args:
|
||||
into: Optional buffer to write the tag into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
The tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If finalization fails in the C library or if already finalized.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError(
|
||||
"The MAC can only be calculated once. Use reset() to start over, or clone() before finalizing to continue."
|
||||
)
|
||||
maclen = self._maclen
|
||||
if into is None:
|
||||
out = bytearray(maclen)
|
||||
else:
|
||||
into = memoryview(into)
|
||||
if into.nbytes < maclen:
|
||||
raise TypeError("into length must be at least maclen")
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis256_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac final failed: {err_name}")
|
||||
self._cached_digest = False
|
||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
||||
|
||||
def digest(self) -> bytes:
|
||||
"""Calculate and return the MAC tag as bytes.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
Can be called after final() to get the cached digest.
|
||||
"""
|
||||
if self._cached_digest:
|
||||
return self._cached_digest
|
||||
self._cached_digest = bytes(self.final()) # Overrides the False set by final()
|
||||
return self._cached_digest
|
||||
|
||||
def hexdigest(self) -> str:
|
||||
"""Calculate and return the MAC tag as a hex string.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
"""
|
||||
return self.digest().hex()
|
||||
|
||||
def verify(self, mac: Buffer):
|
||||
"""Verify that the data entered so far matches the given MAC tag.
|
||||
|
||||
Unlike the C library, this method does not alter the current state.
|
||||
|
||||
Args:
|
||||
mac: The tag to verify against (16 or 32 bytes).
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length is invalid.
|
||||
ValueError: If verification fails.
|
||||
"""
|
||||
mac = memoryview(mac)
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
|
||||
cloned = self.clone()
|
||||
rc = _lib.aegis256_mac_verify(cloned._proxy.ptr, _ptr(mac), maclen)
|
||||
if rc != 0:
|
||||
raise ValueError("mac verification failed")
|
||||
|
||||
|
||||
class Encryptor:
|
||||
"""Incremental encryptor.
|
||||
|
||||
- update(message[, into]) -> returns produced ciphertext bytes
|
||||
- final([into]) -> returns MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental encryptor.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
ad: Associated data to bind to the encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis256_state", ALIGNMENT)
|
||||
_lib.aegis256_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(
|
||||
self, message: Buffer, into: Buffer | None = None
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt a chunk of the message.
|
||||
|
||||
Args:
|
||||
message: Plaintext bytes to encrypt.
|
||||
into: Optional destination buffer; must be >= len(message).
|
||||
|
||||
Returns:
|
||||
The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = message.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be >= expected output size for this update"
|
||||
)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256_state_encrypt_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(
|
||||
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||
)
|
||||
w = int(written[0])
|
||||
assert w == expected_out
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Finalize encryption and return the authentication tag.
|
||||
|
||||
Args:
|
||||
into: Optional destination buffer for the tag.
|
||||
|
||||
Returns:
|
||||
The authentication tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the C final call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
# Only the authentication tag is produced here; allocate exactly maclen
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
out = into if into is not None else bytearray(maclen)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256_state_encrypt_final(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
written,
|
||||
maclen,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||
w = int(written[0])
|
||||
if into is None:
|
||||
# Only the tag bytes are returned when we allocate the buffer
|
||||
assert w == maclen
|
||||
self._state = None
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
|
||||
class Decryptor:
|
||||
"""Incremental decryptor.
|
||||
|
||||
- update(ciphertext[, into]) -> returns plaintext bytes
|
||||
- final(mac) -> verifies the MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental decryptor for detached tags.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ad: Associated data used during encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis256_state", ALIGNMENT)
|
||||
_lib.aegis256_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Process a chunk of ciphertext.
|
||||
|
||||
Args:
|
||||
ct: Ciphertext bytes (without MAC).
|
||||
into: Optional destination buffer; must be >= len(ciphertext).
|
||||
|
||||
Returns:
|
||||
A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = ct.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError("into length must be >= required capacity for this update")
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256_state_decrypt_detached_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
"""Finalize decryption by verifying the MAC tag.
|
||||
|
||||
Args:
|
||||
mac: Tag to verify.
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length doesn't match the expected maclen.
|
||||
ValueError: If authentication fails.
|
||||
RuntimeError: If called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
mac = memoryview(mac)
|
||||
if mac.nbytes != maclen:
|
||||
raise TypeError(f"mac length must be {maclen}")
|
||||
rc = _lib.aegis256_state_decrypt_detached_final(
|
||||
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
self._state = None
|
||||
|
||||
|
||||
def new_state():
|
||||
"""Allocate and return a new aegis256_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis256_state", ALIGNMENT)
|
||||
|
||||
|
||||
def new_mac_state():
|
||||
"""Allocate and return a new aegis256_mac_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis256_mac_state", ALIGNMENT)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
"NAME",
|
||||
"KEYBYTES",
|
||||
"NONCEBYTES",
|
||||
"MACBYTES",
|
||||
"MACBYTES_LONG",
|
||||
"ALIGNMENT",
|
||||
"RATE",
|
||||
# utility functions
|
||||
"random_key",
|
||||
"random_nonce",
|
||||
"nonce_increment",
|
||||
"wipe",
|
||||
# one-shot functions
|
||||
"encrypt_detached",
|
||||
"decrypt_detached",
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"stream",
|
||||
"encrypt_unauthenticated",
|
||||
"decrypt_unauthenticated",
|
||||
"mac",
|
||||
# incremental classes
|
||||
"Encryptor",
|
||||
"Decryptor",
|
||||
"Mac",
|
||||
]
|
||||
@@ -0,0 +1,921 @@
|
||||
"""AEGIS-256X2"""
|
||||
# All modules are generated from aegis256x4.py by tools/generate.py!
|
||||
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
|
||||
|
||||
import errno
|
||||
import secrets
|
||||
from typing import Literal
|
||||
|
||||
from ._loader import ffi
|
||||
from ._loader import lib as _lib
|
||||
from .util import Buffer, new_aligned_struct, nonce_increment, wipe
|
||||
|
||||
NAME = "AEGIS-256X2" #: Algorithm display name
|
||||
KEYBYTES = 32 #: Key size in bytes (varies by algorithm)
|
||||
NONCEBYTES = 32 #: Nonce size in bytes (varies by algorithm)
|
||||
MACBYTES = 16 #: Normal MAC size (always 16)
|
||||
MACBYTES_LONG = 32 #: Long MAC size (always 32)
|
||||
ALIGNMENT = 64 #: Required alignment for internal structures
|
||||
RATE = 64 #: Byte chunk size in internal processing
|
||||
|
||||
|
||||
def random_key() -> bytearray:
|
||||
"""
|
||||
Generate a secret key using cryptographically secure random bytes.
|
||||
|
||||
It is recommended to wipe() the key after no longer needed.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(KEYBYTES))
|
||||
|
||||
|
||||
def random_nonce() -> bytearray:
|
||||
"""
|
||||
Generate a public nonce using cryptographically secure random bytes.
|
||||
|
||||
Nonces (a number used once) are public data that may be sent together
|
||||
with the ciphertext, but they need to be unique for each use.
|
||||
|
||||
See also: nonce_increment() can be used to derive sequential nonces.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(NONCEBYTES))
|
||||
|
||||
|
||||
def _ptr(buf):
|
||||
return ffi.NULL if buf is None else ffi.from_buffer(buf)
|
||||
|
||||
|
||||
def encrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
ct_into: Buffer | None = None,
|
||||
mac_into: Buffer | None = None,
|
||||
) -> tuple[bytearray | memoryview, bytearray | memoryview]:
|
||||
"""Encrypt message with associated data, returning ciphertext and MAC separately.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
ct_into: Buffer to write ciphertext into (default: bytearray created).
|
||||
mac_into: Buffer to write MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Tuple of (ciphertext, mac)
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if ct_into is not None:
|
||||
ct_into = memoryview(ct_into)
|
||||
if mac_into is not None:
|
||||
mac_into = memoryview(mac_into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
if ct_into is None:
|
||||
c = bytearray(message.nbytes)
|
||||
else:
|
||||
if ct_into.nbytes < message.nbytes:
|
||||
raise TypeError("ct_into length must be at least message.nbytes")
|
||||
c = ct_into
|
||||
if mac_into is None:
|
||||
mac = bytearray(maclen)
|
||||
else:
|
||||
if mac_into.nbytes < maclen:
|
||||
raise TypeError("mac_into length must be at least maclen")
|
||||
mac = mac_into
|
||||
|
||||
rc = _lib.aegis256x2_encrypt_detached(
|
||||
ffi.from_buffer(c),
|
||||
ffi.from_buffer(mac),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt detached failed: {err_name}")
|
||||
return (
|
||||
c if ct_into is None else memoryview(c)[: message.nbytes],
|
||||
mac if mac_into is None else memoryview(mac)[:maclen],
|
||||
) # type: ignore
|
||||
|
||||
|
||||
def decrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
mac: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with detached MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
mac: The MAC to verify.
|
||||
ad: Associated data (optional).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
mac = memoryview(mac)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256x2_decrypt_detached(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(mac),
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message with associated data, returning ciphertext with appended MAC.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write ciphertext+MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes + maclen)
|
||||
else:
|
||||
if into.nbytes < message.nbytes + maclen:
|
||||
raise TypeError("into length must be at least message.nbytes + maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256x2_encrypt(
|
||||
ffi.from_buffer(out),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt failed: {err_name}")
|
||||
return out if into is None else memoryview(out)[: message.nbytes + maclen] # type: ignore
|
||||
|
||||
|
||||
def decrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with appended MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext with MAC to decrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if ct.nbytes < maclen:
|
||||
raise TypeError("ciphertext too short for tag")
|
||||
expected_out = ct.nbytes - maclen
|
||||
if into is None:
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256x2_decrypt(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
||||
|
||||
|
||||
def stream(
|
||||
key: Buffer,
|
||||
nonce: Buffer | None,
|
||||
length: int | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | Buffer:
|
||||
"""Generate a stream of pseudorandom bytes.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce(), uses zeroes for nonce if None).
|
||||
length: Number of bytes to generate (required if into is None).
|
||||
into: Buffer to write stream into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Pseudorandom bytes as bytearray, or into returned directly.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid or neither length nor into provided.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
if nonce is not None:
|
||||
nonce = memoryview(nonce)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce is not None and nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
if length is None:
|
||||
raise TypeError("provide either into or length")
|
||||
out = bytearray(length)
|
||||
else:
|
||||
if length is not None and into.nbytes < length:
|
||||
raise TypeError("into length must be at least length")
|
||||
out = into
|
||||
_lib.aegis256x2_stream(
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
into: Buffer to write ciphertext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes)
|
||||
else:
|
||||
if into.nbytes < message.nbytes:
|
||||
raise TypeError("into length must be at least message.nbytes")
|
||||
out = into
|
||||
_lib.aegis256x2_encrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: message.nbytes] # type: ignore
|
||||
|
||||
|
||||
def decrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
_lib.aegis256x2_decrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
# This is missing from C API but convenient to have here
|
||||
def mac(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
data: Buffer,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Compute a MAC for the given data in one shot.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key())
|
||||
nonce: Public nonce (generate with random_nonce())
|
||||
data: Data to MAC
|
||||
maclen: MAC length (16 or 32, default 16)
|
||||
into: Buffer to write MAC into (default: bytearray created)
|
||||
|
||||
Returns:
|
||||
MAC bytes as bytearray if into not provided, memoryview of into otherwise
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
data = memoryview(data)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
mac_state = Mac(key, nonce, maclen)
|
||||
mac_state.update(data)
|
||||
return mac_state.final(into)
|
||||
|
||||
|
||||
class Mac:
|
||||
"""MAC calculation and verification with incremental updates.
|
||||
|
||||
Example:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
...
|
||||
mac = a.final()
|
||||
|
||||
Hashlib compatible interface:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
bytes_mac = a.digest()
|
||||
hex_mac = a.hexdigest()
|
||||
"""
|
||||
|
||||
__slots__ = ("_proxy", "_maclen", "_cached_digest")
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer, maclen: int = MACBYTES) -> None:
|
||||
"""Create a MAC with the given key, nonce, and tag length.
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
self._maclen = maclen
|
||||
self._proxy = new_aligned_struct("aegis256x2_mac_state", ALIGNMENT)
|
||||
_lib.aegis256x2_mac_init(self._proxy.ptr, _ptr(key), _ptr(nonce))
|
||||
self._cached_digest: None | Literal[False] | bytes = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset back to the original state, prior to any updates."""
|
||||
_lib.aegis256x2_mac_reset(self._proxy.ptr)
|
||||
self._cached_digest = None
|
||||
|
||||
def clone(self) -> "Mac":
|
||||
"""Return a clone of current MAC state."""
|
||||
clone = object.__new__(Mac)
|
||||
clone._maclen = self._maclen
|
||||
clone._proxy = new_aligned_struct("aegis256x2_mac_state", ALIGNMENT)
|
||||
_lib.aegis256x2_mac_state_clone(clone._proxy.ptr, self._proxy.ptr)
|
||||
clone._cached_digest = self._cached_digest
|
||||
return clone
|
||||
|
||||
__deepcopy__ = clone
|
||||
|
||||
def update(self, data: Buffer) -> None:
|
||||
"""Update the MAC state with more data.
|
||||
|
||||
Repeated calls to update() are equivalent to a single call with the concatenated data.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError("Cannot update after final()")
|
||||
data = memoryview(data)
|
||||
rc = _lib.aegis256x2_mac_update(self._proxy.ptr, _ptr(data), data.nbytes)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac update failed: {err_name}")
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Calculate and return the MAC tag for the currently input data.
|
||||
|
||||
This method can only be called once. After calling it, the MAC becomes unusable
|
||||
for further updates or calls to final().
|
||||
|
||||
Args:
|
||||
into: Optional buffer to write the tag into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
The tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If finalization fails in the C library or if already finalized.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError(
|
||||
"The MAC can only be calculated once. Use reset() to start over, or clone() before finalizing to continue."
|
||||
)
|
||||
maclen = self._maclen
|
||||
if into is None:
|
||||
out = bytearray(maclen)
|
||||
else:
|
||||
into = memoryview(into)
|
||||
if into.nbytes < maclen:
|
||||
raise TypeError("into length must be at least maclen")
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis256x2_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac final failed: {err_name}")
|
||||
self._cached_digest = False
|
||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
||||
|
||||
def digest(self) -> bytes:
|
||||
"""Calculate and return the MAC tag as bytes.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
Can be called after final() to get the cached digest.
|
||||
"""
|
||||
if self._cached_digest:
|
||||
return self._cached_digest
|
||||
self._cached_digest = bytes(self.final()) # Overrides the False set by final()
|
||||
return self._cached_digest
|
||||
|
||||
def hexdigest(self) -> str:
|
||||
"""Calculate and return the MAC tag as a hex string.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
"""
|
||||
return self.digest().hex()
|
||||
|
||||
def verify(self, mac: Buffer):
|
||||
"""Verify that the data entered so far matches the given MAC tag.
|
||||
|
||||
Unlike the C library, this method does not alter the current state.
|
||||
|
||||
Args:
|
||||
mac: The tag to verify against (16 or 32 bytes).
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length is invalid.
|
||||
ValueError: If verification fails.
|
||||
"""
|
||||
mac = memoryview(mac)
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
|
||||
cloned = self.clone()
|
||||
rc = _lib.aegis256x2_mac_verify(cloned._proxy.ptr, _ptr(mac), maclen)
|
||||
if rc != 0:
|
||||
raise ValueError("mac verification failed")
|
||||
|
||||
|
||||
class Encryptor:
|
||||
"""Incremental encryptor.
|
||||
|
||||
- update(message[, into]) -> returns produced ciphertext bytes
|
||||
- final([into]) -> returns MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental encryptor.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
ad: Associated data to bind to the encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis256x2_state", ALIGNMENT)
|
||||
_lib.aegis256x2_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(
|
||||
self, message: Buffer, into: Buffer | None = None
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt a chunk of the message.
|
||||
|
||||
Args:
|
||||
message: Plaintext bytes to encrypt.
|
||||
into: Optional destination buffer; must be >= len(message).
|
||||
|
||||
Returns:
|
||||
The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = message.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be >= expected output size for this update"
|
||||
)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256x2_state_encrypt_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(
|
||||
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||
)
|
||||
w = int(written[0])
|
||||
assert w == expected_out
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Finalize encryption and return the authentication tag.
|
||||
|
||||
Args:
|
||||
into: Optional destination buffer for the tag.
|
||||
|
||||
Returns:
|
||||
The authentication tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the C final call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
# Only the authentication tag is produced here; allocate exactly maclen
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
out = into if into is not None else bytearray(maclen)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256x2_state_encrypt_final(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
written,
|
||||
maclen,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||
w = int(written[0])
|
||||
if into is None:
|
||||
# Only the tag bytes are returned when we allocate the buffer
|
||||
assert w == maclen
|
||||
self._state = None
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
|
||||
class Decryptor:
|
||||
"""Incremental decryptor.
|
||||
|
||||
- update(ciphertext[, into]) -> returns plaintext bytes
|
||||
- final(mac) -> verifies the MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental decryptor for detached tags.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ad: Associated data used during encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis256x2_state", ALIGNMENT)
|
||||
_lib.aegis256x2_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Process a chunk of ciphertext.
|
||||
|
||||
Args:
|
||||
ct: Ciphertext bytes (without MAC).
|
||||
into: Optional destination buffer; must be >= len(ciphertext).
|
||||
|
||||
Returns:
|
||||
A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = ct.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError("into length must be >= required capacity for this update")
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256x2_state_decrypt_detached_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
"""Finalize decryption by verifying the MAC tag.
|
||||
|
||||
Args:
|
||||
mac: Tag to verify.
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length doesn't match the expected maclen.
|
||||
ValueError: If authentication fails.
|
||||
RuntimeError: If called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
mac = memoryview(mac)
|
||||
if mac.nbytes != maclen:
|
||||
raise TypeError(f"mac length must be {maclen}")
|
||||
rc = _lib.aegis256x2_state_decrypt_detached_final(
|
||||
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
self._state = None
|
||||
|
||||
|
||||
def new_state():
|
||||
"""Allocate and return a new aegis256x2_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis256x2_state", ALIGNMENT)
|
||||
|
||||
|
||||
def new_mac_state():
|
||||
"""Allocate and return a new aegis256x2_mac_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis256x2_mac_state", ALIGNMENT)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
"NAME",
|
||||
"KEYBYTES",
|
||||
"NONCEBYTES",
|
||||
"MACBYTES",
|
||||
"MACBYTES_LONG",
|
||||
"ALIGNMENT",
|
||||
"RATE",
|
||||
# utility functions
|
||||
"random_key",
|
||||
"random_nonce",
|
||||
"nonce_increment",
|
||||
"wipe",
|
||||
# one-shot functions
|
||||
"encrypt_detached",
|
||||
"decrypt_detached",
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"stream",
|
||||
"encrypt_unauthenticated",
|
||||
"decrypt_unauthenticated",
|
||||
"mac",
|
||||
# incremental classes
|
||||
"Encryptor",
|
||||
"Decryptor",
|
||||
"Mac",
|
||||
]
|
||||
@@ -0,0 +1,921 @@
|
||||
"""AEGIS-256X4"""
|
||||
# All modules are generated from aegis256x4.py by tools/generate.py!
|
||||
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
|
||||
|
||||
import errno
|
||||
import secrets
|
||||
from typing import Literal
|
||||
|
||||
from ._loader import ffi
|
||||
from ._loader import lib as _lib
|
||||
from .util import Buffer, new_aligned_struct, nonce_increment, wipe
|
||||
|
||||
NAME = "AEGIS-256X4" #: Algorithm display name
|
||||
KEYBYTES = 32 #: Key size in bytes (varies by algorithm)
|
||||
NONCEBYTES = 32 #: Nonce size in bytes (varies by algorithm)
|
||||
MACBYTES = 16 #: Normal MAC size (always 16)
|
||||
MACBYTES_LONG = 32 #: Long MAC size (always 32)
|
||||
ALIGNMENT = 64 #: Required alignment for internal structures
|
||||
RATE = 64 #: Byte chunk size in internal processing
|
||||
|
||||
|
||||
def random_key() -> bytearray:
|
||||
"""
|
||||
Generate a secret key using cryptographically secure random bytes.
|
||||
|
||||
It is recommended to wipe() the key after no longer needed.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(KEYBYTES))
|
||||
|
||||
|
||||
def random_nonce() -> bytearray:
|
||||
"""
|
||||
Generate a public nonce using cryptographically secure random bytes.
|
||||
|
||||
Nonces (a number used once) are public data that may be sent together
|
||||
with the ciphertext, but they need to be unique for each use.
|
||||
|
||||
See also: nonce_increment() can be used to derive sequential nonces.
|
||||
"""
|
||||
return bytearray(secrets.token_bytes(NONCEBYTES))
|
||||
|
||||
|
||||
def _ptr(buf):
|
||||
return ffi.NULL if buf is None else ffi.from_buffer(buf)
|
||||
|
||||
|
||||
def encrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
ct_into: Buffer | None = None,
|
||||
mac_into: Buffer | None = None,
|
||||
) -> tuple[bytearray | memoryview, bytearray | memoryview]:
|
||||
"""Encrypt message with associated data, returning ciphertext and MAC separately.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
ct_into: Buffer to write ciphertext into (default: bytearray created).
|
||||
mac_into: Buffer to write MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Tuple of (ciphertext, mac)
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if ct_into is not None:
|
||||
ct_into = memoryview(ct_into)
|
||||
if mac_into is not None:
|
||||
mac_into = memoryview(mac_into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
if ct_into is None:
|
||||
c = bytearray(message.nbytes)
|
||||
else:
|
||||
if ct_into.nbytes < message.nbytes:
|
||||
raise TypeError("ct_into length must be at least message.nbytes")
|
||||
c = ct_into
|
||||
if mac_into is None:
|
||||
mac = bytearray(maclen)
|
||||
else:
|
||||
if mac_into.nbytes < maclen:
|
||||
raise TypeError("mac_into length must be at least maclen")
|
||||
mac = mac_into
|
||||
|
||||
rc = _lib.aegis256x4_encrypt_detached(
|
||||
ffi.from_buffer(c),
|
||||
ffi.from_buffer(mac),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt detached failed: {err_name}")
|
||||
return (
|
||||
c if ct_into is None else memoryview(c)[: message.nbytes],
|
||||
mac if mac_into is None else memoryview(mac)[:maclen],
|
||||
) # type: ignore
|
||||
|
||||
|
||||
def decrypt_detached(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
mac: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with detached MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
mac: The MAC to verify.
|
||||
ad: Associated data (optional).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
mac = memoryview(mac)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256x4_decrypt_detached(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(mac),
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message with associated data, returning ciphertext with appended MAC.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write ciphertext+MAC into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If encryption fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes + maclen)
|
||||
else:
|
||||
if into.nbytes < message.nbytes + maclen:
|
||||
raise TypeError("into length must be at least message.nbytes + maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256x4_encrypt(
|
||||
ffi.from_buffer(out),
|
||||
maclen,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"encrypt failed: {err_name}")
|
||||
return out if into is None else memoryview(out)[: message.nbytes + maclen] # type: ignore
|
||||
|
||||
|
||||
def decrypt(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
*,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext with appended MAC and associated data.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext with MAC to decrypt.
|
||||
ad: Associated data (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
ValueError: If authentication fails.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if ct.nbytes < maclen:
|
||||
raise TypeError("ciphertext too short for tag")
|
||||
expected_out = ct.nbytes - maclen
|
||||
if into is None:
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256x4_decrypt(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
maclen,
|
||||
_ptr(ad),
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
||||
|
||||
|
||||
def stream(
|
||||
key: Buffer,
|
||||
nonce: Buffer | None,
|
||||
length: int | None = None,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | Buffer:
|
||||
"""Generate a stream of pseudorandom bytes.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce(), uses zeroes for nonce if None).
|
||||
length: Number of bytes to generate (required if into is None).
|
||||
into: Buffer to write stream into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Pseudorandom bytes as bytearray, or into returned directly.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid or neither length nor into provided.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
if nonce is not None:
|
||||
nonce = memoryview(nonce)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce is not None and nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
if length is None:
|
||||
raise TypeError("provide either into or length")
|
||||
out = bytearray(length)
|
||||
else:
|
||||
if length is not None and into.nbytes < length:
|
||||
raise TypeError("into length must be at least length")
|
||||
out = into
|
||||
_lib.aegis256x4_stream(
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
|
||||
|
||||
|
||||
def encrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
message: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt message without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
message: The plaintext message to encrypt.
|
||||
into: Buffer to write ciphertext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Ciphertext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(message.nbytes)
|
||||
else:
|
||||
if into.nbytes < message.nbytes:
|
||||
raise TypeError("into length must be at least message.nbytes")
|
||||
out = into
|
||||
_lib.aegis256x4_encrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: message.nbytes] # type: ignore
|
||||
|
||||
|
||||
def decrypt_unauthenticated(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ct: Buffer,
|
||||
*,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Decrypt ciphertext without authentication (for testing/debugging).
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ct: The ciphertext to decrypt.
|
||||
into: Buffer to write plaintext into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
Plaintext as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
if into is None:
|
||||
out = bytearray(ct.nbytes)
|
||||
else:
|
||||
if into.nbytes < ct.nbytes:
|
||||
raise TypeError("into length must be at least ct.nbytes")
|
||||
out = into
|
||||
_lib.aegis256x4_decrypt_unauthenticated(
|
||||
ffi.from_buffer(out),
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
return out if into is None else memoryview(out)[: ct.nbytes] # type: ignore
|
||||
|
||||
|
||||
# This is missing from C API but convenient to have here
|
||||
def mac(
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
data: Buffer,
|
||||
maclen: int = MACBYTES,
|
||||
into: Buffer | None = None,
|
||||
) -> bytearray | memoryview:
|
||||
"""Compute a MAC for the given data in one shot.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key())
|
||||
nonce: Public nonce (generate with random_nonce())
|
||||
data: Data to MAC
|
||||
maclen: MAC length (16 or 32, default 16)
|
||||
into: Buffer to write MAC into (default: bytearray created)
|
||||
|
||||
Returns:
|
||||
MAC bytes as bytearray if into not provided, memoryview of into otherwise
|
||||
"""
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
data = memoryview(data)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
mac_state = Mac(key, nonce, maclen)
|
||||
mac_state.update(data)
|
||||
return mac_state.final(into)
|
||||
|
||||
|
||||
class Mac:
|
||||
"""MAC calculation and verification with incremental updates.
|
||||
|
||||
Example:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
...
|
||||
mac = a.final()
|
||||
|
||||
Hashlib compatible interface:
|
||||
a = Mac(key, nonce)
|
||||
a.update(data)
|
||||
bytes_mac = a.digest()
|
||||
hex_mac = a.hexdigest()
|
||||
"""
|
||||
|
||||
__slots__ = ("_proxy", "_maclen", "_cached_digest")
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer, maclen: int = MACBYTES) -> None:
|
||||
"""Create a MAC with the given key, nonce, and tag length.
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
|
||||
self._maclen = maclen
|
||||
self._proxy = new_aligned_struct("aegis256x4_mac_state", ALIGNMENT)
|
||||
_lib.aegis256x4_mac_init(self._proxy.ptr, _ptr(key), _ptr(nonce))
|
||||
self._cached_digest: None | Literal[False] | bytes = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset back to the original state, prior to any updates."""
|
||||
_lib.aegis256x4_mac_reset(self._proxy.ptr)
|
||||
self._cached_digest = None
|
||||
|
||||
def clone(self) -> "Mac":
|
||||
"""Return a clone of current MAC state."""
|
||||
clone = object.__new__(Mac)
|
||||
clone._maclen = self._maclen
|
||||
clone._proxy = new_aligned_struct("aegis256x4_mac_state", ALIGNMENT)
|
||||
_lib.aegis256x4_mac_state_clone(clone._proxy.ptr, self._proxy.ptr)
|
||||
clone._cached_digest = self._cached_digest
|
||||
return clone
|
||||
|
||||
__deepcopy__ = clone
|
||||
|
||||
def update(self, data: Buffer) -> None:
|
||||
"""Update the MAC state with more data.
|
||||
|
||||
Repeated calls to update() are equivalent to a single call with the concatenated data.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError("Cannot update after final()")
|
||||
data = memoryview(data)
|
||||
rc = _lib.aegis256x4_mac_update(self._proxy.ptr, _ptr(data), data.nbytes)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac update failed: {err_name}")
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Calculate and return the MAC tag for the currently input data.
|
||||
|
||||
This method can only be called once. After calling it, the MAC becomes unusable
|
||||
for further updates or calls to final().
|
||||
|
||||
Args:
|
||||
into: Optional buffer to write the tag into (default: bytearray created).
|
||||
|
||||
Returns:
|
||||
The tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If lengths are invalid.
|
||||
RuntimeError: If finalization fails in the C library or if already finalized.
|
||||
"""
|
||||
if self._cached_digest is not None:
|
||||
raise RuntimeError(
|
||||
"The MAC can only be calculated once. Use reset() to start over, or clone() before finalizing to continue."
|
||||
)
|
||||
maclen = self._maclen
|
||||
if into is None:
|
||||
out = bytearray(maclen)
|
||||
else:
|
||||
into = memoryview(into)
|
||||
if into.nbytes < maclen:
|
||||
raise TypeError("into length must be at least maclen")
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis256x4_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"mac final failed: {err_name}")
|
||||
self._cached_digest = False
|
||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
||||
|
||||
def digest(self) -> bytes:
|
||||
"""Calculate and return the MAC tag as bytes.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
Can be called after final() to get the cached digest.
|
||||
"""
|
||||
if self._cached_digest:
|
||||
return self._cached_digest
|
||||
self._cached_digest = bytes(self.final()) # Overrides the False set by final()
|
||||
return self._cached_digest
|
||||
|
||||
def hexdigest(self) -> str:
|
||||
"""Calculate and return the MAC tag as a hex string.
|
||||
|
||||
After calling this method, the MAC becomes unusable for further updates.
|
||||
The result is cached and subsequent calls return the same value.
|
||||
"""
|
||||
return self.digest().hex()
|
||||
|
||||
def verify(self, mac: Buffer):
|
||||
"""Verify that the data entered so far matches the given MAC tag.
|
||||
|
||||
Unlike the C library, this method does not alter the current state.
|
||||
|
||||
Args:
|
||||
mac: The tag to verify against (16 or 32 bytes).
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length is invalid.
|
||||
ValueError: If verification fails.
|
||||
"""
|
||||
mac = memoryview(mac)
|
||||
maclen = mac.nbytes
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("mac length must be 16 or 32")
|
||||
|
||||
cloned = self.clone()
|
||||
rc = _lib.aegis256x4_mac_verify(cloned._proxy.ptr, _ptr(mac), maclen)
|
||||
if rc != 0:
|
||||
raise ValueError("mac verification failed")
|
||||
|
||||
|
||||
class Encryptor:
|
||||
"""Incremental encryptor.
|
||||
|
||||
- update(message[, into]) -> returns produced ciphertext bytes
|
||||
- final([into]) -> returns MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental encryptor.
|
||||
|
||||
Args:
|
||||
key: Secret key (generate with random_key()).
|
||||
nonce: Public nonce (generate with random_nonce()).
|
||||
ad: Associated data to bind to the encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis256x4_state", ALIGNMENT)
|
||||
_lib.aegis256x4_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(
|
||||
self, message: Buffer, into: Buffer | None = None
|
||||
) -> bytearray | memoryview:
|
||||
"""Encrypt a chunk of the message.
|
||||
|
||||
Args:
|
||||
message: Plaintext bytes to encrypt.
|
||||
into: Optional destination buffer; must be >= len(message).
|
||||
|
||||
Returns:
|
||||
The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
message = memoryview(message)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = message.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be >= expected output size for this update"
|
||||
)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256x4_state_encrypt_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(message),
|
||||
message.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(
|
||||
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||
)
|
||||
w = int(written[0])
|
||||
assert w == expected_out
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Finalize encryption and return the authentication tag.
|
||||
|
||||
Args:
|
||||
into: Optional destination buffer for the tag.
|
||||
|
||||
Returns:
|
||||
The authentication tag as bytearray if into not provided, memoryview of into otherwise.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the C final call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
# Only the authentication tag is produced here; allocate exactly maclen
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
out = into if into is not None else bytearray(maclen)
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256x4_state_encrypt_final(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out),
|
||||
memoryview(out).nbytes,
|
||||
written,
|
||||
maclen,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||
w = int(written[0])
|
||||
if into is None:
|
||||
# Only the tag bytes are returned when we allocate the buffer
|
||||
assert w == maclen
|
||||
self._state = None
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
|
||||
class Decryptor:
|
||||
"""Incremental decryptor.
|
||||
|
||||
- update(ciphertext[, into]) -> returns plaintext bytes
|
||||
- final(mac) -> verifies the MAC tag
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "_maclen")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
ad: Buffer | None = None,
|
||||
maclen: int = MACBYTES,
|
||||
):
|
||||
"""Create an incremental decryptor for detached tags.
|
||||
|
||||
Args:
|
||||
key: Secret key (same key used during encryption).
|
||||
nonce: Public nonce (same nonce used during encryption).
|
||||
ad: Associated data used during encryption (optional).
|
||||
maclen: MAC length (16 or 32, default 16).
|
||||
|
||||
Raises:
|
||||
TypeError: If key, nonce, or maclen are invalid.
|
||||
"""
|
||||
if maclen not in (16, 32):
|
||||
raise TypeError("maclen must be 16 or 32")
|
||||
key = memoryview(key)
|
||||
nonce = memoryview(nonce)
|
||||
if ad is not None:
|
||||
ad = memoryview(ad)
|
||||
if key.nbytes != KEYBYTES:
|
||||
raise TypeError(f"key length must be {KEYBYTES}")
|
||||
if nonce.nbytes != NONCEBYTES:
|
||||
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
||||
self._state = new_aligned_struct("aegis256x4_state", ALIGNMENT)
|
||||
_lib.aegis256x4_state_init(
|
||||
self._state.ptr,
|
||||
_ptr(ad) if ad is not None else ffi.NULL,
|
||||
0 if ad is None else ad.nbytes,
|
||||
_ptr(nonce),
|
||||
_ptr(key),
|
||||
)
|
||||
self._maclen = maclen
|
||||
|
||||
def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
|
||||
"""Process a chunk of ciphertext.
|
||||
|
||||
Args:
|
||||
ct: Ciphertext bytes (without MAC).
|
||||
into: Optional destination buffer; must be >= len(ciphertext).
|
||||
|
||||
Returns:
|
||||
A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
|
||||
|
||||
Raises:
|
||||
TypeError: If destination buffer is too small.
|
||||
RuntimeError: If the C update call fails or if called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call update() after final()")
|
||||
ct = memoryview(ct)
|
||||
if into is not None:
|
||||
into = memoryview(into)
|
||||
expected_out = ct.nbytes
|
||||
out = into if into is not None else bytearray(expected_out)
|
||||
out_mv = memoryview(out)
|
||||
if out_mv.nbytes < expected_out:
|
||||
raise TypeError("into length must be >= required capacity for this update")
|
||||
written = ffi.new("size_t *")
|
||||
rc = _lib.aegis256x4_state_decrypt_detached_update(
|
||||
self._state.ptr,
|
||||
ffi.from_buffer(out_mv),
|
||||
out_mv.nbytes,
|
||||
written,
|
||||
_ptr(ct),
|
||||
ct.nbytes,
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
"""Finalize decryption by verifying the MAC tag.
|
||||
|
||||
Args:
|
||||
mac: Tag to verify.
|
||||
|
||||
Raises:
|
||||
TypeError: If tag length doesn't match the expected maclen.
|
||||
ValueError: If authentication fails.
|
||||
RuntimeError: If called after final().
|
||||
"""
|
||||
if self._state is None:
|
||||
raise RuntimeError("Cannot call final() after final()")
|
||||
maclen = self._maclen
|
||||
mac = memoryview(mac)
|
||||
if mac.nbytes != maclen:
|
||||
raise TypeError(f"mac length must be {maclen}")
|
||||
rc = _lib.aegis256x4_state_decrypt_detached_final(
|
||||
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||
)
|
||||
if rc != 0:
|
||||
raise ValueError("authentication failed")
|
||||
self._state = None
|
||||
|
||||
|
||||
def new_state():
|
||||
"""Allocate and return a new aegis256x4_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis256x4_state", ALIGNMENT)
|
||||
|
||||
|
||||
def new_mac_state():
|
||||
"""Allocate and return a new aegis256x4_mac_state* with proper alignment."""
|
||||
return new_aligned_struct("aegis256x4_mac_state", ALIGNMENT)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
"NAME",
|
||||
"KEYBYTES",
|
||||
"NONCEBYTES",
|
||||
"MACBYTES",
|
||||
"MACBYTES_LONG",
|
||||
"ALIGNMENT",
|
||||
"RATE",
|
||||
# utility functions
|
||||
"random_key",
|
||||
"random_nonce",
|
||||
"nonce_increment",
|
||||
"wipe",
|
||||
# one-shot functions
|
||||
"encrypt_detached",
|
||||
"decrypt_detached",
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"stream",
|
||||
"encrypt_unauthenticated",
|
||||
"decrypt_unauthenticated",
|
||||
"mac",
|
||||
# incremental classes
|
||||
"Encryptor",
|
||||
"Decryptor",
|
||||
"Mac",
|
||||
]
|
||||
@@ -0,0 +1,573 @@
|
||||
/* This file is generated with tools/generate.py. Do not edit. */
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned long size_t;
|
||||
|
||||
/* aegis.h */
|
||||
int aegis_init(void);
|
||||
int aegis_verify_16(const uint8_t *x, const uint8_t *y) ;
|
||||
int aegis_verify_32(const uint8_t *x, const uint8_t *y) ;
|
||||
|
||||
/* aegis128l.h */
|
||||
typedef struct aegis128l_state { ...; } aegis128l_state;
|
||||
typedef struct aegis128l_mac_state { ...; } aegis128l_mac_state;
|
||||
size_t aegis128l_keybytes(void);
|
||||
size_t aegis128l_npubbytes(void);
|
||||
size_t aegis128l_abytes_min(void);
|
||||
size_t aegis128l_abytes_max(void);
|
||||
size_t aegis128l_tailbytes_max(void);
|
||||
int aegis128l_encrypt_detached(uint8_t *c,
|
||||
uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128l_decrypt_detached(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
int aegis128l_encrypt(uint8_t *c,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128l_decrypt(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
void aegis128l_state_init(aegis128l_state *st_,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128l_state_encrypt_update(aegis128l_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
const uint8_t *m,
|
||||
size_t mlen);
|
||||
int aegis128l_state_encrypt_detached_final(aegis128l_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
uint8_t *mac,
|
||||
size_t maclen);
|
||||
int aegis128l_state_encrypt_final(aegis128l_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
size_t maclen);
|
||||
int aegis128l_state_decrypt_detached_update(aegis128l_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *c,
|
||||
size_t clen) ;
|
||||
int aegis128l_state_decrypt_detached_final(aegis128l_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *mac,
|
||||
size_t maclen) ;
|
||||
void aegis128l_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||
void aegis128l_encrypt_unauthenticated(uint8_t *c,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis128l_decrypt_unauthenticated(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis128l_mac_init(aegis128l_mac_state *st_, const uint8_t *k, const uint8_t *npub);
|
||||
int aegis128l_mac_update(aegis128l_mac_state *st_, const uint8_t *m, size_t mlen);
|
||||
int aegis128l_mac_final(aegis128l_mac_state *st_, uint8_t *mac, size_t maclen);
|
||||
int aegis128l_mac_verify(aegis128l_mac_state *st_, const uint8_t *mac, size_t maclen);
|
||||
void aegis128l_mac_reset(aegis128l_mac_state *st_);
|
||||
void aegis128l_mac_state_clone(aegis128l_mac_state *dst, const aegis128l_mac_state *src);
|
||||
|
||||
/* aegis128x2.h */
|
||||
typedef struct aegis128x2_state { ...; } aegis128x2_state;
|
||||
typedef struct aegis128x2_mac_state { ...; } aegis128x2_mac_state;
|
||||
size_t aegis128x2_keybytes(void);
|
||||
size_t aegis128x2_npubbytes(void);
|
||||
size_t aegis128x2_abytes_min(void);
|
||||
size_t aegis128x2_abytes_max(void);
|
||||
size_t aegis128x2_tailbytes_max(void);
|
||||
int aegis128x2_encrypt_detached(uint8_t *c,
|
||||
uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128x2_decrypt_detached(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
int aegis128x2_encrypt(uint8_t *c,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128x2_decrypt(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
void aegis128x2_state_init(aegis128x2_state *st_,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128x2_state_encrypt_update(aegis128x2_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
const uint8_t *m,
|
||||
size_t mlen);
|
||||
int aegis128x2_state_encrypt_detached_final(aegis128x2_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
uint8_t *mac,
|
||||
size_t maclen);
|
||||
int aegis128x2_state_encrypt_final(aegis128x2_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
size_t maclen);
|
||||
int aegis128x2_state_decrypt_detached_update(aegis128x2_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *c,
|
||||
size_t clen) ;
|
||||
int aegis128x2_state_decrypt_detached_final(aegis128x2_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *mac,
|
||||
size_t maclen) ;
|
||||
void aegis128x2_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||
void aegis128x2_encrypt_unauthenticated(uint8_t *c,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis128x2_decrypt_unauthenticated(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis128x2_mac_init(aegis128x2_mac_state *st_, const uint8_t *k, const uint8_t *npub);
|
||||
int aegis128x2_mac_update(aegis128x2_mac_state *st_, const uint8_t *m, size_t mlen);
|
||||
int aegis128x2_mac_final(aegis128x2_mac_state *st_, uint8_t *mac, size_t maclen);
|
||||
int aegis128x2_mac_verify(aegis128x2_mac_state *st_, const uint8_t *mac, size_t maclen);
|
||||
void aegis128x2_mac_reset(aegis128x2_mac_state *st_);
|
||||
void aegis128x2_mac_state_clone(aegis128x2_mac_state *dst, const aegis128x2_mac_state *src);
|
||||
|
||||
/* aegis128x4.h */
|
||||
typedef struct aegis128x4_state { ...; } aegis128x4_state;
|
||||
typedef struct aegis128x4_mac_state { ...; } aegis128x4_mac_state;
|
||||
size_t aegis128x4_keybytes(void);
|
||||
size_t aegis128x4_npubbytes(void);
|
||||
size_t aegis128x4_abytes_min(void);
|
||||
size_t aegis128x4_abytes_max(void);
|
||||
size_t aegis128x4_tailbytes_max(void);
|
||||
int aegis128x4_encrypt_detached(uint8_t *c,
|
||||
uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128x4_decrypt_detached(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
int aegis128x4_encrypt(uint8_t *c,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128x4_decrypt(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
void aegis128x4_state_init(aegis128x4_state *st_,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis128x4_state_encrypt_update(aegis128x4_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
const uint8_t *m,
|
||||
size_t mlen);
|
||||
int aegis128x4_state_encrypt_detached_final(aegis128x4_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
uint8_t *mac,
|
||||
size_t maclen);
|
||||
int aegis128x4_state_encrypt_final(aegis128x4_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
size_t maclen);
|
||||
int aegis128x4_state_decrypt_detached_update(aegis128x4_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *c,
|
||||
size_t clen) ;
|
||||
int aegis128x4_state_decrypt_detached_final(aegis128x4_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *mac,
|
||||
size_t maclen) ;
|
||||
void aegis128x4_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||
void aegis128x4_encrypt_unauthenticated(uint8_t *c,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis128x4_decrypt_unauthenticated(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis128x4_mac_init(aegis128x4_mac_state *st_, const uint8_t *k, const uint8_t *npub);
|
||||
int aegis128x4_mac_update(aegis128x4_mac_state *st_, const uint8_t *m, size_t mlen);
|
||||
int aegis128x4_mac_final(aegis128x4_mac_state *st_, uint8_t *mac, size_t maclen);
|
||||
int aegis128x4_mac_verify(aegis128x4_mac_state *st_, const uint8_t *mac, size_t maclen);
|
||||
void aegis128x4_mac_reset(aegis128x4_mac_state *st_);
|
||||
void aegis128x4_mac_state_clone(aegis128x4_mac_state *dst, const aegis128x4_mac_state *src);
|
||||
|
||||
/* aegis256.h */
|
||||
typedef struct aegis256_state { ...; } aegis256_state;
|
||||
typedef struct aegis256_mac_state { ...; } aegis256_mac_state;
|
||||
size_t aegis256_keybytes(void);
|
||||
size_t aegis256_npubbytes(void);
|
||||
size_t aegis256_abytes_min(void);
|
||||
size_t aegis256_abytes_max(void);
|
||||
size_t aegis256_tailbytes_max(void);
|
||||
int aegis256_encrypt_detached(uint8_t *c,
|
||||
uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256_decrypt_detached(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
int aegis256_encrypt(uint8_t *c,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256_decrypt(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
void aegis256_state_init(aegis256_state *st_,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256_state_encrypt_update(aegis256_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
const uint8_t *m,
|
||||
size_t mlen);
|
||||
int aegis256_state_encrypt_detached_final(aegis256_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
uint8_t *mac,
|
||||
size_t maclen);
|
||||
int aegis256_state_encrypt_final(aegis256_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
size_t maclen);
|
||||
int aegis256_state_decrypt_detached_update(aegis256_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *c,
|
||||
size_t clen) ;
|
||||
int aegis256_state_decrypt_detached_final(aegis256_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *mac,
|
||||
size_t maclen) ;
|
||||
void aegis256_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||
void aegis256_encrypt_unauthenticated(uint8_t *c,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis256_decrypt_unauthenticated(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis256_mac_init(aegis256_mac_state *st_, const uint8_t *k, const uint8_t *npub);
|
||||
int aegis256_mac_update(aegis256_mac_state *st_, const uint8_t *m, size_t mlen);
|
||||
int aegis256_mac_final(aegis256_mac_state *st_, uint8_t *mac, size_t maclen);
|
||||
int aegis256_mac_verify(aegis256_mac_state *st_, const uint8_t *mac, size_t maclen);
|
||||
void aegis256_mac_reset(aegis256_mac_state *st_);
|
||||
void aegis256_mac_state_clone(aegis256_mac_state *dst, const aegis256_mac_state *src);
|
||||
|
||||
/* aegis256x2.h */
|
||||
typedef struct aegis256x2_state { ...; } aegis256x2_state;
|
||||
typedef struct aegis256x2_mac_state { ...; } aegis256x2_mac_state;
|
||||
size_t aegis256x2_keybytes(void);
|
||||
size_t aegis256x2_npubbytes(void);
|
||||
size_t aegis256x2_abytes_min(void);
|
||||
size_t aegis256x2_abytes_max(void);
|
||||
size_t aegis256x2_tailbytes_max(void);
|
||||
int aegis256x2_encrypt_detached(uint8_t *c,
|
||||
uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256x2_decrypt_detached(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
int aegis256x2_encrypt(uint8_t *c,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256x2_decrypt(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
void aegis256x2_state_init(aegis256x2_state *st_,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256x2_state_encrypt_update(aegis256x2_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
const uint8_t *m,
|
||||
size_t mlen);
|
||||
int aegis256x2_state_encrypt_detached_final(aegis256x2_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
uint8_t *mac,
|
||||
size_t maclen);
|
||||
int aegis256x2_state_encrypt_final(aegis256x2_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
size_t maclen);
|
||||
int aegis256x2_state_decrypt_detached_update(aegis256x2_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *c,
|
||||
size_t clen) ;
|
||||
int aegis256x2_state_decrypt_detached_final(aegis256x2_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *mac,
|
||||
size_t maclen) ;
|
||||
void aegis256x2_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||
void aegis256x2_encrypt_unauthenticated(uint8_t *c,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis256x2_decrypt_unauthenticated(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis256x2_mac_init(aegis256x2_mac_state *st_, const uint8_t *k, const uint8_t *npub);
|
||||
int aegis256x2_mac_update(aegis256x2_mac_state *st_, const uint8_t *m, size_t mlen);
|
||||
int aegis256x2_mac_final(aegis256x2_mac_state *st_, uint8_t *mac, size_t maclen);
|
||||
int aegis256x2_mac_verify(aegis256x2_mac_state *st_, const uint8_t *mac, size_t maclen);
|
||||
void aegis256x2_mac_reset(aegis256x2_mac_state *st_);
|
||||
void aegis256x2_mac_state_clone(aegis256x2_mac_state *dst, const aegis256x2_mac_state *src);
|
||||
|
||||
/* aegis256x4.h */
|
||||
typedef struct aegis256x4_state { ...; } aegis256x4_state;
|
||||
typedef struct aegis256x4_mac_state { ...; } aegis256x4_mac_state;
|
||||
size_t aegis256x4_keybytes(void);
|
||||
size_t aegis256x4_npubbytes(void);
|
||||
size_t aegis256x4_abytes_min(void);
|
||||
size_t aegis256x4_abytes_max(void);
|
||||
size_t aegis256x4_tailbytes_max(void);
|
||||
int aegis256x4_encrypt_detached(uint8_t *c,
|
||||
uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256x4_decrypt_detached(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *mac,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
int aegis256x4_encrypt(uint8_t *c,
|
||||
size_t maclen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256x4_decrypt(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
size_t maclen,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k) ;
|
||||
void aegis256x4_state_init(aegis256x4_state *st_,
|
||||
const uint8_t *ad,
|
||||
size_t adlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
int aegis256x4_state_encrypt_update(aegis256x4_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
const uint8_t *m,
|
||||
size_t mlen);
|
||||
int aegis256x4_state_encrypt_detached_final(aegis256x4_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
uint8_t *mac,
|
||||
size_t maclen);
|
||||
int aegis256x4_state_encrypt_final(aegis256x4_state *st_,
|
||||
uint8_t *c,
|
||||
size_t clen_max,
|
||||
size_t *written,
|
||||
size_t maclen);
|
||||
int aegis256x4_state_decrypt_detached_update(aegis256x4_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *c,
|
||||
size_t clen) ;
|
||||
int aegis256x4_state_decrypt_detached_final(aegis256x4_state *st_,
|
||||
uint8_t *m,
|
||||
size_t mlen_max,
|
||||
size_t *written,
|
||||
const uint8_t *mac,
|
||||
size_t maclen) ;
|
||||
void aegis256x4_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||
void aegis256x4_encrypt_unauthenticated(uint8_t *c,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis256x4_decrypt_unauthenticated(uint8_t *m,
|
||||
const uint8_t *c,
|
||||
size_t clen,
|
||||
const uint8_t *npub,
|
||||
const uint8_t *k);
|
||||
void aegis256x4_mac_init(aegis256x4_mac_state *st_, const uint8_t *k, const uint8_t *npub);
|
||||
int aegis256x4_mac_update(aegis256x4_mac_state *st_, const uint8_t *m, size_t mlen);
|
||||
int aegis256x4_mac_final(aegis256x4_mac_state *st_, uint8_t *mac, size_t maclen);
|
||||
int aegis256x4_mac_verify(aegis256x4_mac_state *st_, const uint8_t *mac, size_t maclen);
|
||||
void aegis256x4_mac_reset(aegis256x4_mac_state *st_);
|
||||
void aegis256x4_mac_state_clone(aegis256x4_mac_state *dst, const aegis256x4_mac_state *src);
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Python benchmark matching src/test/benchmark.zig for all supported Aegis algorithms.
|
||||
|
||||
It performs two benchmarks with the same parameters as the Zig version:
|
||||
- AEGIS encrypt (attached tag, maclen = MACBYTES)
|
||||
- AEGIS MAC (clone state pattern)
|
||||
|
||||
Output format and throughput units mirror the Zig benchmark (Mb/s).
|
||||
"""
|
||||
|
||||
import secrets
|
||||
import time
|
||||
|
||||
from aeg import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
||||
|
||||
MSG_LEN = 16384000 # 16 000 KiB
|
||||
ITERATIONS = 100
|
||||
|
||||
|
||||
def bench_encrypt(ciph) -> None:
|
||||
key = ciph.random_key()
|
||||
nonce = ciph.random_nonce()
|
||||
|
||||
# Single buffer, as in Zig: c_out == m buffer, with tag appended
|
||||
maclen = ciph.MACBYTES
|
||||
buf = bytearray(MSG_LEN + maclen)
|
||||
# Initialize buffer with random data
|
||||
buf[:] = secrets.token_bytes(len(buf))
|
||||
|
||||
mview = memoryview(buf)[:MSG_LEN]
|
||||
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(ITERATIONS):
|
||||
ciph.encrypt(key, nonce, mview, None, maclen=maclen, into=buf)
|
||||
t1 = time.perf_counter()
|
||||
|
||||
# Prevent any unrealistic optimization assumptions
|
||||
_ = buf[0]
|
||||
|
||||
bits = MSG_LEN * ITERATIONS * 8
|
||||
elapsed_s = t1 - t0
|
||||
throughput_mbps = (
|
||||
(bits / (elapsed_s * 1_000_000)) if elapsed_s > 0 else float("inf")
|
||||
)
|
||||
print(f"{ciph.NAME}\t{throughput_mbps:10.2f} Mb/s")
|
||||
|
||||
|
||||
def bench_mac(ciph) -> None:
|
||||
key = ciph.random_key()
|
||||
nonce = ciph.random_nonce()
|
||||
|
||||
buf = bytearray(MSG_LEN)
|
||||
buf[:] = secrets.token_bytes(len(buf))
|
||||
|
||||
mac_out = bytearray(ciph.MACBYTES_LONG)
|
||||
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(ITERATIONS):
|
||||
ciph.mac(key, nonce, buf, maclen=ciph.MACBYTES_LONG, into=mac_out)
|
||||
t1 = time.perf_counter()
|
||||
|
||||
_ = mac_out[0]
|
||||
|
||||
bits = MSG_LEN * ITERATIONS * 8
|
||||
elapsed_s = t1 - t0
|
||||
throughput_mbps = (
|
||||
(bits / (elapsed_s * 1_000_000)) if elapsed_s > 0 else float("inf")
|
||||
)
|
||||
print(f"{ciph.NAME} MAC\t{throughput_mbps:10.2f} Mb/s")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# aegis_init() is called in the loader at import time already
|
||||
# Run encrypt benchmarks in order: 256, 256x2, 256x4, 128l, 128x2, 128x4
|
||||
bench_encrypt(aegis256)
|
||||
bench_encrypt(aegis256x2)
|
||||
bench_encrypt(aegis256x4)
|
||||
bench_encrypt(aegis128l)
|
||||
bench_encrypt(aegis128x2)
|
||||
bench_encrypt(aegis128x4)
|
||||
|
||||
# Run MAC benchmarks in order: 128l, 128x2, 128x4, 256, 256x2, 256x4
|
||||
bench_mac(aegis128l)
|
||||
bench_mac(aegis128x2)
|
||||
bench_mac(aegis128x4)
|
||||
bench_mac(aegis256)
|
||||
bench_mac(aegis256x2)
|
||||
bench_mac(aegis256x4)
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Utility helpers for aeg.
|
||||
|
||||
Currently provides Python-side aligned allocation helpers that avoid relying
|
||||
on libc/posix_memalign. Memory is owned by Python; C code only borrows it.
|
||||
"""
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
from ._loader import ffi
|
||||
|
||||
__all__ = ["new_aligned_struct", "aligned_address", "Buffer", "nonce_increment", "wipe"]
|
||||
|
||||
try:
|
||||
from collections.abc import Buffer # type: ignore
|
||||
except ImportError:
|
||||
# Fallback for Python < 3.12
|
||||
class Buffer(Protocol):
|
||||
def __buffer__(self, flags: int) -> memoryview: ...
|
||||
|
||||
|
||||
def aligned_address(obj) -> int:
|
||||
"""Return the integer address of the start of a cffi array object."""
|
||||
return int(ffi.cast("uintptr_t", ffi.addressof(obj, 0)))
|
||||
|
||||
|
||||
class StructHolder:
|
||||
"""Proxy object for aligned struct allocation.
|
||||
|
||||
Exposes the aligned pointer as a property and wipes the buffer on deletion.
|
||||
"""
|
||||
|
||||
def __init__(self, ptr: object, view: memoryview):
|
||||
self._ptr = ptr
|
||||
self._view = view # Keep memoryview slice and its bytearray alive
|
||||
|
||||
@property
|
||||
def ptr(self) -> object:
|
||||
"""The aligned pointer to the struct."""
|
||||
return self._ptr
|
||||
|
||||
def __del__(self):
|
||||
wipe(self._view)
|
||||
del self._ptr, self._view
|
||||
|
||||
|
||||
def new_aligned_struct(ctype: str, alignment: int) -> StructHolder:
|
||||
"""Allocate memory for one instance of ``ctype`` with requested alignment."""
|
||||
# Allocate backing storage with extra space for alignment
|
||||
size = ffi.sizeof(ctype)
|
||||
view = memoryview(bytearray(size + alignment - 1))
|
||||
# Compute alignment offset from the base address
|
||||
offset = (-aligned_address(ffi.from_buffer(view))) & (alignment - 1)
|
||||
# Slice the memoryview to the aligned region (keeps bytearray alive)
|
||||
view = view[offset : offset + size]
|
||||
return StructHolder(ffi.from_buffer(f"{ctype} *", view), view)
|
||||
|
||||
|
||||
def nonce_increment(nonce: Buffer) -> None:
|
||||
"""Increment the nonce in place using little-endian byte order.
|
||||
|
||||
Useful for generating unique nonces for each consecutive message.
|
||||
|
||||
Args:
|
||||
nonce: The nonce buffer to increment (modified in place).
|
||||
"""
|
||||
n = memoryview(nonce)
|
||||
for i in range(len(n)):
|
||||
if n[i] < 255:
|
||||
n[i] += 1
|
||||
return
|
||||
n[i] = 0
|
||||
|
||||
|
||||
def wipe(buffer: Buffer) -> None:
|
||||
"""Securely clearing sensitive data from memory. Sets all bytes of the buffer to 0xFF.
|
||||
|
||||
Args:
|
||||
buffer: The buffer to wipe (modified in place).
|
||||
"""
|
||||
# This is the fastest method I have found in Python
|
||||
n = memoryview(buffer).cast("B")
|
||||
n[:] = b"\xff" * len(n)
|
||||
Reference in New Issue
Block a user