From b15174af8b8bdceb68c4edc5ba6ea8ad97d3ed65 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Fri, 7 Nov 2025 07:43:33 -0600 Subject: [PATCH] Make random_key and random_nonce return bytearray, and add a nonce_increment utility function. --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ pyaegis/aegis128l.py | 14 +++++++++----- pyaegis/aegis128x2.py | 14 +++++++++----- pyaegis/aegis128x4.py | 14 +++++++++----- pyaegis/aegis256.py | 14 +++++++++----- pyaegis/aegis256x2.py | 14 +++++++++----- pyaegis/aegis256x4.py | 14 +++++++++----- pyaegis/util.py | 18 +++++++++++++++++- 8 files changed, 112 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 6c96787..481faee 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,47 @@ Run the built-in benchmark to see which variant is fastest on your machine: python -m pyaegis.benchmark ``` +Benchmarks of the Python module and the C library run on Intel i7-14700, linux, single core (the software is not multithreaded). Note that the results are in megabits per second, not bytes. The CPU lacks AVX-512 that makes the X4 variants faster on AMD hardware. + + + + + + +
+```bash +$ python -m pyaegis.benchmark +AEGIS-256 107666.56 Mb/s +AEGIS-256X2 191314.53 Mb/s +AEGIS-256X4 211537.44 Mb/s +AEGIS-128L 159074.08 Mb/s +AEGIS-128X2 307332.53 Mb/s +AEGIS-128X4 230106.70 Mb/s +AEGIS-128L MAC 206082.24 Mb/s +AEGIS-128X2 MAC 366401.20 Mb/s +AEGIS-128X4 MAC 375011.51 Mb/s +AEGIS-256 MAC 110187.03 Mb/s +AEGIS-256X2 MAC 210063.51 Mb/s +AEGIS-256X4 MAC 347406.96 Mb/s +``` + +```bash +$ ./libaegis/zig-out/bin/benchmark +AEGIS-256 107820.86 Mb/s +AEGIS-256X2 205025.57 Mb/s +AEGIS-256X4 223361.81 Mb/s +AEGIS-128L 187530.77 Mb/s +AEGIS-128X2 354003.14 Mb/s +AEGIS-128X4 218596.59 Mb/s +AEGIS-128L MAC 224276.49 Mb/s +AEGIS-128X2 MAC 417741.65 Mb/s +AEGIS-128X4 MAC 410454.05 Mb/s +AEGIS-256 MAC 116776.62 Mb/s +AEGIS-256X2 MAC 224150.04 Mb/s +AEGIS-256X4 MAC 392088.05 Mb/s +``` +
+ ## Errors - Authentication failures raise ValueError. diff --git a/pyaegis/aegis128l.py b/pyaegis/aegis128l.py index f02986b..a32adbd 100644 --- a/pyaegis/aegis128l.py +++ b/pyaegis/aegis128l.py @@ -7,7 +7,7 @@ import secrets from ._loader import ffi from ._loader import lib as _lib -from .util import Buffer, new_aligned_struct +from .util import Buffer, new_aligned_struct, nonce_inc_inplace # Constants exposed as functions in C; mirror them as integers at module import time KEYBYTES = _lib.aegis128l_keybytes() @@ -19,14 +19,14 @@ ALIGNMENT = 32 RATE = 32 -def random_key() -> bytes: +def random_key() -> bytearray: """Generate a random key using cryptographically secure random bytes.""" - return secrets.token_bytes(KEYBYTES) + return bytearray(secrets.token_bytes(KEYBYTES)) -def random_nonce() -> bytes: +def random_nonce() -> bytearray: """Generate a random nonce using cryptographically secure random bytes.""" - return secrets.token_bytes(NPUBBYTES) + return bytearray(secrets.token_bytes(NPUBBYTES)) def _ptr(buf): @@ -802,6 +802,10 @@ __all__ = [ "TAILBYTES_MAX", "ALIGNMENT", "RATE", + # utility functions + "random_key", + "random_nonce", + "nonce_inc_inplace", # one-shot functions "encrypt_detached", "decrypt_detached", diff --git a/pyaegis/aegis128x2.py b/pyaegis/aegis128x2.py index 4f1aa7c..8d9b9b1 100644 --- a/pyaegis/aegis128x2.py +++ b/pyaegis/aegis128x2.py @@ -7,7 +7,7 @@ import secrets from ._loader import ffi from ._loader import lib as _lib -from .util import Buffer, new_aligned_struct +from .util import Buffer, new_aligned_struct, nonce_inc_inplace # Constants exposed as functions in C; mirror them as integers at module import time KEYBYTES = _lib.aegis128x2_keybytes() @@ -19,14 +19,14 @@ ALIGNMENT = 64 RATE = 64 -def random_key() -> bytes: +def random_key() -> bytearray: """Generate a random key using cryptographically secure random bytes.""" - return secrets.token_bytes(KEYBYTES) + return bytearray(secrets.token_bytes(KEYBYTES)) -def random_nonce() -> bytes: +def random_nonce() -> bytearray: """Generate a random nonce using cryptographically secure random bytes.""" - return secrets.token_bytes(NPUBBYTES) + return bytearray(secrets.token_bytes(NPUBBYTES)) def _ptr(buf): @@ -802,6 +802,10 @@ __all__ = [ "TAILBYTES_MAX", "ALIGNMENT", "RATE", + # utility functions + "random_key", + "random_nonce", + "nonce_inc_inplace", # one-shot functions "encrypt_detached", "decrypt_detached", diff --git a/pyaegis/aegis128x4.py b/pyaegis/aegis128x4.py index d2a8b6c..4bcb31f 100644 --- a/pyaegis/aegis128x4.py +++ b/pyaegis/aegis128x4.py @@ -7,7 +7,7 @@ import secrets from ._loader import ffi from ._loader import lib as _lib -from .util import Buffer, new_aligned_struct +from .util import Buffer, new_aligned_struct, nonce_inc_inplace # Constants exposed as functions in C; mirror them as integers at module import time KEYBYTES = _lib.aegis128x4_keybytes() @@ -19,14 +19,14 @@ ALIGNMENT = 64 RATE = 128 -def random_key() -> bytes: +def random_key() -> bytearray: """Generate a random key using cryptographically secure random bytes.""" - return secrets.token_bytes(KEYBYTES) + return bytearray(secrets.token_bytes(KEYBYTES)) -def random_nonce() -> bytes: +def random_nonce() -> bytearray: """Generate a random nonce using cryptographically secure random bytes.""" - return secrets.token_bytes(NPUBBYTES) + return bytearray(secrets.token_bytes(NPUBBYTES)) def _ptr(buf): @@ -802,6 +802,10 @@ __all__ = [ "TAILBYTES_MAX", "ALIGNMENT", "RATE", + # utility functions + "random_key", + "random_nonce", + "nonce_inc_inplace", # one-shot functions "encrypt_detached", "decrypt_detached", diff --git a/pyaegis/aegis256.py b/pyaegis/aegis256.py index 22675b7..989487c 100644 --- a/pyaegis/aegis256.py +++ b/pyaegis/aegis256.py @@ -7,7 +7,7 @@ import secrets from ._loader import ffi from ._loader import lib as _lib -from .util import Buffer, new_aligned_struct +from .util import Buffer, new_aligned_struct, nonce_inc_inplace # Constants exposed as functions in C; mirror them as integers at module import time KEYBYTES = _lib.aegis256_keybytes() @@ -19,14 +19,14 @@ ALIGNMENT = 16 RATE = 16 -def random_key() -> bytes: +def random_key() -> bytearray: """Generate a random key using cryptographically secure random bytes.""" - return secrets.token_bytes(KEYBYTES) + return bytearray(secrets.token_bytes(KEYBYTES)) -def random_nonce() -> bytes: +def random_nonce() -> bytearray: """Generate a random nonce using cryptographically secure random bytes.""" - return secrets.token_bytes(NPUBBYTES) + return bytearray(secrets.token_bytes(NPUBBYTES)) def _ptr(buf): @@ -802,6 +802,10 @@ __all__ = [ "TAILBYTES_MAX", "ALIGNMENT", "RATE", + # utility functions + "random_key", + "random_nonce", + "nonce_inc_inplace", # one-shot functions "encrypt_detached", "decrypt_detached", diff --git a/pyaegis/aegis256x2.py b/pyaegis/aegis256x2.py index 6e6ac9c..24a1d62 100644 --- a/pyaegis/aegis256x2.py +++ b/pyaegis/aegis256x2.py @@ -7,7 +7,7 @@ import secrets from ._loader import ffi from ._loader import lib as _lib -from .util import Buffer, new_aligned_struct +from .util import Buffer, new_aligned_struct, nonce_inc_inplace # Constants exposed as functions in C; mirror them as integers at module import time KEYBYTES = _lib.aegis256x2_keybytes() @@ -19,14 +19,14 @@ ALIGNMENT = 32 RATE = 32 -def random_key() -> bytes: +def random_key() -> bytearray: """Generate a random key using cryptographically secure random bytes.""" - return secrets.token_bytes(KEYBYTES) + return bytearray(secrets.token_bytes(KEYBYTES)) -def random_nonce() -> bytes: +def random_nonce() -> bytearray: """Generate a random nonce using cryptographically secure random bytes.""" - return secrets.token_bytes(NPUBBYTES) + return bytearray(secrets.token_bytes(NPUBBYTES)) def _ptr(buf): @@ -802,6 +802,10 @@ __all__ = [ "TAILBYTES_MAX", "ALIGNMENT", "RATE", + # utility functions + "random_key", + "random_nonce", + "nonce_inc_inplace", # one-shot functions "encrypt_detached", "decrypt_detached", diff --git a/pyaegis/aegis256x4.py b/pyaegis/aegis256x4.py index 774db88..20b2525 100644 --- a/pyaegis/aegis256x4.py +++ b/pyaegis/aegis256x4.py @@ -7,7 +7,7 @@ import secrets from ._loader import ffi from ._loader import lib as _lib -from .util import Buffer, new_aligned_struct +from .util import Buffer, new_aligned_struct, nonce_inc_inplace # Constants exposed as functions in C; mirror them as integers at module import time KEYBYTES = _lib.aegis256x4_keybytes() @@ -19,14 +19,14 @@ ALIGNMENT = 64 RATE = 64 -def random_key() -> bytes: +def random_key() -> bytearray: """Generate a random key using cryptographically secure random bytes.""" - return secrets.token_bytes(KEYBYTES) + return bytearray(secrets.token_bytes(KEYBYTES)) -def random_nonce() -> bytes: +def random_nonce() -> bytearray: """Generate a random nonce using cryptographically secure random bytes.""" - return secrets.token_bytes(NPUBBYTES) + return bytearray(secrets.token_bytes(NPUBBYTES)) def _ptr(buf): @@ -802,6 +802,10 @@ __all__ = [ "TAILBYTES_MAX", "ALIGNMENT", "RATE", + # utility functions + "random_key", + "random_nonce", + "nonce_inc_inplace", # one-shot functions "encrypt_detached", "decrypt_detached", diff --git a/pyaegis/util.py b/pyaegis/util.py index 41ad4f0..8523929 100644 --- a/pyaegis/util.py +++ b/pyaegis/util.py @@ -10,7 +10,7 @@ from typing import Protocol from ._loader import ffi -__all__ = ["new_aligned_struct", "aligned_address", "Buffer"] +__all__ = ["new_aligned_struct", "aligned_address", "Buffer", "nonce_increment"] try: from collections.abc import Buffer as _Buffer @@ -45,3 +45,19 @@ def new_aligned_struct(ctype: str, alignment: int) -> tuple[object, object]: aligned_uc = ffi.addressof(base, offset) ptr = ffi.cast(f"{ctype} *", aligned_uc) return ptr, base + + +def nonce_increment(nonce: Buffer) -> None: + """Increment the nonce in place using little-endian byte order. + + Useful for generating unique nonces for each consecutive message. + + Args: + nonce: The nonce buffer to increment (modified in place). + """ + n = memoryview(nonce) + for i in range(len(n)): + if n[i] < 255: + n[i] += 1 + return + n[i] = 0