API cleanup for simplified update/final. Returns bytearrays when into is not used. Allows into buffers larger than needed. Misc other changes.

This commit is contained in:
2025-11-07 07:38:18 +00:00
parent 2e460b5796
commit cca78f4224
10 changed files with 1277 additions and 1254 deletions
+222
View File
@@ -0,0 +1,222 @@
# pyaegis
[![PyPI version](https://badge.fury.io/py/pyaegis.svg)](https://badge.fury.io/py/pyaegis)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/LeoVasanko/pyaegis/blob/main/libaegis/LICENSE)
Python bindings for libaegis - high-performance AEGIS authenticated encryption.
## Overview
pyaegis provides Python bindings to the AEGIS family of authenticated encryption algorithms implemented in the libaegis C library.
AEGIS is a high-performance authenticated cipher that provides both confidentiality and authenticity guarantees.
### Variant selection
The following submodules are available:
- **aegis128l**: 16-byte key, 16-byte nonce
- **aegis256**: 32-byte key, 32-byte nonce
- **aegis128x2**: 16-byte key, 16-byte nonce (recommended on most platforms)
- **aegis128x4**: 16-byte key, 16-byte nonce (recommended on high-end Intel CPUs)
- **aegis256x2**: 32-byte key, 32-byte nonce
- **aegis256x4**: 32-byte key, 32-byte nonce (recommended if a 256-bit nonce is required)
```python
from pyaegis import aegis128x4
```
### One-shot Functions
- `encrypt(key, nonce, message, ad=None, maclen=16)` - Encrypt with attached MAC
- `decrypt(key, nonce, ciphertext, ad=None, maclen=16)` - Decrypt with attached MAC
- `encrypt_detached(key, nonce, message, ad=None, maclen=16)` - Encrypt with detached MAC
- `decrypt_detached(key, nonce, ciphertext, mac, ad=None, maclen=16)` - Decrypt with detached MAC
- `stream(key, nonce, length)` - Generate pseudo-random bytes
- `encrypt_unauthenticated(key, nonce, message)` - Encrypt without authentication (insecure)
- `decrypt_unauthenticated(key, nonce, ciphertext)` - Decrypt without authentication (insecure)
**Note**: Functions that return buffers (like `encrypt`, `decrypt`, `stream`) return `memoryview` objects. Callers can optionally supply their own buffer via the `into=` keyword argument to avoid memory allocations.
### Incremental Classes
- `Encryptor(key, nonce, ad=None)` - For streaming encryption
- `Decryptor(key, nonce, ad=None)` - For streaming decryption
## Installation
### From PyPI
Using [uv](https://docs.astral.sh/uv/):
```bash
uv pip install pyaegis
```
Or using pip:
```bash
pip install pyaegis
```
### From Source
The package compiles the C library automatically using any installed C compiler:
```bash
# Clone the repository
git clone https://github.com/LeoVasanko/pyaegis.git
cd pyaegis
# Install with uv (compiles C sources automatically)
uv pip install .
# Or for development
uv pip install -e .
```
Alternatively with pip:
```bash
pip install .
# Or for development
pip install -e .
```
### Building a Distribution
```bash
# With uv
uv run python -m build
# Or with pip
python -m build
```
This creates both source and wheel distributions in the `dist/` directory. The C sources are bundled in the package and compiled during installation.
## Usage
All modules (`aegis128l`, `aegis256`, `aegis128x2`, `aegis128x4`, `aegis256x2`, `aegis256x4`) provide the exact same API.
### Basic Encryption/Decryption
```python
import pyaegis.aegis128l as a
key = b"K" * a.KEYBYTES # 16 bytes for aegis128l
nonce = b"N" * a.NPUBBYTES # 16 bytes for aegis128l
plaintext = b"Hello, World!"
ciphertext = a.encrypt(key, nonce, plaintext)
decrypted = a.decrypt(key, nonce, ciphertext)
assert decrypted == plaintext
```
### With Additional Authenticated Data (AAD)
```python
ciphertext = a.encrypt(key, nonce, plaintext, ad=b"metadata")
plaintext = a.decrypt(key, nonce, ciphertext, ad=b"metadata")
```
### Detached Tag Mode
```python
ciphertext, mac = a.encrypt_detached(key, nonce, b"secret")
plaintext = a.decrypt_detached(key, nonce, ciphertext, mac)
```
### Custom MAC Length
Use 16 or 32-byte MACs (default 16):
```python
ciphertext, mac = a.encrypt_detached(key, nonce, message, maclen=32)
plaintext = a.decrypt_detached(key, nonce, ciphertext, mac, maclen=32)
```
### In-Place Operations
```python
buffer = bytearray(b"secret message")
mac = a.encrypt_unauthenticated_into(key, nonce, buffer)
# buffer now contains ciphertext
a.decrypt_unauthenticated_into(key, nonce, buffer, mac)
# buffer now contains plaintext again
```
### Using Pre-allocated Buffers
```python
# Pre-allocate output buffer
output = bytearray(len(plaintext) + 16) # +16 for MAC
result = a.encrypt(key, nonce, plaintext, into=output)
# result is a memoryview of the output buffer
```
### Stream Generation
Generate pseudo-random bytes:
```python
random_bytes = a.stream(key, nonce, 1024)
```
### Incremental Encryption
```python
encryptor = a.Encryptor(key, nonce, ad=b"header")
ciphertext1 = encryptor.update(b"chunk1")
ciphertext2 = encryptor.update(b"chunk2")
final_bytes = encryptor.final() # includes MAC
```
## Performance
The library automatically detects CPU features at runtime and uses the most optimized implementation available:
- AES-NI on Intel/AMD processors
- ARM Crypto Extensions on ARM processors
- AVX2 and AVX-512 for multi-lane variants
- Software fallback for other platforms
Multi-lane variants (X2, X4) provide higher throughput on systems with appropriate SIMD support.
See `examples/benchmark.py` for performance measurements.
## Error Handling
Functions that can fail raise `ValueError` with descriptive messages:
```python
import pyaegis.aegis128l as a
key = b"K" * a.KEYBYTES
nonce = b"N" * a.NPUBBYTES
try:
# This will raise ValueError if authentication fails
plaintext = a.decrypt(key, nonce, tampered_ciphertext)
except ValueError as e:
print(f"Decryption failed: {e}")
```
## Performance
The library automatically detects CPU features at runtime and uses the most optimized implementation available:
- AES-NI on Intel/AMD processors
- ARM Crypto Extensions on ARM processors
- AVX2 and AVX-512 for multi-lane variants
- Software fallback for other platforms
Multi-lane variants (X2, X4) provide higher throughput on systems with appropriate SIMD support.
## Security Considerations
- **Nonce Uniqueness**: Never reuse a nonce with the same key. If you can't maintain a counter, generate random nonces for each message.
- **Key Management**: Generate cryptographically secure keys. Keep keys secret.
- **AAD**: Additional authenticated data is not encrypted but is protected against tampering.
- **MAC vs AEAD**: Use AEAD for encryption needs. MAC-only variants are for authentication without confidentiality.
+173 -208
View File
@@ -10,11 +10,10 @@ Error return codes from the C library raise ValueError.
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY! # DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
import errno import errno
from collections.abc import Buffer
from ._loader import ffi from ._loader import ffi
from ._loader import lib as _lib from ._loader import lib as _lib
from .util import new_aligned_struct from .util import Buffer, new_aligned_struct
# Constants exposed as functions in C; mirror them as integers at module import time # Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis128l_keybytes() KEYBYTES = _lib.aegis128l_keybytes()
@@ -39,7 +38,7 @@ def encrypt_detached(
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
ct_into: Buffer | None = None, ct_into: Buffer | None = None,
mac_into: Buffer | None = None, mac_into: Buffer | None = None,
) -> tuple[memoryview, memoryview]: ) -> tuple[bytearray | memoryview, bytearray | memoryview]:
"""Encrypt message with associated data, returning ciphertext and MAC separately. """Encrypt message with associated data, returning ciphertext and MAC separately.
Args: Args:
@@ -58,35 +57,34 @@ def encrypt_detached(
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
ct_into = memoryview(ct_into) if ct_into is not None else None
mac_into = memoryview(mac_into) if mac_into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
c = ct_into if ct_into is not None else memoryview(bytearray(message.nbytes)) if ct_into is None:
mac = mac_into if mac_into is not None else memoryview(bytearray(maclen)) c = bytearray(len(message))
if c.nbytes != message.nbytes: else:
raise TypeError("into length must equal len(message)") if len(ct_into) < len(message):
if mac.nbytes != maclen: raise TypeError("ct_into length must be at least len(message)")
raise TypeError("mac_into length must equal maclen") 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.aegis128l_encrypt_detached( rc = _lib.aegis128l_encrypt_detached(
ffi.from_buffer(c), ffi.from_buffer(c),
ffi.from_buffer(mac), ffi.from_buffer(mac),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -94,7 +92,10 @@ def encrypt_detached(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt detached failed: {err_name}") raise RuntimeError(f"encrypt detached failed: {err_name}")
return c, mac return (
c if ct_into is None else memoryview(c)[: len(message)],
mac if mac_into is None else memoryview(mac)[:maclen],
)
def decrypt_detached( def decrypt_detached(
@@ -105,7 +106,7 @@ def decrypt_detached(
ad: Buffer | None = None, ad: Buffer | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with detached MAC and associated data. """Decrypt ciphertext with detached MAC and associated data.
Args: Args:
@@ -117,44 +118,40 @@ def decrypt_detached(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
ct = memoryview(ct)
mac = memoryview(mac)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
maclen = mac.nbytes maclen = len(mac)
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if m_out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
rc = _lib.aegis128l_decrypt_detached( rc = _lib.aegis128l_decrypt_detached(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(mac), _ptr(mac),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return memoryview(m_out) return out if into is None else memoryview(out)[: len(ct)] # type: ignore
def encrypt( def encrypt(
@@ -165,7 +162,7 @@ def encrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message with associated data, returning ciphertext with appended MAC. """Encrypt message with associated data, returning ciphertext with appended MAC.
Args: Args:
@@ -177,35 +174,32 @@ def encrypt(
into: Buffer to write ciphertext+MAC into (default: bytearray created). into: Buffer to write ciphertext+MAC into (default: bytearray created).
Returns: Returns:
Ciphertext with appended MAC as bytearray if into not provided. Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes + maclen)) if into is None:
if out.nbytes != message.nbytes + maclen: out = bytearray(len(message) + maclen)
raise TypeError("into length must be 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.aegis128l_encrypt( rc = _lib.aegis128l_encrypt(
ffi.from_buffer(out), ffi.from_buffer(out),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -213,7 +207,7 @@ def encrypt(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt failed: {err_name}") raise RuntimeError(f"encrypt failed: {err_name}")
return out return out if into is None else memoryview(out)[: len(message) + maclen] # type: ignore
def decrypt( def decrypt(
@@ -224,7 +218,7 @@ def decrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with appended MAC and associated data. """Decrypt ciphertext with appended MAC and associated data.
Args: Args:
@@ -236,43 +230,43 @@ def decrypt(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
ct = memoryview(ct)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if ct.nbytes < maclen: if len(ct) < maclen:
raise TypeError("ciphertext too short for tag") raise TypeError("ciphertext too short for tag")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes - maclen)) expected_out = len(ct) - maclen
if m_out.nbytes != ct.nbytes - maclen: if into is None:
raise TypeError("into length must be len(ciphertext_with_tag)-maclen") 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.aegis128l_decrypt( rc = _lib.aegis128l_decrypt(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return m_out return out if into is None else memoryview(out)[:expected_out] # type: ignore
def stream( def stream(
@@ -281,7 +275,7 @@ def stream(
length: int | None = None, length: int | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | Buffer:
"""Generate a stream of pseudorandom bytes. """Generate a stream of pseudorandom bytes.
Args: Args:
@@ -291,29 +285,30 @@ def stream(
into: Buffer to write stream into (default: bytearray created). into: Buffer to write stream into (default: bytearray created).
Returns: Returns:
Pseudorandom bytes as bytearray if into not provided. Pseudorandom bytes as bytearray, or into returned directly.
Raises: Raises:
TypeError: If lengths are invalid or neither length nor into provided. TypeError: If lengths are invalid or neither length nor into provided.
""" """
nonce = memoryview(nonce) if nonce is not None else None if len(key) != KEYBYTES:
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce is not None and nonce.nbytes != NPUBBYTES: if nonce is not None and len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if into is None and length is None: if into is None:
raise TypeError("provide either into or length") if length is None:
out = into if into is not None else memoryview(bytearray(int(length or 0))) 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.aegis128l_stream( _lib.aegis128l_stream(
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: length or len(out)] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
@@ -322,7 +317,7 @@ def encrypt_unauthenticated(
message: Buffer, message: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
Args: Args:
@@ -332,31 +327,29 @@ def encrypt_unauthenticated(
into: Buffer to write ciphertext into (default: bytearray created). into: Buffer to write ciphertext into (default: bytearray created).
Returns: Returns:
Ciphertext as bytearray if into not provided. Ciphertext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
message = memoryview(message) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes)) if into is None:
if out.nbytes != message.nbytes: out = bytearray(len(message))
raise TypeError("into length must equal len(message)") else:
if len(into) < len(message):
raise TypeError("into length must be at least len(message)")
out = into
_lib.aegis128l_encrypt_unauthenticated( _lib.aegis128l_encrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(message)] # type: ignore
def decrypt_unauthenticated( def decrypt_unauthenticated(
@@ -365,7 +358,7 @@ def decrypt_unauthenticated(
ct: Buffer, ct: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
Args: Args:
@@ -375,31 +368,29 @@ def decrypt_unauthenticated(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
ct = memoryview(ct) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
_lib.aegis128l_decrypt_unauthenticated( _lib.aegis128l_decrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(ct)] # type: ignore
# This is missing from C API but convenient to have here # This is missing from C API but convenient to have here
@@ -408,7 +399,8 @@ def mac(
nonce: Buffer, nonce: Buffer,
data: Buffer, data: Buffer,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
) -> memoryview: into: Buffer | None = None,
) -> bytearray | memoryview:
"""Compute a MAC for the given data in one shot. """Compute a MAC for the given data in one shot.
Args: Args:
@@ -416,13 +408,14 @@ def mac(
nonce: Nonce (32 bytes) nonce: Nonce (32 bytes)
data: Data to MAC data: Data to MAC
maclen: MAC length (16 or 32, default 16) maclen: MAC length (16 or 32, default 16)
into: Buffer to write MAC into (default: bytearray created)
Returns: Returns:
MAC bytes MAC bytes as bytearray if into not provided, memoryview of into otherwise
""" """
mac_state = Mac(key, nonce) mac_state = Mac(key, nonce)
mac_state.update(data) mac_state.update(data)
return mac_state.final(maclen) return mac_state.final(maclen, into)
class Mac: class Mac:
@@ -460,11 +453,9 @@ class Mac:
_lib.aegis128l_mac_state_clone(self._st, _other._st) _lib.aegis128l_mac_state_clone(self._st, _other._st)
return return
# Normal init path # Normal init path
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
_lib.aegis128l_mac_init(self._st, _ptr(key), _ptr(nonce)) _lib.aegis128l_mac_init(self._st, _ptr(key), _ptr(nonce))
@@ -487,8 +478,7 @@ class Mac:
Raises: Raises:
RuntimeError: If the underlying C function reports an error. RuntimeError: If the underlying C function reports an error.
""" """
data = memoryview(data) rc = _lib.aegis128l_mac_update(self._st, _ptr(data), len(data))
rc = _lib.aegis128l_mac_update(self._st, _ptr(data), data.nbytes)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
@@ -498,7 +488,7 @@ class Mac:
self, self,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Finalize and return the MAC tag. """Finalize and return the MAC tag.
Args: Args:
@@ -506,7 +496,7 @@ class Mac:
into: Optional buffer to write the tag into (default: bytearray created). into: Optional buffer to write the tag into (default: bytearray created).
Returns: Returns:
The tag as a memoryview; if ``into`` is provided, it views that buffer. The tag as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
@@ -514,16 +504,19 @@ class Mac:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
out = into if into is not None else bytearray(maclen) if into is None:
out = memoryview(out) out = bytearray(maclen)
if out.nbytes != maclen: else:
raise TypeError("into length must equal maclen") if len(into) < maclen:
rc = _lib.aegis128l_mac_final(self._st, ffi.from_buffer(out), maclen) raise TypeError("into length must be at least maclen")
out = into
out_mv = memoryview(out)
rc = _lib.aegis128l_mac_final(self._st, ffi.from_buffer(out_mv), maclen)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"mac final failed: {err_name}") raise RuntimeError(f"mac final failed: {err_name}")
return out return out if into is None else memoryview(out)[:maclen] # type: ignore
def verify(self, mac: Buffer) -> bool: def verify(self, mac: Buffer) -> bool:
"""Verify a tag for the current MAC state. """Verify a tag for the current MAC state.
@@ -538,8 +531,7 @@ class Mac:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If verification fails. ValueError: If verification fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
rc = _lib.aegis128l_mac_verify(self._st, _ptr(mac), maclen) rc = _lib.aegis128l_mac_verify(self._st, _ptr(mac), maclen)
@@ -569,23 +561,20 @@ class Encryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis128l_state", ALIGNMENT) st, owner = new_aligned_struct("aegis128l_state", ALIGNMENT)
_lib.aegis128l_state_init( _lib.aegis128l_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total plaintext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -602,7 +591,9 @@ class Encryptor:
""" """
return self._bytes_out return self._bytes_out
def update(self, message: Buffer, into: Buffer | None = None) -> memoryview: def update(
self, message: Buffer, into: Buffer | None = None
) -> bytearray | memoryview:
"""Encrypt a chunk of the message. """Encrypt a chunk of the message.
Args: Args:
@@ -610,28 +601,27 @@ class Encryptor:
into: Optional destination buffer; must be >= len(message). into: Optional destination buffer; must be >= len(message).
Returns: Returns:
The ciphertext for this chunk as a memoryview; when ``into`` is The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
provided, a view of that buffer up to the number of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
message = memoryview(message) expected_out = len(message)
expected_out = message.nbytes out = into if into is not None else bytearray(expected_out)
out = memoryview(into if into is not None else bytearray(expected_out)) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError( raise TypeError(
"into length must be >= expected output size for this update" "into length must be >= expected output size for this update"
) )
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128l_state_encrypt_update( rc = _lib.aegis128l_state_encrypt_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(message), _ptr(message),
message.nbytes, len(message),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
@@ -641,11 +631,13 @@ class Encryptor:
) )
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out
self._bytes_in += message.nbytes self._bytes_in += len(message)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, into: Buffer | None = None, maclen: int = ABYTES_MIN) -> memoryview: def final(
self, into: Buffer | None = None, maclen: int = ABYTES_MIN
) -> bytearray | memoryview:
"""Finalize encryption, writing any remaining bytes and the tag. """Finalize encryption, writing any remaining bytes and the tag.
Args: Args:
@@ -653,9 +645,7 @@ class Encryptor:
maclen: Tag length (16 or 32). Defaults to 16. maclen: Tag length (16 or 32). Defaults to 16.
Returns: Returns:
A memoryview of the produced bytes (tail + tag). When ``into`` is A memoryview of the produced bytes (tail + tag) if into provided, bytearray slice otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If maclen is invalid. TypeError: If maclen is invalid.
@@ -663,14 +653,13 @@ class Encryptor:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
# Worst-case final length is leftover tail (<= TAILBYTES_MAX) plus tag # Only the authentication tag is produced here; allocate exactly maclen
out = into if into is not None else bytearray(TAILBYTES_MAX + maclen) out = into if into is not None else bytearray(maclen)
out = memoryview(out)
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128l_state_encrypt_final( rc = _lib.aegis128l_state_encrypt_final(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
written, written,
maclen, maclen,
) )
@@ -679,15 +668,18 @@ class Encryptor:
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state encrypt final failed: {err_name}") raise RuntimeError(f"state encrypt final failed: {err_name}")
w = int(written[0]) w = int(written[0])
if into is None:
# Only the tag bytes are returned when we allocate the buffer
assert w == maclen
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
class Decryptor: class Decryptor:
"""Incremental decryptor. """Incremental decryptor.
- update(ciphertext[, into]) -> returns plaintext bytes - update(ciphertext[, into]) -> returns plaintext bytes
- final(mac[, into]) -> returns any remaining plaintext bytes - final(mac) -> verifies the MAC tag
""" """
__slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out") __slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out")
@@ -703,23 +695,20 @@ class Decryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis128l_state", ALIGNMENT) st, owner = new_aligned_struct("aegis128l_state", ALIGNMENT)
_lib.aegis128l_state_init( _lib.aegis128l_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total ciphertext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -730,13 +719,10 @@ class Decryptor:
@property @property
def bytes_out(self) -> int: def bytes_out(self) -> int:
"""Total plaintext bytes produced so far. """Total plaintext bytes produced so far."""
Includes bytes written by update() and by final().
"""
return self._bytes_out return self._bytes_out
def update(self, ct: Buffer, into: Buffer | None = None) -> memoryview: def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
"""Process a chunk of ciphertext. """Process a chunk of ciphertext.
Args: Args:
@@ -744,75 +730,54 @@ class Decryptor:
into: Optional destination buffer; must be >= len(ciphertext). into: Optional destination buffer; must be >= len(ciphertext).
Returns: Returns:
A memoryview of the decrypted bytes for this chunk. When ``into`` is A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
ct = memoryview(ct) expected_out = len(ct)
expected_out = ct.nbytes
out = into if into is not None else bytearray(expected_out) out = into if into is not None else bytearray(expected_out)
out = memoryview(out) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError("into length must be >= required capacity for this update") raise TypeError("into length must be >= required capacity for this update")
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128l_state_decrypt_detached_update( rc = _lib.aegis128l_state_decrypt_detached_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state decrypt update failed: {err_name}") raise RuntimeError(f"state decrypt update failed: {err_name}")
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out, f"got {w}, expected {expected_out}, len(ct)={len(ct)}"
self._bytes_in += ct.nbytes self._bytes_in += len(ct)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, mac: Buffer, into: Buffer | None = None) -> memoryview: def final(self, mac: Buffer) -> None:
"""Finalize decryption by verifying tag and flushing remaining bytes. """Finalize decryption by verifying the MAC tag.
Args: Args:
mac: Tag to verify (16 or 32 bytes). mac: Tag to verify (16 or 32 bytes).
into: Optional destination buffer for the remaining plaintext bytes.
Returns:
A memoryview of the remaining plaintext bytes. When ``into`` is
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
out = into if into is not None else bytearray(TAILBYTES_MAX)
out = memoryview(out)
written = ffi.new("size_t *")
rc = _lib.aegis128l_state_decrypt_detached_final( rc = _lib.aegis128l_state_decrypt_detached_final(
self._st, self._st, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
ffi.from_buffer(out),
out.nbytes,
written,
_ptr(mac),
maclen,
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
w = int(written[0])
self._bytes_out += w
return out[:w]
def new_state(): def new_state():
+173 -208
View File
@@ -10,11 +10,10 @@ Error return codes from the C library raise ValueError.
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY! # DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
import errno import errno
from collections.abc import Buffer
from ._loader import ffi from ._loader import ffi
from ._loader import lib as _lib from ._loader import lib as _lib
from .util import new_aligned_struct from .util import Buffer, new_aligned_struct
# Constants exposed as functions in C; mirror them as integers at module import time # Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis128x2_keybytes() KEYBYTES = _lib.aegis128x2_keybytes()
@@ -39,7 +38,7 @@ def encrypt_detached(
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
ct_into: Buffer | None = None, ct_into: Buffer | None = None,
mac_into: Buffer | None = None, mac_into: Buffer | None = None,
) -> tuple[memoryview, memoryview]: ) -> tuple[bytearray | memoryview, bytearray | memoryview]:
"""Encrypt message with associated data, returning ciphertext and MAC separately. """Encrypt message with associated data, returning ciphertext and MAC separately.
Args: Args:
@@ -58,35 +57,34 @@ def encrypt_detached(
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
ct_into = memoryview(ct_into) if ct_into is not None else None
mac_into = memoryview(mac_into) if mac_into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
c = ct_into if ct_into is not None else memoryview(bytearray(message.nbytes)) if ct_into is None:
mac = mac_into if mac_into is not None else memoryview(bytearray(maclen)) c = bytearray(len(message))
if c.nbytes != message.nbytes: else:
raise TypeError("into length must equal len(message)") if len(ct_into) < len(message):
if mac.nbytes != maclen: raise TypeError("ct_into length must be at least len(message)")
raise TypeError("mac_into length must equal maclen") 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.aegis128x2_encrypt_detached( rc = _lib.aegis128x2_encrypt_detached(
ffi.from_buffer(c), ffi.from_buffer(c),
ffi.from_buffer(mac), ffi.from_buffer(mac),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -94,7 +92,10 @@ def encrypt_detached(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt detached failed: {err_name}") raise RuntimeError(f"encrypt detached failed: {err_name}")
return c, mac return (
c if ct_into is None else memoryview(c)[: len(message)],
mac if mac_into is None else memoryview(mac)[:maclen],
)
def decrypt_detached( def decrypt_detached(
@@ -105,7 +106,7 @@ def decrypt_detached(
ad: Buffer | None = None, ad: Buffer | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with detached MAC and associated data. """Decrypt ciphertext with detached MAC and associated data.
Args: Args:
@@ -117,44 +118,40 @@ def decrypt_detached(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
ct = memoryview(ct)
mac = memoryview(mac)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
maclen = mac.nbytes maclen = len(mac)
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if m_out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
rc = _lib.aegis128x2_decrypt_detached( rc = _lib.aegis128x2_decrypt_detached(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(mac), _ptr(mac),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return memoryview(m_out) return out if into is None else memoryview(out)[: len(ct)] # type: ignore
def encrypt( def encrypt(
@@ -165,7 +162,7 @@ def encrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message with associated data, returning ciphertext with appended MAC. """Encrypt message with associated data, returning ciphertext with appended MAC.
Args: Args:
@@ -177,35 +174,32 @@ def encrypt(
into: Buffer to write ciphertext+MAC into (default: bytearray created). into: Buffer to write ciphertext+MAC into (default: bytearray created).
Returns: Returns:
Ciphertext with appended MAC as bytearray if into not provided. Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes + maclen)) if into is None:
if out.nbytes != message.nbytes + maclen: out = bytearray(len(message) + maclen)
raise TypeError("into length must be 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.aegis128x2_encrypt( rc = _lib.aegis128x2_encrypt(
ffi.from_buffer(out), ffi.from_buffer(out),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -213,7 +207,7 @@ def encrypt(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt failed: {err_name}") raise RuntimeError(f"encrypt failed: {err_name}")
return out return out if into is None else memoryview(out)[: len(message) + maclen] # type: ignore
def decrypt( def decrypt(
@@ -224,7 +218,7 @@ def decrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with appended MAC and associated data. """Decrypt ciphertext with appended MAC and associated data.
Args: Args:
@@ -236,43 +230,43 @@ def decrypt(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
ct = memoryview(ct)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if ct.nbytes < maclen: if len(ct) < maclen:
raise TypeError("ciphertext too short for tag") raise TypeError("ciphertext too short for tag")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes - maclen)) expected_out = len(ct) - maclen
if m_out.nbytes != ct.nbytes - maclen: if into is None:
raise TypeError("into length must be len(ciphertext_with_tag)-maclen") 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.aegis128x2_decrypt( rc = _lib.aegis128x2_decrypt(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return m_out return out if into is None else memoryview(out)[:expected_out] # type: ignore
def stream( def stream(
@@ -281,7 +275,7 @@ def stream(
length: int | None = None, length: int | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | Buffer:
"""Generate a stream of pseudorandom bytes. """Generate a stream of pseudorandom bytes.
Args: Args:
@@ -291,29 +285,30 @@ def stream(
into: Buffer to write stream into (default: bytearray created). into: Buffer to write stream into (default: bytearray created).
Returns: Returns:
Pseudorandom bytes as bytearray if into not provided. Pseudorandom bytes as bytearray, or into returned directly.
Raises: Raises:
TypeError: If lengths are invalid or neither length nor into provided. TypeError: If lengths are invalid or neither length nor into provided.
""" """
nonce = memoryview(nonce) if nonce is not None else None if len(key) != KEYBYTES:
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce is not None and nonce.nbytes != NPUBBYTES: if nonce is not None and len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if into is None and length is None: if into is None:
raise TypeError("provide either into or length") if length is None:
out = into if into is not None else memoryview(bytearray(int(length or 0))) 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.aegis128x2_stream( _lib.aegis128x2_stream(
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: length or len(out)] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
@@ -322,7 +317,7 @@ def encrypt_unauthenticated(
message: Buffer, message: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
Args: Args:
@@ -332,31 +327,29 @@ def encrypt_unauthenticated(
into: Buffer to write ciphertext into (default: bytearray created). into: Buffer to write ciphertext into (default: bytearray created).
Returns: Returns:
Ciphertext as bytearray if into not provided. Ciphertext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
message = memoryview(message) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes)) if into is None:
if out.nbytes != message.nbytes: out = bytearray(len(message))
raise TypeError("into length must equal len(message)") else:
if len(into) < len(message):
raise TypeError("into length must be at least len(message)")
out = into
_lib.aegis128x2_encrypt_unauthenticated( _lib.aegis128x2_encrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(message)] # type: ignore
def decrypt_unauthenticated( def decrypt_unauthenticated(
@@ -365,7 +358,7 @@ def decrypt_unauthenticated(
ct: Buffer, ct: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
Args: Args:
@@ -375,31 +368,29 @@ def decrypt_unauthenticated(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
ct = memoryview(ct) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
_lib.aegis128x2_decrypt_unauthenticated( _lib.aegis128x2_decrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(ct)] # type: ignore
# This is missing from C API but convenient to have here # This is missing from C API but convenient to have here
@@ -408,7 +399,8 @@ def mac(
nonce: Buffer, nonce: Buffer,
data: Buffer, data: Buffer,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
) -> memoryview: into: Buffer | None = None,
) -> bytearray | memoryview:
"""Compute a MAC for the given data in one shot. """Compute a MAC for the given data in one shot.
Args: Args:
@@ -416,13 +408,14 @@ def mac(
nonce: Nonce (32 bytes) nonce: Nonce (32 bytes)
data: Data to MAC data: Data to MAC
maclen: MAC length (16 or 32, default 16) maclen: MAC length (16 or 32, default 16)
into: Buffer to write MAC into (default: bytearray created)
Returns: Returns:
MAC bytes MAC bytes as bytearray if into not provided, memoryview of into otherwise
""" """
mac_state = Mac(key, nonce) mac_state = Mac(key, nonce)
mac_state.update(data) mac_state.update(data)
return mac_state.final(maclen) return mac_state.final(maclen, into)
class Mac: class Mac:
@@ -460,11 +453,9 @@ class Mac:
_lib.aegis128x2_mac_state_clone(self._st, _other._st) _lib.aegis128x2_mac_state_clone(self._st, _other._st)
return return
# Normal init path # Normal init path
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
_lib.aegis128x2_mac_init(self._st, _ptr(key), _ptr(nonce)) _lib.aegis128x2_mac_init(self._st, _ptr(key), _ptr(nonce))
@@ -487,8 +478,7 @@ class Mac:
Raises: Raises:
RuntimeError: If the underlying C function reports an error. RuntimeError: If the underlying C function reports an error.
""" """
data = memoryview(data) rc = _lib.aegis128x2_mac_update(self._st, _ptr(data), len(data))
rc = _lib.aegis128x2_mac_update(self._st, _ptr(data), data.nbytes)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
@@ -498,7 +488,7 @@ class Mac:
self, self,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Finalize and return the MAC tag. """Finalize and return the MAC tag.
Args: Args:
@@ -506,7 +496,7 @@ class Mac:
into: Optional buffer to write the tag into (default: bytearray created). into: Optional buffer to write the tag into (default: bytearray created).
Returns: Returns:
The tag as a memoryview; if ``into`` is provided, it views that buffer. The tag as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
@@ -514,16 +504,19 @@ class Mac:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
out = into if into is not None else bytearray(maclen) if into is None:
out = memoryview(out) out = bytearray(maclen)
if out.nbytes != maclen: else:
raise TypeError("into length must equal maclen") if len(into) < maclen:
rc = _lib.aegis128x2_mac_final(self._st, ffi.from_buffer(out), maclen) raise TypeError("into length must be at least maclen")
out = into
out_mv = memoryview(out)
rc = _lib.aegis128x2_mac_final(self._st, ffi.from_buffer(out_mv), maclen)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"mac final failed: {err_name}") raise RuntimeError(f"mac final failed: {err_name}")
return out return out if into is None else memoryview(out)[:maclen] # type: ignore
def verify(self, mac: Buffer) -> bool: def verify(self, mac: Buffer) -> bool:
"""Verify a tag for the current MAC state. """Verify a tag for the current MAC state.
@@ -538,8 +531,7 @@ class Mac:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If verification fails. ValueError: If verification fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
rc = _lib.aegis128x2_mac_verify(self._st, _ptr(mac), maclen) rc = _lib.aegis128x2_mac_verify(self._st, _ptr(mac), maclen)
@@ -569,23 +561,20 @@ class Encryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis128x2_state", ALIGNMENT) st, owner = new_aligned_struct("aegis128x2_state", ALIGNMENT)
_lib.aegis128x2_state_init( _lib.aegis128x2_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total plaintext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -602,7 +591,9 @@ class Encryptor:
""" """
return self._bytes_out return self._bytes_out
def update(self, message: Buffer, into: Buffer | None = None) -> memoryview: def update(
self, message: Buffer, into: Buffer | None = None
) -> bytearray | memoryview:
"""Encrypt a chunk of the message. """Encrypt a chunk of the message.
Args: Args:
@@ -610,28 +601,27 @@ class Encryptor:
into: Optional destination buffer; must be >= len(message). into: Optional destination buffer; must be >= len(message).
Returns: Returns:
The ciphertext for this chunk as a memoryview; when ``into`` is The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
provided, a view of that buffer up to the number of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
message = memoryview(message) expected_out = len(message)
expected_out = message.nbytes out = into if into is not None else bytearray(expected_out)
out = memoryview(into if into is not None else bytearray(expected_out)) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError( raise TypeError(
"into length must be >= expected output size for this update" "into length must be >= expected output size for this update"
) )
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128x2_state_encrypt_update( rc = _lib.aegis128x2_state_encrypt_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(message), _ptr(message),
message.nbytes, len(message),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
@@ -641,11 +631,13 @@ class Encryptor:
) )
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out
self._bytes_in += message.nbytes self._bytes_in += len(message)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, into: Buffer | None = None, maclen: int = ABYTES_MIN) -> memoryview: def final(
self, into: Buffer | None = None, maclen: int = ABYTES_MIN
) -> bytearray | memoryview:
"""Finalize encryption, writing any remaining bytes and the tag. """Finalize encryption, writing any remaining bytes and the tag.
Args: Args:
@@ -653,9 +645,7 @@ class Encryptor:
maclen: Tag length (16 or 32). Defaults to 16. maclen: Tag length (16 or 32). Defaults to 16.
Returns: Returns:
A memoryview of the produced bytes (tail + tag). When ``into`` is A memoryview of the produced bytes (tail + tag) if into provided, bytearray slice otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If maclen is invalid. TypeError: If maclen is invalid.
@@ -663,14 +653,13 @@ class Encryptor:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
# Worst-case final length is leftover tail (<= TAILBYTES_MAX) plus tag # Only the authentication tag is produced here; allocate exactly maclen
out = into if into is not None else bytearray(TAILBYTES_MAX + maclen) out = into if into is not None else bytearray(maclen)
out = memoryview(out)
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128x2_state_encrypt_final( rc = _lib.aegis128x2_state_encrypt_final(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
written, written,
maclen, maclen,
) )
@@ -679,15 +668,18 @@ class Encryptor:
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state encrypt final failed: {err_name}") raise RuntimeError(f"state encrypt final failed: {err_name}")
w = int(written[0]) w = int(written[0])
if into is None:
# Only the tag bytes are returned when we allocate the buffer
assert w == maclen
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
class Decryptor: class Decryptor:
"""Incremental decryptor. """Incremental decryptor.
- update(ciphertext[, into]) -> returns plaintext bytes - update(ciphertext[, into]) -> returns plaintext bytes
- final(mac[, into]) -> returns any remaining plaintext bytes - final(mac) -> verifies the MAC tag
""" """
__slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out") __slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out")
@@ -703,23 +695,20 @@ class Decryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis128x2_state", ALIGNMENT) st, owner = new_aligned_struct("aegis128x2_state", ALIGNMENT)
_lib.aegis128x2_state_init( _lib.aegis128x2_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total ciphertext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -730,13 +719,10 @@ class Decryptor:
@property @property
def bytes_out(self) -> int: def bytes_out(self) -> int:
"""Total plaintext bytes produced so far. """Total plaintext bytes produced so far."""
Includes bytes written by update() and by final().
"""
return self._bytes_out return self._bytes_out
def update(self, ct: Buffer, into: Buffer | None = None) -> memoryview: def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
"""Process a chunk of ciphertext. """Process a chunk of ciphertext.
Args: Args:
@@ -744,75 +730,54 @@ class Decryptor:
into: Optional destination buffer; must be >= len(ciphertext). into: Optional destination buffer; must be >= len(ciphertext).
Returns: Returns:
A memoryview of the decrypted bytes for this chunk. When ``into`` is A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
ct = memoryview(ct) expected_out = len(ct)
expected_out = ct.nbytes
out = into if into is not None else bytearray(expected_out) out = into if into is not None else bytearray(expected_out)
out = memoryview(out) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError("into length must be >= required capacity for this update") raise TypeError("into length must be >= required capacity for this update")
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128x2_state_decrypt_detached_update( rc = _lib.aegis128x2_state_decrypt_detached_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state decrypt update failed: {err_name}") raise RuntimeError(f"state decrypt update failed: {err_name}")
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out, f"got {w}, expected {expected_out}, len(ct)={len(ct)}"
self._bytes_in += ct.nbytes self._bytes_in += len(ct)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, mac: Buffer, into: Buffer | None = None) -> memoryview: def final(self, mac: Buffer) -> None:
"""Finalize decryption by verifying tag and flushing remaining bytes. """Finalize decryption by verifying the MAC tag.
Args: Args:
mac: Tag to verify (16 or 32 bytes). mac: Tag to verify (16 or 32 bytes).
into: Optional destination buffer for the remaining plaintext bytes.
Returns:
A memoryview of the remaining plaintext bytes. When ``into`` is
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
out = into if into is not None else bytearray(TAILBYTES_MAX)
out = memoryview(out)
written = ffi.new("size_t *")
rc = _lib.aegis128x2_state_decrypt_detached_final( rc = _lib.aegis128x2_state_decrypt_detached_final(
self._st, self._st, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
ffi.from_buffer(out),
out.nbytes,
written,
_ptr(mac),
maclen,
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
w = int(written[0])
self._bytes_out += w
return out[:w]
def new_state(): def new_state():
+173 -208
View File
@@ -10,11 +10,10 @@ Error return codes from the C library raise ValueError.
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY! # DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
import errno import errno
from collections.abc import Buffer
from ._loader import ffi from ._loader import ffi
from ._loader import lib as _lib from ._loader import lib as _lib
from .util import new_aligned_struct from .util import Buffer, new_aligned_struct
# Constants exposed as functions in C; mirror them as integers at module import time # Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis128x4_keybytes() KEYBYTES = _lib.aegis128x4_keybytes()
@@ -39,7 +38,7 @@ def encrypt_detached(
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
ct_into: Buffer | None = None, ct_into: Buffer | None = None,
mac_into: Buffer | None = None, mac_into: Buffer | None = None,
) -> tuple[memoryview, memoryview]: ) -> tuple[bytearray | memoryview, bytearray | memoryview]:
"""Encrypt message with associated data, returning ciphertext and MAC separately. """Encrypt message with associated data, returning ciphertext and MAC separately.
Args: Args:
@@ -58,35 +57,34 @@ def encrypt_detached(
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
ct_into = memoryview(ct_into) if ct_into is not None else None
mac_into = memoryview(mac_into) if mac_into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
c = ct_into if ct_into is not None else memoryview(bytearray(message.nbytes)) if ct_into is None:
mac = mac_into if mac_into is not None else memoryview(bytearray(maclen)) c = bytearray(len(message))
if c.nbytes != message.nbytes: else:
raise TypeError("into length must equal len(message)") if len(ct_into) < len(message):
if mac.nbytes != maclen: raise TypeError("ct_into length must be at least len(message)")
raise TypeError("mac_into length must equal maclen") 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.aegis128x4_encrypt_detached( rc = _lib.aegis128x4_encrypt_detached(
ffi.from_buffer(c), ffi.from_buffer(c),
ffi.from_buffer(mac), ffi.from_buffer(mac),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -94,7 +92,10 @@ def encrypt_detached(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt detached failed: {err_name}") raise RuntimeError(f"encrypt detached failed: {err_name}")
return c, mac return (
c if ct_into is None else memoryview(c)[: len(message)],
mac if mac_into is None else memoryview(mac)[:maclen],
)
def decrypt_detached( def decrypt_detached(
@@ -105,7 +106,7 @@ def decrypt_detached(
ad: Buffer | None = None, ad: Buffer | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with detached MAC and associated data. """Decrypt ciphertext with detached MAC and associated data.
Args: Args:
@@ -117,44 +118,40 @@ def decrypt_detached(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
ct = memoryview(ct)
mac = memoryview(mac)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
maclen = mac.nbytes maclen = len(mac)
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if m_out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
rc = _lib.aegis128x4_decrypt_detached( rc = _lib.aegis128x4_decrypt_detached(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(mac), _ptr(mac),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return memoryview(m_out) return out if into is None else memoryview(out)[: len(ct)] # type: ignore
def encrypt( def encrypt(
@@ -165,7 +162,7 @@ def encrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message with associated data, returning ciphertext with appended MAC. """Encrypt message with associated data, returning ciphertext with appended MAC.
Args: Args:
@@ -177,35 +174,32 @@ def encrypt(
into: Buffer to write ciphertext+MAC into (default: bytearray created). into: Buffer to write ciphertext+MAC into (default: bytearray created).
Returns: Returns:
Ciphertext with appended MAC as bytearray if into not provided. Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes + maclen)) if into is None:
if out.nbytes != message.nbytes + maclen: out = bytearray(len(message) + maclen)
raise TypeError("into length must be 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.aegis128x4_encrypt( rc = _lib.aegis128x4_encrypt(
ffi.from_buffer(out), ffi.from_buffer(out),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -213,7 +207,7 @@ def encrypt(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt failed: {err_name}") raise RuntimeError(f"encrypt failed: {err_name}")
return out return out if into is None else memoryview(out)[: len(message) + maclen] # type: ignore
def decrypt( def decrypt(
@@ -224,7 +218,7 @@ def decrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with appended MAC and associated data. """Decrypt ciphertext with appended MAC and associated data.
Args: Args:
@@ -236,43 +230,43 @@ def decrypt(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
ct = memoryview(ct)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if ct.nbytes < maclen: if len(ct) < maclen:
raise TypeError("ciphertext too short for tag") raise TypeError("ciphertext too short for tag")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes - maclen)) expected_out = len(ct) - maclen
if m_out.nbytes != ct.nbytes - maclen: if into is None:
raise TypeError("into length must be len(ciphertext_with_tag)-maclen") 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.aegis128x4_decrypt( rc = _lib.aegis128x4_decrypt(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return m_out return out if into is None else memoryview(out)[:expected_out] # type: ignore
def stream( def stream(
@@ -281,7 +275,7 @@ def stream(
length: int | None = None, length: int | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | Buffer:
"""Generate a stream of pseudorandom bytes. """Generate a stream of pseudorandom bytes.
Args: Args:
@@ -291,29 +285,30 @@ def stream(
into: Buffer to write stream into (default: bytearray created). into: Buffer to write stream into (default: bytearray created).
Returns: Returns:
Pseudorandom bytes as bytearray if into not provided. Pseudorandom bytes as bytearray, or into returned directly.
Raises: Raises:
TypeError: If lengths are invalid or neither length nor into provided. TypeError: If lengths are invalid or neither length nor into provided.
""" """
nonce = memoryview(nonce) if nonce is not None else None if len(key) != KEYBYTES:
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce is not None and nonce.nbytes != NPUBBYTES: if nonce is not None and len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if into is None and length is None: if into is None:
raise TypeError("provide either into or length") if length is None:
out = into if into is not None else memoryview(bytearray(int(length or 0))) 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.aegis128x4_stream( _lib.aegis128x4_stream(
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: length or len(out)] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
@@ -322,7 +317,7 @@ def encrypt_unauthenticated(
message: Buffer, message: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
Args: Args:
@@ -332,31 +327,29 @@ def encrypt_unauthenticated(
into: Buffer to write ciphertext into (default: bytearray created). into: Buffer to write ciphertext into (default: bytearray created).
Returns: Returns:
Ciphertext as bytearray if into not provided. Ciphertext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
message = memoryview(message) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes)) if into is None:
if out.nbytes != message.nbytes: out = bytearray(len(message))
raise TypeError("into length must equal len(message)") else:
if len(into) < len(message):
raise TypeError("into length must be at least len(message)")
out = into
_lib.aegis128x4_encrypt_unauthenticated( _lib.aegis128x4_encrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(message)] # type: ignore
def decrypt_unauthenticated( def decrypt_unauthenticated(
@@ -365,7 +358,7 @@ def decrypt_unauthenticated(
ct: Buffer, ct: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
Args: Args:
@@ -375,31 +368,29 @@ def decrypt_unauthenticated(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
ct = memoryview(ct) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
_lib.aegis128x4_decrypt_unauthenticated( _lib.aegis128x4_decrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(ct)] # type: ignore
# This is missing from C API but convenient to have here # This is missing from C API but convenient to have here
@@ -408,7 +399,8 @@ def mac(
nonce: Buffer, nonce: Buffer,
data: Buffer, data: Buffer,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
) -> memoryview: into: Buffer | None = None,
) -> bytearray | memoryview:
"""Compute a MAC for the given data in one shot. """Compute a MAC for the given data in one shot.
Args: Args:
@@ -416,13 +408,14 @@ def mac(
nonce: Nonce (32 bytes) nonce: Nonce (32 bytes)
data: Data to MAC data: Data to MAC
maclen: MAC length (16 or 32, default 16) maclen: MAC length (16 or 32, default 16)
into: Buffer to write MAC into (default: bytearray created)
Returns: Returns:
MAC bytes MAC bytes as bytearray if into not provided, memoryview of into otherwise
""" """
mac_state = Mac(key, nonce) mac_state = Mac(key, nonce)
mac_state.update(data) mac_state.update(data)
return mac_state.final(maclen) return mac_state.final(maclen, into)
class Mac: class Mac:
@@ -460,11 +453,9 @@ class Mac:
_lib.aegis128x4_mac_state_clone(self._st, _other._st) _lib.aegis128x4_mac_state_clone(self._st, _other._st)
return return
# Normal init path # Normal init path
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
_lib.aegis128x4_mac_init(self._st, _ptr(key), _ptr(nonce)) _lib.aegis128x4_mac_init(self._st, _ptr(key), _ptr(nonce))
@@ -487,8 +478,7 @@ class Mac:
Raises: Raises:
RuntimeError: If the underlying C function reports an error. RuntimeError: If the underlying C function reports an error.
""" """
data = memoryview(data) rc = _lib.aegis128x4_mac_update(self._st, _ptr(data), len(data))
rc = _lib.aegis128x4_mac_update(self._st, _ptr(data), data.nbytes)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
@@ -498,7 +488,7 @@ class Mac:
self, self,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Finalize and return the MAC tag. """Finalize and return the MAC tag.
Args: Args:
@@ -506,7 +496,7 @@ class Mac:
into: Optional buffer to write the tag into (default: bytearray created). into: Optional buffer to write the tag into (default: bytearray created).
Returns: Returns:
The tag as a memoryview; if ``into`` is provided, it views that buffer. The tag as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
@@ -514,16 +504,19 @@ class Mac:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
out = into if into is not None else bytearray(maclen) if into is None:
out = memoryview(out) out = bytearray(maclen)
if out.nbytes != maclen: else:
raise TypeError("into length must equal maclen") if len(into) < maclen:
rc = _lib.aegis128x4_mac_final(self._st, ffi.from_buffer(out), maclen) raise TypeError("into length must be at least maclen")
out = into
out_mv = memoryview(out)
rc = _lib.aegis128x4_mac_final(self._st, ffi.from_buffer(out_mv), maclen)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"mac final failed: {err_name}") raise RuntimeError(f"mac final failed: {err_name}")
return out return out if into is None else memoryview(out)[:maclen] # type: ignore
def verify(self, mac: Buffer) -> bool: def verify(self, mac: Buffer) -> bool:
"""Verify a tag for the current MAC state. """Verify a tag for the current MAC state.
@@ -538,8 +531,7 @@ class Mac:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If verification fails. ValueError: If verification fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
rc = _lib.aegis128x4_mac_verify(self._st, _ptr(mac), maclen) rc = _lib.aegis128x4_mac_verify(self._st, _ptr(mac), maclen)
@@ -569,23 +561,20 @@ class Encryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis128x4_state", ALIGNMENT) st, owner = new_aligned_struct("aegis128x4_state", ALIGNMENT)
_lib.aegis128x4_state_init( _lib.aegis128x4_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total plaintext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -602,7 +591,9 @@ class Encryptor:
""" """
return self._bytes_out return self._bytes_out
def update(self, message: Buffer, into: Buffer | None = None) -> memoryview: def update(
self, message: Buffer, into: Buffer | None = None
) -> bytearray | memoryview:
"""Encrypt a chunk of the message. """Encrypt a chunk of the message.
Args: Args:
@@ -610,28 +601,27 @@ class Encryptor:
into: Optional destination buffer; must be >= len(message). into: Optional destination buffer; must be >= len(message).
Returns: Returns:
The ciphertext for this chunk as a memoryview; when ``into`` is The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
provided, a view of that buffer up to the number of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
message = memoryview(message) expected_out = len(message)
expected_out = message.nbytes out = into if into is not None else bytearray(expected_out)
out = memoryview(into if into is not None else bytearray(expected_out)) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError( raise TypeError(
"into length must be >= expected output size for this update" "into length must be >= expected output size for this update"
) )
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128x4_state_encrypt_update( rc = _lib.aegis128x4_state_encrypt_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(message), _ptr(message),
message.nbytes, len(message),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
@@ -641,11 +631,13 @@ class Encryptor:
) )
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out
self._bytes_in += message.nbytes self._bytes_in += len(message)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, into: Buffer | None = None, maclen: int = ABYTES_MIN) -> memoryview: def final(
self, into: Buffer | None = None, maclen: int = ABYTES_MIN
) -> bytearray | memoryview:
"""Finalize encryption, writing any remaining bytes and the tag. """Finalize encryption, writing any remaining bytes and the tag.
Args: Args:
@@ -653,9 +645,7 @@ class Encryptor:
maclen: Tag length (16 or 32). Defaults to 16. maclen: Tag length (16 or 32). Defaults to 16.
Returns: Returns:
A memoryview of the produced bytes (tail + tag). When ``into`` is A memoryview of the produced bytes (tail + tag) if into provided, bytearray slice otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If maclen is invalid. TypeError: If maclen is invalid.
@@ -663,14 +653,13 @@ class Encryptor:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
# Worst-case final length is leftover tail (<= TAILBYTES_MAX) plus tag # Only the authentication tag is produced here; allocate exactly maclen
out = into if into is not None else bytearray(TAILBYTES_MAX + maclen) out = into if into is not None else bytearray(maclen)
out = memoryview(out)
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128x4_state_encrypt_final( rc = _lib.aegis128x4_state_encrypt_final(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
written, written,
maclen, maclen,
) )
@@ -679,15 +668,18 @@ class Encryptor:
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state encrypt final failed: {err_name}") raise RuntimeError(f"state encrypt final failed: {err_name}")
w = int(written[0]) w = int(written[0])
if into is None:
# Only the tag bytes are returned when we allocate the buffer
assert w == maclen
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
class Decryptor: class Decryptor:
"""Incremental decryptor. """Incremental decryptor.
- update(ciphertext[, into]) -> returns plaintext bytes - update(ciphertext[, into]) -> returns plaintext bytes
- final(mac[, into]) -> returns any remaining plaintext bytes - final(mac) -> verifies the MAC tag
""" """
__slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out") __slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out")
@@ -703,23 +695,20 @@ class Decryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis128x4_state", ALIGNMENT) st, owner = new_aligned_struct("aegis128x4_state", ALIGNMENT)
_lib.aegis128x4_state_init( _lib.aegis128x4_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total ciphertext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -730,13 +719,10 @@ class Decryptor:
@property @property
def bytes_out(self) -> int: def bytes_out(self) -> int:
"""Total plaintext bytes produced so far. """Total plaintext bytes produced so far."""
Includes bytes written by update() and by final().
"""
return self._bytes_out return self._bytes_out
def update(self, ct: Buffer, into: Buffer | None = None) -> memoryview: def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
"""Process a chunk of ciphertext. """Process a chunk of ciphertext.
Args: Args:
@@ -744,75 +730,54 @@ class Decryptor:
into: Optional destination buffer; must be >= len(ciphertext). into: Optional destination buffer; must be >= len(ciphertext).
Returns: Returns:
A memoryview of the decrypted bytes for this chunk. When ``into`` is A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
ct = memoryview(ct) expected_out = len(ct)
expected_out = ct.nbytes
out = into if into is not None else bytearray(expected_out) out = into if into is not None else bytearray(expected_out)
out = memoryview(out) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError("into length must be >= required capacity for this update") raise TypeError("into length must be >= required capacity for this update")
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis128x4_state_decrypt_detached_update( rc = _lib.aegis128x4_state_decrypt_detached_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state decrypt update failed: {err_name}") raise RuntimeError(f"state decrypt update failed: {err_name}")
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out, f"got {w}, expected {expected_out}, len(ct)={len(ct)}"
self._bytes_in += ct.nbytes self._bytes_in += len(ct)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, mac: Buffer, into: Buffer | None = None) -> memoryview: def final(self, mac: Buffer) -> None:
"""Finalize decryption by verifying tag and flushing remaining bytes. """Finalize decryption by verifying the MAC tag.
Args: Args:
mac: Tag to verify (16 or 32 bytes). mac: Tag to verify (16 or 32 bytes).
into: Optional destination buffer for the remaining plaintext bytes.
Returns:
A memoryview of the remaining plaintext bytes. When ``into`` is
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
out = into if into is not None else bytearray(TAILBYTES_MAX)
out = memoryview(out)
written = ffi.new("size_t *")
rc = _lib.aegis128x4_state_decrypt_detached_final( rc = _lib.aegis128x4_state_decrypt_detached_final(
self._st, self._st, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
ffi.from_buffer(out),
out.nbytes,
written,
_ptr(mac),
maclen,
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
w = int(written[0])
self._bytes_out += w
return out[:w]
def new_state(): def new_state():
+173 -208
View File
@@ -10,11 +10,10 @@ Error return codes from the C library raise ValueError.
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY! # DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
import errno import errno
from collections.abc import Buffer
from ._loader import ffi from ._loader import ffi
from ._loader import lib as _lib from ._loader import lib as _lib
from .util import new_aligned_struct from .util import Buffer, new_aligned_struct
# Constants exposed as functions in C; mirror them as integers at module import time # Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis256_keybytes() KEYBYTES = _lib.aegis256_keybytes()
@@ -39,7 +38,7 @@ def encrypt_detached(
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
ct_into: Buffer | None = None, ct_into: Buffer | None = None,
mac_into: Buffer | None = None, mac_into: Buffer | None = None,
) -> tuple[memoryview, memoryview]: ) -> tuple[bytearray | memoryview, bytearray | memoryview]:
"""Encrypt message with associated data, returning ciphertext and MAC separately. """Encrypt message with associated data, returning ciphertext and MAC separately.
Args: Args:
@@ -58,35 +57,34 @@ def encrypt_detached(
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
ct_into = memoryview(ct_into) if ct_into is not None else None
mac_into = memoryview(mac_into) if mac_into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
c = ct_into if ct_into is not None else memoryview(bytearray(message.nbytes)) if ct_into is None:
mac = mac_into if mac_into is not None else memoryview(bytearray(maclen)) c = bytearray(len(message))
if c.nbytes != message.nbytes: else:
raise TypeError("into length must equal len(message)") if len(ct_into) < len(message):
if mac.nbytes != maclen: raise TypeError("ct_into length must be at least len(message)")
raise TypeError("mac_into length must equal maclen") 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( rc = _lib.aegis256_encrypt_detached(
ffi.from_buffer(c), ffi.from_buffer(c),
ffi.from_buffer(mac), ffi.from_buffer(mac),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -94,7 +92,10 @@ def encrypt_detached(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt detached failed: {err_name}") raise RuntimeError(f"encrypt detached failed: {err_name}")
return c, mac return (
c if ct_into is None else memoryview(c)[: len(message)],
mac if mac_into is None else memoryview(mac)[:maclen],
)
def decrypt_detached( def decrypt_detached(
@@ -105,7 +106,7 @@ def decrypt_detached(
ad: Buffer | None = None, ad: Buffer | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with detached MAC and associated data. """Decrypt ciphertext with detached MAC and associated data.
Args: Args:
@@ -117,44 +118,40 @@ def decrypt_detached(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
ct = memoryview(ct)
mac = memoryview(mac)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
maclen = mac.nbytes maclen = len(mac)
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if m_out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
rc = _lib.aegis256_decrypt_detached( rc = _lib.aegis256_decrypt_detached(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(mac), _ptr(mac),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return memoryview(m_out) return out if into is None else memoryview(out)[: len(ct)] # type: ignore
def encrypt( def encrypt(
@@ -165,7 +162,7 @@ def encrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message with associated data, returning ciphertext with appended MAC. """Encrypt message with associated data, returning ciphertext with appended MAC.
Args: Args:
@@ -177,35 +174,32 @@ def encrypt(
into: Buffer to write ciphertext+MAC into (default: bytearray created). into: Buffer to write ciphertext+MAC into (default: bytearray created).
Returns: Returns:
Ciphertext with appended MAC as bytearray if into not provided. Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes + maclen)) if into is None:
if out.nbytes != message.nbytes + maclen: out = bytearray(len(message) + maclen)
raise TypeError("into length must be 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( rc = _lib.aegis256_encrypt(
ffi.from_buffer(out), ffi.from_buffer(out),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -213,7 +207,7 @@ def encrypt(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt failed: {err_name}") raise RuntimeError(f"encrypt failed: {err_name}")
return out return out if into is None else memoryview(out)[: len(message) + maclen] # type: ignore
def decrypt( def decrypt(
@@ -224,7 +218,7 @@ def decrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with appended MAC and associated data. """Decrypt ciphertext with appended MAC and associated data.
Args: Args:
@@ -236,43 +230,43 @@ def decrypt(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
ct = memoryview(ct)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if ct.nbytes < maclen: if len(ct) < maclen:
raise TypeError("ciphertext too short for tag") raise TypeError("ciphertext too short for tag")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes - maclen)) expected_out = len(ct) - maclen
if m_out.nbytes != ct.nbytes - maclen: if into is None:
raise TypeError("into length must be len(ciphertext_with_tag)-maclen") 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( rc = _lib.aegis256_decrypt(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return m_out return out if into is None else memoryview(out)[:expected_out] # type: ignore
def stream( def stream(
@@ -281,7 +275,7 @@ def stream(
length: int | None = None, length: int | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | Buffer:
"""Generate a stream of pseudorandom bytes. """Generate a stream of pseudorandom bytes.
Args: Args:
@@ -291,29 +285,30 @@ def stream(
into: Buffer to write stream into (default: bytearray created). into: Buffer to write stream into (default: bytearray created).
Returns: Returns:
Pseudorandom bytes as bytearray if into not provided. Pseudorandom bytes as bytearray, or into returned directly.
Raises: Raises:
TypeError: If lengths are invalid or neither length nor into provided. TypeError: If lengths are invalid or neither length nor into provided.
""" """
nonce = memoryview(nonce) if nonce is not None else None if len(key) != KEYBYTES:
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce is not None and nonce.nbytes != NPUBBYTES: if nonce is not None and len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if into is None and length is None: if into is None:
raise TypeError("provide either into or length") if length is None:
out = into if into is not None else memoryview(bytearray(int(length or 0))) 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( _lib.aegis256_stream(
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: length or len(out)] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
@@ -322,7 +317,7 @@ def encrypt_unauthenticated(
message: Buffer, message: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
Args: Args:
@@ -332,31 +327,29 @@ def encrypt_unauthenticated(
into: Buffer to write ciphertext into (default: bytearray created). into: Buffer to write ciphertext into (default: bytearray created).
Returns: Returns:
Ciphertext as bytearray if into not provided. Ciphertext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
message = memoryview(message) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes)) if into is None:
if out.nbytes != message.nbytes: out = bytearray(len(message))
raise TypeError("into length must equal len(message)") else:
if len(into) < len(message):
raise TypeError("into length must be at least len(message)")
out = into
_lib.aegis256_encrypt_unauthenticated( _lib.aegis256_encrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(message)] # type: ignore
def decrypt_unauthenticated( def decrypt_unauthenticated(
@@ -365,7 +358,7 @@ def decrypt_unauthenticated(
ct: Buffer, ct: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
Args: Args:
@@ -375,31 +368,29 @@ def decrypt_unauthenticated(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
ct = memoryview(ct) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
_lib.aegis256_decrypt_unauthenticated( _lib.aegis256_decrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(ct)] # type: ignore
# This is missing from C API but convenient to have here # This is missing from C API but convenient to have here
@@ -408,7 +399,8 @@ def mac(
nonce: Buffer, nonce: Buffer,
data: Buffer, data: Buffer,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
) -> memoryview: into: Buffer | None = None,
) -> bytearray | memoryview:
"""Compute a MAC for the given data in one shot. """Compute a MAC for the given data in one shot.
Args: Args:
@@ -416,13 +408,14 @@ def mac(
nonce: Nonce (32 bytes) nonce: Nonce (32 bytes)
data: Data to MAC data: Data to MAC
maclen: MAC length (16 or 32, default 16) maclen: MAC length (16 or 32, default 16)
into: Buffer to write MAC into (default: bytearray created)
Returns: Returns:
MAC bytes MAC bytes as bytearray if into not provided, memoryview of into otherwise
""" """
mac_state = Mac(key, nonce) mac_state = Mac(key, nonce)
mac_state.update(data) mac_state.update(data)
return mac_state.final(maclen) return mac_state.final(maclen, into)
class Mac: class Mac:
@@ -460,11 +453,9 @@ class Mac:
_lib.aegis256_mac_state_clone(self._st, _other._st) _lib.aegis256_mac_state_clone(self._st, _other._st)
return return
# Normal init path # Normal init path
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
_lib.aegis256_mac_init(self._st, _ptr(key), _ptr(nonce)) _lib.aegis256_mac_init(self._st, _ptr(key), _ptr(nonce))
@@ -487,8 +478,7 @@ class Mac:
Raises: Raises:
RuntimeError: If the underlying C function reports an error. RuntimeError: If the underlying C function reports an error.
""" """
data = memoryview(data) rc = _lib.aegis256_mac_update(self._st, _ptr(data), len(data))
rc = _lib.aegis256_mac_update(self._st, _ptr(data), data.nbytes)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
@@ -498,7 +488,7 @@ class Mac:
self, self,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Finalize and return the MAC tag. """Finalize and return the MAC tag.
Args: Args:
@@ -506,7 +496,7 @@ class Mac:
into: Optional buffer to write the tag into (default: bytearray created). into: Optional buffer to write the tag into (default: bytearray created).
Returns: Returns:
The tag as a memoryview; if ``into`` is provided, it views that buffer. The tag as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
@@ -514,16 +504,19 @@ class Mac:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
out = into if into is not None else bytearray(maclen) if into is None:
out = memoryview(out) out = bytearray(maclen)
if out.nbytes != maclen: else:
raise TypeError("into length must equal maclen") if len(into) < maclen:
rc = _lib.aegis256_mac_final(self._st, ffi.from_buffer(out), maclen) raise TypeError("into length must be at least maclen")
out = into
out_mv = memoryview(out)
rc = _lib.aegis256_mac_final(self._st, ffi.from_buffer(out_mv), maclen)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"mac final failed: {err_name}") raise RuntimeError(f"mac final failed: {err_name}")
return out return out if into is None else memoryview(out)[:maclen] # type: ignore
def verify(self, mac: Buffer) -> bool: def verify(self, mac: Buffer) -> bool:
"""Verify a tag for the current MAC state. """Verify a tag for the current MAC state.
@@ -538,8 +531,7 @@ class Mac:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If verification fails. ValueError: If verification fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
rc = _lib.aegis256_mac_verify(self._st, _ptr(mac), maclen) rc = _lib.aegis256_mac_verify(self._st, _ptr(mac), maclen)
@@ -569,23 +561,20 @@ class Encryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis256_state", ALIGNMENT) st, owner = new_aligned_struct("aegis256_state", ALIGNMENT)
_lib.aegis256_state_init( _lib.aegis256_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total plaintext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -602,7 +591,9 @@ class Encryptor:
""" """
return self._bytes_out return self._bytes_out
def update(self, message: Buffer, into: Buffer | None = None) -> memoryview: def update(
self, message: Buffer, into: Buffer | None = None
) -> bytearray | memoryview:
"""Encrypt a chunk of the message. """Encrypt a chunk of the message.
Args: Args:
@@ -610,28 +601,27 @@ class Encryptor:
into: Optional destination buffer; must be >= len(message). into: Optional destination buffer; must be >= len(message).
Returns: Returns:
The ciphertext for this chunk as a memoryview; when ``into`` is The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
provided, a view of that buffer up to the number of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
message = memoryview(message) expected_out = len(message)
expected_out = message.nbytes out = into if into is not None else bytearray(expected_out)
out = memoryview(into if into is not None else bytearray(expected_out)) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError( raise TypeError(
"into length must be >= expected output size for this update" "into length must be >= expected output size for this update"
) )
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256_state_encrypt_update( rc = _lib.aegis256_state_encrypt_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(message), _ptr(message),
message.nbytes, len(message),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
@@ -641,11 +631,13 @@ class Encryptor:
) )
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out
self._bytes_in += message.nbytes self._bytes_in += len(message)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, into: Buffer | None = None, maclen: int = ABYTES_MIN) -> memoryview: def final(
self, into: Buffer | None = None, maclen: int = ABYTES_MIN
) -> bytearray | memoryview:
"""Finalize encryption, writing any remaining bytes and the tag. """Finalize encryption, writing any remaining bytes and the tag.
Args: Args:
@@ -653,9 +645,7 @@ class Encryptor:
maclen: Tag length (16 or 32). Defaults to 16. maclen: Tag length (16 or 32). Defaults to 16.
Returns: Returns:
A memoryview of the produced bytes (tail + tag). When ``into`` is A memoryview of the produced bytes (tail + tag) if into provided, bytearray slice otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If maclen is invalid. TypeError: If maclen is invalid.
@@ -663,14 +653,13 @@ class Encryptor:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
# Worst-case final length is leftover tail (<= TAILBYTES_MAX) plus tag # Only the authentication tag is produced here; allocate exactly maclen
out = into if into is not None else bytearray(TAILBYTES_MAX + maclen) out = into if into is not None else bytearray(maclen)
out = memoryview(out)
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256_state_encrypt_final( rc = _lib.aegis256_state_encrypt_final(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
written, written,
maclen, maclen,
) )
@@ -679,15 +668,18 @@ class Encryptor:
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state encrypt final failed: {err_name}") raise RuntimeError(f"state encrypt final failed: {err_name}")
w = int(written[0]) w = int(written[0])
if into is None:
# Only the tag bytes are returned when we allocate the buffer
assert w == maclen
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
class Decryptor: class Decryptor:
"""Incremental decryptor. """Incremental decryptor.
- update(ciphertext[, into]) -> returns plaintext bytes - update(ciphertext[, into]) -> returns plaintext bytes
- final(mac[, into]) -> returns any remaining plaintext bytes - final(mac) -> verifies the MAC tag
""" """
__slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out") __slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out")
@@ -703,23 +695,20 @@ class Decryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis256_state", ALIGNMENT) st, owner = new_aligned_struct("aegis256_state", ALIGNMENT)
_lib.aegis256_state_init( _lib.aegis256_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total ciphertext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -730,13 +719,10 @@ class Decryptor:
@property @property
def bytes_out(self) -> int: def bytes_out(self) -> int:
"""Total plaintext bytes produced so far. """Total plaintext bytes produced so far."""
Includes bytes written by update() and by final().
"""
return self._bytes_out return self._bytes_out
def update(self, ct: Buffer, into: Buffer | None = None) -> memoryview: def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
"""Process a chunk of ciphertext. """Process a chunk of ciphertext.
Args: Args:
@@ -744,75 +730,54 @@ class Decryptor:
into: Optional destination buffer; must be >= len(ciphertext). into: Optional destination buffer; must be >= len(ciphertext).
Returns: Returns:
A memoryview of the decrypted bytes for this chunk. When ``into`` is A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
ct = memoryview(ct) expected_out = len(ct)
expected_out = ct.nbytes
out = into if into is not None else bytearray(expected_out) out = into if into is not None else bytearray(expected_out)
out = memoryview(out) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError("into length must be >= required capacity for this update") raise TypeError("into length must be >= required capacity for this update")
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256_state_decrypt_detached_update( rc = _lib.aegis256_state_decrypt_detached_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state decrypt update failed: {err_name}") raise RuntimeError(f"state decrypt update failed: {err_name}")
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out, f"got {w}, expected {expected_out}, len(ct)={len(ct)}"
self._bytes_in += ct.nbytes self._bytes_in += len(ct)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, mac: Buffer, into: Buffer | None = None) -> memoryview: def final(self, mac: Buffer) -> None:
"""Finalize decryption by verifying tag and flushing remaining bytes. """Finalize decryption by verifying the MAC tag.
Args: Args:
mac: Tag to verify (16 or 32 bytes). mac: Tag to verify (16 or 32 bytes).
into: Optional destination buffer for the remaining plaintext bytes.
Returns:
A memoryview of the remaining plaintext bytes. When ``into`` is
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
out = into if into is not None else bytearray(TAILBYTES_MAX)
out = memoryview(out)
written = ffi.new("size_t *")
rc = _lib.aegis256_state_decrypt_detached_final( rc = _lib.aegis256_state_decrypt_detached_final(
self._st, self._st, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
ffi.from_buffer(out),
out.nbytes,
written,
_ptr(mac),
maclen,
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
w = int(written[0])
self._bytes_out += w
return out[:w]
def new_state(): def new_state():
+173 -208
View File
@@ -10,11 +10,10 @@ Error return codes from the C library raise ValueError.
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY! # DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
import errno import errno
from collections.abc import Buffer
from ._loader import ffi from ._loader import ffi
from ._loader import lib as _lib from ._loader import lib as _lib
from .util import new_aligned_struct from .util import Buffer, new_aligned_struct
# Constants exposed as functions in C; mirror them as integers at module import time # Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis256x2_keybytes() KEYBYTES = _lib.aegis256x2_keybytes()
@@ -39,7 +38,7 @@ def encrypt_detached(
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
ct_into: Buffer | None = None, ct_into: Buffer | None = None,
mac_into: Buffer | None = None, mac_into: Buffer | None = None,
) -> tuple[memoryview, memoryview]: ) -> tuple[bytearray | memoryview, bytearray | memoryview]:
"""Encrypt message with associated data, returning ciphertext and MAC separately. """Encrypt message with associated data, returning ciphertext and MAC separately.
Args: Args:
@@ -58,35 +57,34 @@ def encrypt_detached(
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
ct_into = memoryview(ct_into) if ct_into is not None else None
mac_into = memoryview(mac_into) if mac_into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
c = ct_into if ct_into is not None else memoryview(bytearray(message.nbytes)) if ct_into is None:
mac = mac_into if mac_into is not None else memoryview(bytearray(maclen)) c = bytearray(len(message))
if c.nbytes != message.nbytes: else:
raise TypeError("into length must equal len(message)") if len(ct_into) < len(message):
if mac.nbytes != maclen: raise TypeError("ct_into length must be at least len(message)")
raise TypeError("mac_into length must equal maclen") 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.aegis256x2_encrypt_detached( rc = _lib.aegis256x2_encrypt_detached(
ffi.from_buffer(c), ffi.from_buffer(c),
ffi.from_buffer(mac), ffi.from_buffer(mac),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -94,7 +92,10 @@ def encrypt_detached(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt detached failed: {err_name}") raise RuntimeError(f"encrypt detached failed: {err_name}")
return c, mac return (
c if ct_into is None else memoryview(c)[: len(message)],
mac if mac_into is None else memoryview(mac)[:maclen],
)
def decrypt_detached( def decrypt_detached(
@@ -105,7 +106,7 @@ def decrypt_detached(
ad: Buffer | None = None, ad: Buffer | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with detached MAC and associated data. """Decrypt ciphertext with detached MAC and associated data.
Args: Args:
@@ -117,44 +118,40 @@ def decrypt_detached(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
ct = memoryview(ct)
mac = memoryview(mac)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
maclen = mac.nbytes maclen = len(mac)
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if m_out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
rc = _lib.aegis256x2_decrypt_detached( rc = _lib.aegis256x2_decrypt_detached(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(mac), _ptr(mac),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return memoryview(m_out) return out if into is None else memoryview(out)[: len(ct)] # type: ignore
def encrypt( def encrypt(
@@ -165,7 +162,7 @@ def encrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message with associated data, returning ciphertext with appended MAC. """Encrypt message with associated data, returning ciphertext with appended MAC.
Args: Args:
@@ -177,35 +174,32 @@ def encrypt(
into: Buffer to write ciphertext+MAC into (default: bytearray created). into: Buffer to write ciphertext+MAC into (default: bytearray created).
Returns: Returns:
Ciphertext with appended MAC as bytearray if into not provided. Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes + maclen)) if into is None:
if out.nbytes != message.nbytes + maclen: out = bytearray(len(message) + maclen)
raise TypeError("into length must be 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.aegis256x2_encrypt( rc = _lib.aegis256x2_encrypt(
ffi.from_buffer(out), ffi.from_buffer(out),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -213,7 +207,7 @@ def encrypt(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt failed: {err_name}") raise RuntimeError(f"encrypt failed: {err_name}")
return out return out if into is None else memoryview(out)[: len(message) + maclen] # type: ignore
def decrypt( def decrypt(
@@ -224,7 +218,7 @@ def decrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with appended MAC and associated data. """Decrypt ciphertext with appended MAC and associated data.
Args: Args:
@@ -236,43 +230,43 @@ def decrypt(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
ct = memoryview(ct)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if ct.nbytes < maclen: if len(ct) < maclen:
raise TypeError("ciphertext too short for tag") raise TypeError("ciphertext too short for tag")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes - maclen)) expected_out = len(ct) - maclen
if m_out.nbytes != ct.nbytes - maclen: if into is None:
raise TypeError("into length must be len(ciphertext_with_tag)-maclen") 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.aegis256x2_decrypt( rc = _lib.aegis256x2_decrypt(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return m_out return out if into is None else memoryview(out)[:expected_out] # type: ignore
def stream( def stream(
@@ -281,7 +275,7 @@ def stream(
length: int | None = None, length: int | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | Buffer:
"""Generate a stream of pseudorandom bytes. """Generate a stream of pseudorandom bytes.
Args: Args:
@@ -291,29 +285,30 @@ def stream(
into: Buffer to write stream into (default: bytearray created). into: Buffer to write stream into (default: bytearray created).
Returns: Returns:
Pseudorandom bytes as bytearray if into not provided. Pseudorandom bytes as bytearray, or into returned directly.
Raises: Raises:
TypeError: If lengths are invalid or neither length nor into provided. TypeError: If lengths are invalid or neither length nor into provided.
""" """
nonce = memoryview(nonce) if nonce is not None else None if len(key) != KEYBYTES:
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce is not None and nonce.nbytes != NPUBBYTES: if nonce is not None and len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if into is None and length is None: if into is None:
raise TypeError("provide either into or length") if length is None:
out = into if into is not None else memoryview(bytearray(int(length or 0))) 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.aegis256x2_stream( _lib.aegis256x2_stream(
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: length or len(out)] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
@@ -322,7 +317,7 @@ def encrypt_unauthenticated(
message: Buffer, message: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
Args: Args:
@@ -332,31 +327,29 @@ def encrypt_unauthenticated(
into: Buffer to write ciphertext into (default: bytearray created). into: Buffer to write ciphertext into (default: bytearray created).
Returns: Returns:
Ciphertext as bytearray if into not provided. Ciphertext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
message = memoryview(message) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes)) if into is None:
if out.nbytes != message.nbytes: out = bytearray(len(message))
raise TypeError("into length must equal len(message)") else:
if len(into) < len(message):
raise TypeError("into length must be at least len(message)")
out = into
_lib.aegis256x2_encrypt_unauthenticated( _lib.aegis256x2_encrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(message)] # type: ignore
def decrypt_unauthenticated( def decrypt_unauthenticated(
@@ -365,7 +358,7 @@ def decrypt_unauthenticated(
ct: Buffer, ct: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
Args: Args:
@@ -375,31 +368,29 @@ def decrypt_unauthenticated(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
ct = memoryview(ct) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
_lib.aegis256x2_decrypt_unauthenticated( _lib.aegis256x2_decrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(ct)] # type: ignore
# This is missing from C API but convenient to have here # This is missing from C API but convenient to have here
@@ -408,7 +399,8 @@ def mac(
nonce: Buffer, nonce: Buffer,
data: Buffer, data: Buffer,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
) -> memoryview: into: Buffer | None = None,
) -> bytearray | memoryview:
"""Compute a MAC for the given data in one shot. """Compute a MAC for the given data in one shot.
Args: Args:
@@ -416,13 +408,14 @@ def mac(
nonce: Nonce (32 bytes) nonce: Nonce (32 bytes)
data: Data to MAC data: Data to MAC
maclen: MAC length (16 or 32, default 16) maclen: MAC length (16 or 32, default 16)
into: Buffer to write MAC into (default: bytearray created)
Returns: Returns:
MAC bytes MAC bytes as bytearray if into not provided, memoryview of into otherwise
""" """
mac_state = Mac(key, nonce) mac_state = Mac(key, nonce)
mac_state.update(data) mac_state.update(data)
return mac_state.final(maclen) return mac_state.final(maclen, into)
class Mac: class Mac:
@@ -460,11 +453,9 @@ class Mac:
_lib.aegis256x2_mac_state_clone(self._st, _other._st) _lib.aegis256x2_mac_state_clone(self._st, _other._st)
return return
# Normal init path # Normal init path
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
_lib.aegis256x2_mac_init(self._st, _ptr(key), _ptr(nonce)) _lib.aegis256x2_mac_init(self._st, _ptr(key), _ptr(nonce))
@@ -487,8 +478,7 @@ class Mac:
Raises: Raises:
RuntimeError: If the underlying C function reports an error. RuntimeError: If the underlying C function reports an error.
""" """
data = memoryview(data) rc = _lib.aegis256x2_mac_update(self._st, _ptr(data), len(data))
rc = _lib.aegis256x2_mac_update(self._st, _ptr(data), data.nbytes)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
@@ -498,7 +488,7 @@ class Mac:
self, self,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Finalize and return the MAC tag. """Finalize and return the MAC tag.
Args: Args:
@@ -506,7 +496,7 @@ class Mac:
into: Optional buffer to write the tag into (default: bytearray created). into: Optional buffer to write the tag into (default: bytearray created).
Returns: Returns:
The tag as a memoryview; if ``into`` is provided, it views that buffer. The tag as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
@@ -514,16 +504,19 @@ class Mac:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
out = into if into is not None else bytearray(maclen) if into is None:
out = memoryview(out) out = bytearray(maclen)
if out.nbytes != maclen: else:
raise TypeError("into length must equal maclen") if len(into) < maclen:
rc = _lib.aegis256x2_mac_final(self._st, ffi.from_buffer(out), maclen) raise TypeError("into length must be at least maclen")
out = into
out_mv = memoryview(out)
rc = _lib.aegis256x2_mac_final(self._st, ffi.from_buffer(out_mv), maclen)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"mac final failed: {err_name}") raise RuntimeError(f"mac final failed: {err_name}")
return out return out if into is None else memoryview(out)[:maclen] # type: ignore
def verify(self, mac: Buffer) -> bool: def verify(self, mac: Buffer) -> bool:
"""Verify a tag for the current MAC state. """Verify a tag for the current MAC state.
@@ -538,8 +531,7 @@ class Mac:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If verification fails. ValueError: If verification fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
rc = _lib.aegis256x2_mac_verify(self._st, _ptr(mac), maclen) rc = _lib.aegis256x2_mac_verify(self._st, _ptr(mac), maclen)
@@ -569,23 +561,20 @@ class Encryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis256x2_state", ALIGNMENT) st, owner = new_aligned_struct("aegis256x2_state", ALIGNMENT)
_lib.aegis256x2_state_init( _lib.aegis256x2_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total plaintext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -602,7 +591,9 @@ class Encryptor:
""" """
return self._bytes_out return self._bytes_out
def update(self, message: Buffer, into: Buffer | None = None) -> memoryview: def update(
self, message: Buffer, into: Buffer | None = None
) -> bytearray | memoryview:
"""Encrypt a chunk of the message. """Encrypt a chunk of the message.
Args: Args:
@@ -610,28 +601,27 @@ class Encryptor:
into: Optional destination buffer; must be >= len(message). into: Optional destination buffer; must be >= len(message).
Returns: Returns:
The ciphertext for this chunk as a memoryview; when ``into`` is The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
provided, a view of that buffer up to the number of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
message = memoryview(message) expected_out = len(message)
expected_out = message.nbytes out = into if into is not None else bytearray(expected_out)
out = memoryview(into if into is not None else bytearray(expected_out)) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError( raise TypeError(
"into length must be >= expected output size for this update" "into length must be >= expected output size for this update"
) )
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256x2_state_encrypt_update( rc = _lib.aegis256x2_state_encrypt_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(message), _ptr(message),
message.nbytes, len(message),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
@@ -641,11 +631,13 @@ class Encryptor:
) )
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out
self._bytes_in += message.nbytes self._bytes_in += len(message)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, into: Buffer | None = None, maclen: int = ABYTES_MIN) -> memoryview: def final(
self, into: Buffer | None = None, maclen: int = ABYTES_MIN
) -> bytearray | memoryview:
"""Finalize encryption, writing any remaining bytes and the tag. """Finalize encryption, writing any remaining bytes and the tag.
Args: Args:
@@ -653,9 +645,7 @@ class Encryptor:
maclen: Tag length (16 or 32). Defaults to 16. maclen: Tag length (16 or 32). Defaults to 16.
Returns: Returns:
A memoryview of the produced bytes (tail + tag). When ``into`` is A memoryview of the produced bytes (tail + tag) if into provided, bytearray slice otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If maclen is invalid. TypeError: If maclen is invalid.
@@ -663,14 +653,13 @@ class Encryptor:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
# Worst-case final length is leftover tail (<= TAILBYTES_MAX) plus tag # Only the authentication tag is produced here; allocate exactly maclen
out = into if into is not None else bytearray(TAILBYTES_MAX + maclen) out = into if into is not None else bytearray(maclen)
out = memoryview(out)
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256x2_state_encrypt_final( rc = _lib.aegis256x2_state_encrypt_final(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
written, written,
maclen, maclen,
) )
@@ -679,15 +668,18 @@ class Encryptor:
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state encrypt final failed: {err_name}") raise RuntimeError(f"state encrypt final failed: {err_name}")
w = int(written[0]) w = int(written[0])
if into is None:
# Only the tag bytes are returned when we allocate the buffer
assert w == maclen
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
class Decryptor: class Decryptor:
"""Incremental decryptor. """Incremental decryptor.
- update(ciphertext[, into]) -> returns plaintext bytes - update(ciphertext[, into]) -> returns plaintext bytes
- final(mac[, into]) -> returns any remaining plaintext bytes - final(mac) -> verifies the MAC tag
""" """
__slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out") __slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out")
@@ -703,23 +695,20 @@ class Decryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis256x2_state", ALIGNMENT) st, owner = new_aligned_struct("aegis256x2_state", ALIGNMENT)
_lib.aegis256x2_state_init( _lib.aegis256x2_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total ciphertext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -730,13 +719,10 @@ class Decryptor:
@property @property
def bytes_out(self) -> int: def bytes_out(self) -> int:
"""Total plaintext bytes produced so far. """Total plaintext bytes produced so far."""
Includes bytes written by update() and by final().
"""
return self._bytes_out return self._bytes_out
def update(self, ct: Buffer, into: Buffer | None = None) -> memoryview: def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
"""Process a chunk of ciphertext. """Process a chunk of ciphertext.
Args: Args:
@@ -744,75 +730,54 @@ class Decryptor:
into: Optional destination buffer; must be >= len(ciphertext). into: Optional destination buffer; must be >= len(ciphertext).
Returns: Returns:
A memoryview of the decrypted bytes for this chunk. When ``into`` is A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
ct = memoryview(ct) expected_out = len(ct)
expected_out = ct.nbytes
out = into if into is not None else bytearray(expected_out) out = into if into is not None else bytearray(expected_out)
out = memoryview(out) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError("into length must be >= required capacity for this update") raise TypeError("into length must be >= required capacity for this update")
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256x2_state_decrypt_detached_update( rc = _lib.aegis256x2_state_decrypt_detached_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state decrypt update failed: {err_name}") raise RuntimeError(f"state decrypt update failed: {err_name}")
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out, f"got {w}, expected {expected_out}, len(ct)={len(ct)}"
self._bytes_in += ct.nbytes self._bytes_in += len(ct)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, mac: Buffer, into: Buffer | None = None) -> memoryview: def final(self, mac: Buffer) -> None:
"""Finalize decryption by verifying tag and flushing remaining bytes. """Finalize decryption by verifying the MAC tag.
Args: Args:
mac: Tag to verify (16 or 32 bytes). mac: Tag to verify (16 or 32 bytes).
into: Optional destination buffer for the remaining plaintext bytes.
Returns:
A memoryview of the remaining plaintext bytes. When ``into`` is
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
out = into if into is not None else bytearray(TAILBYTES_MAX)
out = memoryview(out)
written = ffi.new("size_t *")
rc = _lib.aegis256x2_state_decrypt_detached_final( rc = _lib.aegis256x2_state_decrypt_detached_final(
self._st, self._st, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
ffi.from_buffer(out),
out.nbytes,
written,
_ptr(mac),
maclen,
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
w = int(written[0])
self._bytes_out += w
return out[:w]
def new_state(): def new_state():
+173 -208
View File
@@ -10,11 +10,10 @@ Error return codes from the C library raise ValueError.
# DO NOT EDIT OTHER ALGORITHM FILES MANUALLY! # DO NOT EDIT OTHER ALGORITHM FILES MANUALLY!
import errno import errno
from collections.abc import Buffer
from ._loader import ffi from ._loader import ffi
from ._loader import lib as _lib from ._loader import lib as _lib
from .util import new_aligned_struct from .util import Buffer, new_aligned_struct
# Constants exposed as functions in C; mirror them as integers at module import time # Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis256x4_keybytes() KEYBYTES = _lib.aegis256x4_keybytes()
@@ -39,7 +38,7 @@ def encrypt_detached(
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
ct_into: Buffer | None = None, ct_into: Buffer | None = None,
mac_into: Buffer | None = None, mac_into: Buffer | None = None,
) -> tuple[memoryview, memoryview]: ) -> tuple[bytearray | memoryview, bytearray | memoryview]:
"""Encrypt message with associated data, returning ciphertext and MAC separately. """Encrypt message with associated data, returning ciphertext and MAC separately.
Args: Args:
@@ -58,35 +57,34 @@ def encrypt_detached(
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
ct_into = memoryview(ct_into) if ct_into is not None else None
mac_into = memoryview(mac_into) if mac_into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
c = ct_into if ct_into is not None else memoryview(bytearray(message.nbytes)) if ct_into is None:
mac = mac_into if mac_into is not None else memoryview(bytearray(maclen)) c = bytearray(len(message))
if c.nbytes != message.nbytes: else:
raise TypeError("into length must equal len(message)") if len(ct_into) < len(message):
if mac.nbytes != maclen: raise TypeError("ct_into length must be at least len(message)")
raise TypeError("mac_into length must equal maclen") 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.aegis256x4_encrypt_detached( rc = _lib.aegis256x4_encrypt_detached(
ffi.from_buffer(c), ffi.from_buffer(c),
ffi.from_buffer(mac), ffi.from_buffer(mac),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -94,7 +92,10 @@ def encrypt_detached(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt detached failed: {err_name}") raise RuntimeError(f"encrypt detached failed: {err_name}")
return c, mac return (
c if ct_into is None else memoryview(c)[: len(message)],
mac if mac_into is None else memoryview(mac)[:maclen],
)
def decrypt_detached( def decrypt_detached(
@@ -105,7 +106,7 @@ def decrypt_detached(
ad: Buffer | None = None, ad: Buffer | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with detached MAC and associated data. """Decrypt ciphertext with detached MAC and associated data.
Args: Args:
@@ -117,44 +118,40 @@ def decrypt_detached(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
ct = memoryview(ct)
mac = memoryview(mac)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
maclen = mac.nbytes maclen = len(mac)
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if m_out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
rc = _lib.aegis256x4_decrypt_detached( rc = _lib.aegis256x4_decrypt_detached(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(mac), _ptr(mac),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return memoryview(m_out) return out if into is None else memoryview(out)[: len(ct)] # type: ignore
def encrypt( def encrypt(
@@ -165,7 +162,7 @@ def encrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message with associated data, returning ciphertext with appended MAC. """Encrypt message with associated data, returning ciphertext with appended MAC.
Args: Args:
@@ -177,35 +174,32 @@ def encrypt(
into: Buffer to write ciphertext+MAC into (default: bytearray created). into: Buffer to write ciphertext+MAC into (default: bytearray created).
Returns: Returns:
Ciphertext with appended MAC as bytearray if into not provided. Ciphertext with appended MAC as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
RuntimeError: If encryption fails. RuntimeError: If encryption fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
message = memoryview(message)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes + maclen)) if into is None:
if out.nbytes != message.nbytes + maclen: out = bytearray(len(message) + maclen)
raise TypeError("into length must be 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.aegis256x4_encrypt( rc = _lib.aegis256x4_encrypt(
ffi.from_buffer(out), ffi.from_buffer(out),
maclen, maclen,
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
@@ -213,7 +207,7 @@ def encrypt(
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"encrypt failed: {err_name}") raise RuntimeError(f"encrypt failed: {err_name}")
return out return out if into is None else memoryview(out)[: len(message) + maclen] # type: ignore
def decrypt( def decrypt(
@@ -224,7 +218,7 @@ def decrypt(
*, *,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext with appended MAC and associated data. """Decrypt ciphertext with appended MAC and associated data.
Args: Args:
@@ -236,43 +230,43 @@ def decrypt(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
nonce = memoryview(nonce)
key = memoryview(key)
ct = memoryview(ct)
ad = memoryview(ad) if ad is not None else None
into = memoryview(into) if into is not None else None
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
if key.nbytes != KEYBYTES: if len(key) != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if ct.nbytes < maclen: if len(ct) < maclen:
raise TypeError("ciphertext too short for tag") raise TypeError("ciphertext too short for tag")
m_out = into if into is not None else memoryview(bytearray(ct.nbytes - maclen)) expected_out = len(ct) - maclen
if m_out.nbytes != ct.nbytes - maclen: if into is None:
raise TypeError("into length must be len(ciphertext_with_tag)-maclen") 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.aegis256x4_decrypt( rc = _lib.aegis256x4_decrypt(
ffi.from_buffer(m_out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
maclen, maclen,
_ptr(ad), _ptr(ad),
0 if ad is None else ad.nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
return m_out return out if into is None else memoryview(out)[:expected_out] # type: ignore
def stream( def stream(
@@ -281,7 +275,7 @@ def stream(
length: int | None = None, length: int | None = None,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | Buffer:
"""Generate a stream of pseudorandom bytes. """Generate a stream of pseudorandom bytes.
Args: Args:
@@ -291,29 +285,30 @@ def stream(
into: Buffer to write stream into (default: bytearray created). into: Buffer to write stream into (default: bytearray created).
Returns: Returns:
Pseudorandom bytes as bytearray if into not provided. Pseudorandom bytes as bytearray, or into returned directly.
Raises: Raises:
TypeError: If lengths are invalid or neither length nor into provided. TypeError: If lengths are invalid or neither length nor into provided.
""" """
nonce = memoryview(nonce) if nonce is not None else None if len(key) != KEYBYTES:
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce is not None and nonce.nbytes != NPUBBYTES: if nonce is not None and len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
if into is None and length is None: if into is None:
raise TypeError("provide either into or length") if length is None:
out = into if into is not None else memoryview(bytearray(int(length or 0))) 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.aegis256x4_stream( _lib.aegis256x4_stream(
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: length or len(out)] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
@@ -322,7 +317,7 @@ def encrypt_unauthenticated(
message: Buffer, message: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
Args: Args:
@@ -332,31 +327,29 @@ def encrypt_unauthenticated(
into: Buffer to write ciphertext into (default: bytearray created). into: Buffer to write ciphertext into (default: bytearray created).
Returns: Returns:
Ciphertext as bytearray if into not provided. Ciphertext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
message = memoryview(message) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(message.nbytes)) if into is None:
if out.nbytes != message.nbytes: out = bytearray(len(message))
raise TypeError("into length must equal len(message)") else:
if len(into) < len(message):
raise TypeError("into length must be at least len(message)")
out = into
_lib.aegis256x4_encrypt_unauthenticated( _lib.aegis256x4_encrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(message), _ptr(message),
message.nbytes, len(message),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(message)] # type: ignore
def decrypt_unauthenticated( def decrypt_unauthenticated(
@@ -365,7 +358,7 @@ def decrypt_unauthenticated(
ct: Buffer, ct: Buffer,
*, *,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
Args: Args:
@@ -375,31 +368,29 @@ def decrypt_unauthenticated(
into: Buffer to write plaintext into (default: bytearray created). into: Buffer to write plaintext into (default: bytearray created).
Returns: Returns:
Plaintext as bytearray if into not provided. Plaintext as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
ct = memoryview(ct) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
key = memoryview(key)
into = memoryview(into) if into is not None else None
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
out = into if into is not None else memoryview(bytearray(ct.nbytes)) if into is None:
if out.nbytes != ct.nbytes: out = bytearray(len(ct))
raise TypeError("into length must equal len(ciphertext)") else:
if len(into) < len(ct):
raise TypeError("into length must be at least len(ciphertext)")
out = into
_lib.aegis256x4_decrypt_unauthenticated( _lib.aegis256x4_decrypt_unauthenticated(
ffi.from_buffer(out), ffi.from_buffer(out),
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
return out return out if into is None else memoryview(out)[: len(ct)] # type: ignore
# This is missing from C API but convenient to have here # This is missing from C API but convenient to have here
@@ -408,7 +399,8 @@ def mac(
nonce: Buffer, nonce: Buffer,
data: Buffer, data: Buffer,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
) -> memoryview: into: Buffer | None = None,
) -> bytearray | memoryview:
"""Compute a MAC for the given data in one shot. """Compute a MAC for the given data in one shot.
Args: Args:
@@ -416,13 +408,14 @@ def mac(
nonce: Nonce (32 bytes) nonce: Nonce (32 bytes)
data: Data to MAC data: Data to MAC
maclen: MAC length (16 or 32, default 16) maclen: MAC length (16 or 32, default 16)
into: Buffer to write MAC into (default: bytearray created)
Returns: Returns:
MAC bytes MAC bytes as bytearray if into not provided, memoryview of into otherwise
""" """
mac_state = Mac(key, nonce) mac_state = Mac(key, nonce)
mac_state.update(data) mac_state.update(data)
return mac_state.final(maclen) return mac_state.final(maclen, into)
class Mac: class Mac:
@@ -460,11 +453,9 @@ class Mac:
_lib.aegis256x4_mac_state_clone(self._st, _other._st) _lib.aegis256x4_mac_state_clone(self._st, _other._st)
return return
# Normal init path # Normal init path
nonce = memoryview(nonce) if len(key) != KEYBYTES:
key = memoryview(key)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
_lib.aegis256x4_mac_init(self._st, _ptr(key), _ptr(nonce)) _lib.aegis256x4_mac_init(self._st, _ptr(key), _ptr(nonce))
@@ -487,8 +478,7 @@ class Mac:
Raises: Raises:
RuntimeError: If the underlying C function reports an error. RuntimeError: If the underlying C function reports an error.
""" """
data = memoryview(data) rc = _lib.aegis256x4_mac_update(self._st, _ptr(data), len(data))
rc = _lib.aegis256x4_mac_update(self._st, _ptr(data), data.nbytes)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
@@ -498,7 +488,7 @@ class Mac:
self, self,
maclen: int = ABYTES_MIN, maclen: int = ABYTES_MIN,
into: Buffer | None = None, into: Buffer | None = None,
) -> memoryview: ) -> bytearray | memoryview:
"""Finalize and return the MAC tag. """Finalize and return the MAC tag.
Args: Args:
@@ -506,7 +496,7 @@ class Mac:
into: Optional buffer to write the tag into (default: bytearray created). into: Optional buffer to write the tag into (default: bytearray created).
Returns: Returns:
The tag as a memoryview; if ``into`` is provided, it views that buffer. The tag as bytearray if into not provided, memoryview of into otherwise.
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
@@ -514,16 +504,19 @@ class Mac:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
out = into if into is not None else bytearray(maclen) if into is None:
out = memoryview(out) out = bytearray(maclen)
if out.nbytes != maclen: else:
raise TypeError("into length must equal maclen") if len(into) < maclen:
rc = _lib.aegis256x4_mac_final(self._st, ffi.from_buffer(out), maclen) raise TypeError("into length must be at least maclen")
out = into
out_mv = memoryview(out)
rc = _lib.aegis256x4_mac_final(self._st, ffi.from_buffer(out_mv), maclen)
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"mac final failed: {err_name}") raise RuntimeError(f"mac final failed: {err_name}")
return out return out if into is None else memoryview(out)[:maclen] # type: ignore
def verify(self, mac: Buffer) -> bool: def verify(self, mac: Buffer) -> bool:
"""Verify a tag for the current MAC state. """Verify a tag for the current MAC state.
@@ -538,8 +531,7 @@ class Mac:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If verification fails. ValueError: If verification fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
rc = _lib.aegis256x4_mac_verify(self._st, _ptr(mac), maclen) rc = _lib.aegis256x4_mac_verify(self._st, _ptr(mac), maclen)
@@ -569,23 +561,20 @@ class Encryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis256x4_state", ALIGNMENT) st, owner = new_aligned_struct("aegis256x4_state", ALIGNMENT)
_lib.aegis256x4_state_init( _lib.aegis256x4_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total plaintext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -602,7 +591,9 @@ class Encryptor:
""" """
return self._bytes_out return self._bytes_out
def update(self, message: Buffer, into: Buffer | None = None) -> memoryview: def update(
self, message: Buffer, into: Buffer | None = None
) -> bytearray | memoryview:
"""Encrypt a chunk of the message. """Encrypt a chunk of the message.
Args: Args:
@@ -610,28 +601,27 @@ class Encryptor:
into: Optional destination buffer; must be >= len(message). into: Optional destination buffer; must be >= len(message).
Returns: Returns:
The ciphertext for this chunk as a memoryview; when ``into`` is The ciphertext for this chunk as bytearray if into not provided, memoryview of into otherwise.
provided, a view of that buffer up to the number of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
message = memoryview(message) expected_out = len(message)
expected_out = message.nbytes out = into if into is not None else bytearray(expected_out)
out = memoryview(into if into is not None else bytearray(expected_out)) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError( raise TypeError(
"into length must be >= expected output size for this update" "into length must be >= expected output size for this update"
) )
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256x4_state_encrypt_update( rc = _lib.aegis256x4_state_encrypt_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(message), _ptr(message),
message.nbytes, len(message),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
@@ -641,11 +631,13 @@ class Encryptor:
) )
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out
self._bytes_in += message.nbytes self._bytes_in += len(message)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, into: Buffer | None = None, maclen: int = ABYTES_MIN) -> memoryview: def final(
self, into: Buffer | None = None, maclen: int = ABYTES_MIN
) -> bytearray | memoryview:
"""Finalize encryption, writing any remaining bytes and the tag. """Finalize encryption, writing any remaining bytes and the tag.
Args: Args:
@@ -653,9 +645,7 @@ class Encryptor:
maclen: Tag length (16 or 32). Defaults to 16. maclen: Tag length (16 or 32). Defaults to 16.
Returns: Returns:
A memoryview of the produced bytes (tail + tag). When ``into`` is A memoryview of the produced bytes (tail + tag) if into provided, bytearray slice otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If maclen is invalid. TypeError: If maclen is invalid.
@@ -663,14 +653,13 @@ class Encryptor:
""" """
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("maclen must be 16 or 32") raise TypeError("maclen must be 16 or 32")
# Worst-case final length is leftover tail (<= TAILBYTES_MAX) plus tag # Only the authentication tag is produced here; allocate exactly maclen
out = into if into is not None else bytearray(TAILBYTES_MAX + maclen) out = into if into is not None else bytearray(maclen)
out = memoryview(out)
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256x4_state_encrypt_final( rc = _lib.aegis256x4_state_encrypt_final(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out),
out.nbytes, len(out),
written, written,
maclen, maclen,
) )
@@ -679,15 +668,18 @@ class Encryptor:
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state encrypt final failed: {err_name}") raise RuntimeError(f"state encrypt final failed: {err_name}")
w = int(written[0]) w = int(written[0])
if into is None:
# Only the tag bytes are returned when we allocate the buffer
assert w == maclen
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
class Decryptor: class Decryptor:
"""Incremental decryptor. """Incremental decryptor.
- update(ciphertext[, into]) -> returns plaintext bytes - update(ciphertext[, into]) -> returns plaintext bytes
- final(mac[, into]) -> returns any remaining plaintext bytes - final(mac) -> verifies the MAC tag
""" """
__slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out") __slots__ = ("_st", "_owner", "_bytes_in", "_bytes_out")
@@ -703,23 +695,20 @@ class Decryptor:
Raises: Raises:
TypeError: If key or nonce lengths are invalid. TypeError: If key or nonce lengths are invalid.
""" """
key = memoryview(key) if len(key) != KEYBYTES:
nonce = memoryview(nonce)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}") raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NPUBBYTES: if len(nonce) != NPUBBYTES:
raise TypeError(f"nonce length must be {NPUBBYTES}") raise TypeError(f"nonce length must be {NPUBBYTES}")
st, owner = new_aligned_struct("aegis256x4_state", ALIGNMENT) st, owner = new_aligned_struct("aegis256x4_state", ALIGNMENT)
_lib.aegis256x4_state_init( _lib.aegis256x4_state_init(
st, st,
_ptr(ad) if ad is not None else ffi.NULL, _ptr(ad) if ad is not None else ffi.NULL,
0 if ad is None else memoryview(ad).nbytes, 0 if ad is None else len(ad),
_ptr(nonce), _ptr(nonce),
_ptr(key), _ptr(key),
) )
self._st = st self._st = st
self._owner = owner self._owner = owner
# Track total ciphertext bytes passed through update() so far
self._bytes_in = 0 self._bytes_in = 0
self._bytes_out = 0 self._bytes_out = 0
@@ -730,13 +719,10 @@ class Decryptor:
@property @property
def bytes_out(self) -> int: def bytes_out(self) -> int:
"""Total plaintext bytes produced so far. """Total plaintext bytes produced so far."""
Includes bytes written by update() and by final().
"""
return self._bytes_out return self._bytes_out
def update(self, ct: Buffer, into: Buffer | None = None) -> memoryview: def update(self, ct: Buffer, into: Buffer | None = None) -> bytearray | memoryview:
"""Process a chunk of ciphertext. """Process a chunk of ciphertext.
Args: Args:
@@ -744,75 +730,54 @@ class Decryptor:
into: Optional destination buffer; must be >= len(ciphertext). into: Optional destination buffer; must be >= len(ciphertext).
Returns: Returns:
A memoryview of the decrypted bytes for this chunk. When ``into`` is A memoryview of the decrypted bytes for this chunk if into provided, bytearray otherwise.
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If destination buffer is too small. TypeError: If destination buffer is too small.
RuntimeError: If the C update call fails. RuntimeError: If the C update call fails.
""" """
ct = memoryview(ct) expected_out = len(ct)
expected_out = ct.nbytes
out = into if into is not None else bytearray(expected_out) out = into if into is not None else bytearray(expected_out)
out = memoryview(out) out_mv = memoryview(out)
if out.nbytes < expected_out: if len(out_mv) < expected_out:
raise TypeError("into length must be >= required capacity for this update") raise TypeError("into length must be >= required capacity for this update")
written = ffi.new("size_t *") written = ffi.new("size_t *")
rc = _lib.aegis256x4_state_decrypt_detached_update( rc = _lib.aegis256x4_state_decrypt_detached_update(
self._st, self._st,
ffi.from_buffer(out), ffi.from_buffer(out_mv),
out.nbytes, len(out_mv),
written, written,
_ptr(ct), _ptr(ct),
ct.nbytes, len(ct),
) )
if rc != 0: if rc != 0:
err_num = ffi.errno err_num = ffi.errno
err_name = errno.errorcode.get(err_num, f"errno_{err_num}") err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
raise RuntimeError(f"state decrypt update failed: {err_name}") raise RuntimeError(f"state decrypt update failed: {err_name}")
w = int(written[0]) w = int(written[0])
assert w == expected_out assert w == expected_out, f"got {w}, expected {expected_out}, len(ct)={len(ct)}"
self._bytes_in += ct.nbytes self._bytes_in += len(ct)
self._bytes_out += w self._bytes_out += w
return out[:w] return out if into is None else memoryview(out)[:w] # type: ignore
def final(self, mac: Buffer, into: Buffer | None = None) -> memoryview: def final(self, mac: Buffer) -> None:
"""Finalize decryption by verifying tag and flushing remaining bytes. """Finalize decryption by verifying the MAC tag.
Args: Args:
mac: Tag to verify (16 or 32 bytes). mac: Tag to verify (16 or 32 bytes).
into: Optional destination buffer for the remaining plaintext bytes.
Returns:
A memoryview of the remaining plaintext bytes. When ``into`` is
provided, the returned view references that buffer up to the number
of bytes written.
Raises: Raises:
TypeError: If tag length is invalid. TypeError: If tag length is invalid.
ValueError: If authentication fails. ValueError: If authentication fails.
""" """
mac = memoryview(mac) maclen = len(mac)
maclen = mac.nbytes
if maclen not in (16, 32): if maclen not in (16, 32):
raise TypeError("mac length must be 16 or 32") raise TypeError("mac length must be 16 or 32")
out = into if into is not None else bytearray(TAILBYTES_MAX)
out = memoryview(out)
written = ffi.new("size_t *")
rc = _lib.aegis256x4_state_decrypt_detached_final( rc = _lib.aegis256x4_state_decrypt_detached_final(
self._st, self._st, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
ffi.from_buffer(out),
out.nbytes,
written,
_ptr(mac),
maclen,
) )
if rc != 0: if rc != 0:
raise ValueError("authentication failed") raise ValueError("authentication failed")
w = int(written[0])
self._bytes_out += w
return out[:w]
def new_state(): def new_state():
+14 -1
View File
@@ -6,9 +6,22 @@ on libc/posix_memalign. Memory is owned by Python; C code only borrows it.
from __future__ import annotations from __future__ import annotations
from typing import Protocol
from ._loader import ffi from ._loader import ffi
__all__ = ["new_aligned_struct", "aligned_address"] __all__ = ["new_aligned_struct", "aligned_address", "Buffer"]
try:
from collections.abc import Buffer as _Buffer
class Buffer(_Buffer, Protocol): # type: ignore[misc]
def __len__(self) -> int: ...
except ImportError:
class Buffer(Protocol):
def __len__(self) -> int: ...
def __buffer__(self, flags: int) -> memoryview: ...
def aligned_address(obj) -> int: def aligned_address(obj) -> int:
+2 -4
View File
@@ -174,8 +174,7 @@ def test_encrypt_decrypt_incremental(vector):
pt_chunks = [] pt_chunks = []
for chunk in random_split_bytes(computed_ct): for chunk in random_split_bytes(computed_ct):
pt_chunks.append(bytes(decryptor.update(chunk))) pt_chunks.append(bytes(decryptor.update(chunk)))
final_pt = decryptor.final(expected_tag128) decryptor.final(expected_tag128)
pt_chunks.append(bytes(final_pt))
# Combine plaintext chunks # Combine plaintext chunks
computed_pt = b"".join(pt_chunks) computed_pt = b"".join(pt_chunks)
@@ -215,8 +214,7 @@ def test_encrypt_decrypt_incremental(vector):
pt_chunks = [] pt_chunks = []
for chunk in random_split_bytes(computed_ct): for chunk in random_split_bytes(computed_ct):
pt_chunks.append(bytes(decryptor.update(chunk))) pt_chunks.append(bytes(decryptor.update(chunk)))
final_pt = decryptor.final(expected_tag256) decryptor.final(expected_tag256)
pt_chunks.append(bytes(final_pt))
# Combine plaintext chunks # Combine plaintext chunks
computed_pt = b"".join(pt_chunks) computed_pt = b"".join(pt_chunks)