817 lines
26 KiB
Python
817 lines
26 KiB
Python
"""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 ._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")
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != NONCEBYTES:
|
|
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
|
|
|
if ct_into is None:
|
|
c = bytearray(len(message))
|
|
else:
|
|
if len(ct_into) < len(message):
|
|
raise TypeError("ct_into length must be at least len(message)")
|
|
c = ct_into
|
|
if mac_into is None:
|
|
mac = bytearray(maclen)
|
|
else:
|
|
if len(mac_into) < 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),
|
|
len(message),
|
|
_ptr(ad),
|
|
0 if ad is None else len(ad),
|
|
_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)[: len(message)],
|
|
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.
|
|
"""
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != NONCEBYTES:
|
|
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
|
maclen = len(mac)
|
|
if maclen not in (16, 32):
|
|
raise TypeError("mac length must be 16 or 32")
|
|
if into is None:
|
|
out = bytearray(len(ct))
|
|
else:
|
|
if len(into) < len(ct):
|
|
raise TypeError("into length must be at least len(ciphertext)")
|
|
out = into
|
|
|
|
rc = _lib.aegis256_decrypt_detached(
|
|
ffi.from_buffer(out),
|
|
_ptr(ct),
|
|
len(ct),
|
|
_ptr(mac),
|
|
maclen,
|
|
_ptr(ad),
|
|
0 if ad is None else len(ad),
|
|
_ptr(nonce),
|
|
_ptr(key),
|
|
)
|
|
if rc != 0:
|
|
raise ValueError("authentication failed")
|
|
return out if into is None else memoryview(out)[: len(ct)] # 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")
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != NONCEBYTES:
|
|
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
|
if into is None:
|
|
out = bytearray(len(message) + maclen)
|
|
else:
|
|
if len(into) < len(message) + maclen:
|
|
raise TypeError("into length must be at least len(message)+maclen")
|
|
out = into
|
|
|
|
rc = _lib.aegis256_encrypt(
|
|
ffi.from_buffer(out),
|
|
maclen,
|
|
_ptr(message),
|
|
len(message),
|
|
_ptr(ad),
|
|
0 if ad is None else len(ad),
|
|
_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)[: len(message) + 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")
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != NONCEBYTES:
|
|
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
|
if len(ct) < maclen:
|
|
raise TypeError("ciphertext too short for tag")
|
|
expected_out = len(ct) - maclen
|
|
if into is None:
|
|
out = bytearray(expected_out)
|
|
else:
|
|
if len(into) < expected_out:
|
|
raise TypeError(
|
|
"into length must be at least len(ciphertext_with_tag)-maclen"
|
|
)
|
|
out = into
|
|
|
|
rc = _lib.aegis256_decrypt(
|
|
ffi.from_buffer(out),
|
|
_ptr(ct),
|
|
len(ct),
|
|
maclen,
|
|
_ptr(ad),
|
|
0 if ad is None else len(ad),
|
|
_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.
|
|
"""
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if nonce is not None and len(nonce) != 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 len(into) < length:
|
|
raise TypeError("into length must be at least length")
|
|
out = into
|
|
_lib.aegis256_stream(
|
|
ffi.from_buffer(out),
|
|
len(out),
|
|
_ptr(nonce),
|
|
_ptr(key),
|
|
)
|
|
return out if into is None else memoryview(out)[: length or len(out)] # 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.
|
|
"""
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != NONCEBYTES:
|
|
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
|
if into is None:
|
|
out = bytearray(len(message))
|
|
else:
|
|
if len(into) < len(message):
|
|
raise TypeError("into length must be at least len(message)")
|
|
out = into
|
|
_lib.aegis256_encrypt_unauthenticated(
|
|
ffi.from_buffer(out),
|
|
_ptr(message),
|
|
len(message),
|
|
_ptr(nonce),
|
|
_ptr(key),
|
|
)
|
|
return out if into is None else memoryview(out)[: len(message)] # 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.
|
|
"""
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != NONCEBYTES:
|
|
raise TypeError(f"nonce length must be {NONCEBYTES}")
|
|
if into is None:
|
|
out = bytearray(len(ct))
|
|
else:
|
|
if len(into) < len(ct):
|
|
raise TypeError("into length must be at least len(ciphertext)")
|
|
out = into
|
|
_lib.aegis256_decrypt_unauthenticated(
|
|
ffi.from_buffer(out),
|
|
_ptr(ct),
|
|
len(ct),
|
|
_ptr(nonce),
|
|
_ptr(key),
|
|
)
|
|
return out if into is None else memoryview(out)[: len(ct)] # 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
|
|
"""
|
|
mac_state = Mac(key, nonce, maclen)
|
|
mac_state.update(data)
|
|
return mac_state.final(into)
|
|
|
|
|
|
class Mac:
|
|
"""AEGIS-256 MAC state wrapper.
|
|
|
|
Example:
|
|
a = Mac(key, nonce)
|
|
a.update(data)
|
|
mac = a.final()
|
|
"""
|
|
|
|
__slots__ = ("_proxy", "_maclen")
|
|
|
|
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")
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != 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))
|
|
|
|
def reset(self) -> None:
|
|
"""Reset back to the original state, prior to any updates."""
|
|
_lib.aegis256_mac_reset(self._proxy.ptr)
|
|
|
|
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)
|
|
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.
|
|
"""
|
|
rc = _lib.aegis256_mac_update(self._proxy.ptr, _ptr(data), len(data))
|
|
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.
|
|
|
|
Unlike the C library, this method does not alter the current state,
|
|
allowing for multiple calls and further updates on the same object.
|
|
|
|
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.
|
|
"""
|
|
maclen = self._maclen
|
|
if into is None:
|
|
out = bytearray(maclen)
|
|
else:
|
|
if len(into) < 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), maclen)
|
|
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}")
|
|
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
|
|
|
def digest(self) -> bytes:
|
|
"""Calculate and return the MAC tag as bytes."""
|
|
return bytes(self.final())
|
|
|
|
def hexdigest(self) -> str:
|
|
"""Calculate and return the MAC tag as a hex string."""
|
|
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.
|
|
"""
|
|
maclen = len(mac)
|
|
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")
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != 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 len(ad),
|
|
_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()")
|
|
expected_out = len(message)
|
|
out = into if into is not None else bytearray(expected_out)
|
|
out_mv = memoryview(out)
|
|
if len(out_mv) < 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),
|
|
len(out_mv),
|
|
written,
|
|
_ptr(message),
|
|
len(message),
|
|
)
|
|
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
|
|
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),
|
|
len(out),
|
|
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")
|
|
if len(key) != KEYBYTES:
|
|
raise TypeError(f"key length must be {KEYBYTES}")
|
|
if len(nonce) != 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 len(ad),
|
|
_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()")
|
|
expected_out = len(ct)
|
|
out = into if into is not None else bytearray(expected_out)
|
|
out_mv = memoryview(out)
|
|
if len(out_mv) < 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),
|
|
len(out_mv),
|
|
written,
|
|
_ptr(ct),
|
|
len(ct),
|
|
)
|
|
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}, len(ct)={len(ct)}"
|
|
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
|
|
if len(mac) != 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",
|
|
]
|