* [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4
@ 2026-11-12 12:54 Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 1/5] lib/crypto: tests: Make the AEAD dst clearing check effective Antonio Quartulli
` (6 more replies)
0 siblings, 7 replies; 12+ messages in thread
From: Antonio Quartulli @ 2026-11-12 12:54 UTC (permalink / raw)
To: linux-crypto
Cc: Antonio Quartulli, Eric Biggers, Ard Biesheuvel,
Jason A . Donenfeld, Herbert Xu
From: Antonio Quartulli <antonio@mandelbit.com>
This series adds AEGIS-128X4, the 4-way parallel variant of AEGIS-128L
specified in RFC 10032 (Informational, September 2026), as a crypto
library with an x86_64 VAES-AVX512 implementation, a KUnit test suite
and benchmark, and a crypto API wrapper.
AEGIS-128X4 runs four AEGIS-128L instances, one per 128-bit lane of a
512-bit vector, so that each state update is eight VAESENC instructions
on zmm registers. On CPUs with VAES and AVX-512 it is very fast even for
short messages, which is what makes it attractive for network protocols.
The intended user is ovpn, the OpenVPN data channel offload driver. In
single-stream ovpn tests over a 100 Gbit/s link, with both ciphers used
through the crypto API, the sending CPU spent 1266 cycles per packet with
AEGIS-128X4 against 1448 with AES-GCM over TCP, and 1032 against 1122
with UDP GSO input. It is posted as an RFC because ovpn can't use it
yet.
The library mirrors the AES-GCM one: one-shot encryption and decryption,
and an incremental API for nonlinear data. The generic code keeps the
state in the same layout as the assembly, so the incremental functions
can switch between the two between any two calls, as needed when the FPU
isn't usable.
Encryption throughput from the KUnit benchmark on a Ryzen 9 9950X, in
MB/s:
data_len generic VAES-AVX512
16 10 581
64 41 2351
256 159 8986
1420 576 34521
4096 987 46761
16384 1308 79588
Open questions:
- There is no AVX2 code yet. AEGIS-128X4 can also run with VAES on
ymm registers, two per state word, which would cover CPUs without
AVX-512; for now they use the generic code. For the same reason,
unlike AES-GCM, the AVX-512 code is used on CPUs with
X86_FEATURE_PREFER_YMM too. Should an AVX2 version be part of this
series?
- Is the crypto API wrapper (patch 5) wanted? ovpn currently uses the
crypto API for AES-GCM and ChaCha20-Poly1305 (but it could call the
library directly).
- The assembly is a port of the libaegis AVX-512 code by Frank Denis
from C intrinsics; as libaegis is MIT licensed, the file is
dual-licensed GPL-2.0-only OR MIT and keeps his copyright line.
Patch 1 is independent of the rest: the AEAD test template's check that
decryption clears dst on authentication failure can't fail with its
all-zero plaintext. AES-GCM and AES-CCM pass it with a random one.
Testing: the KUnit suite passes (RFC 10032 test vectors, the shared AEAD
test cases and a Monte Carlo checksum from an independent Python
implementation in gen-aead-testvecs.py) with the VAES-AVX512 code, with
it built in but AVX-512 hidden from the guest, and with the generic code
only. The crypto API self-tests pass with CRYPTO_SELFTESTS_FULL.
Antonio Quartulli (5):
lib/crypto: tests: Make the AEAD dst clearing check effective
lib/crypto: aegis128x4: Add AEGIS-128X4 library
lib/crypto: tests: Add KUnit test suite for AEGIS-128X4
lib/crypto: x86/aegis128x4: Add VAES-AVX512 optimized code
crypto: aegis128x4 - Add AEGIS-128X4 AEAD algorithm
crypto/Kconfig | 11 +
crypto/Makefile | 1 +
crypto/aegis128x4.c | 173 ++++++++
crypto/testmgr.c | 7 +
crypto/testmgr.h | 119 ++++++
include/crypto/aegis128x4.h | 275 +++++++++++++
lib/crypto/.kunitconfig | 1 +
lib/crypto/Kconfig | 14 +
lib/crypto/Makefile | 9 +
lib/crypto/aegis128x4.c | 509 ++++++++++++++++++++++++
lib/crypto/tests/Kconfig | 10 +
lib/crypto/tests/Makefile | 1 +
lib/crypto/tests/aead-test-template.h | 5 +
lib/crypto/tests/aegis128x4_kunit.c | 352 ++++++++++++++++
lib/crypto/x86/aegis128x4-vaes-avx512.S | 470 ++++++++++++++++++++++
lib/crypto/x86/aegis128x4.h | 167 ++++++++
scripts/crypto/gen-aead-testvecs.py | 166 +++++++-
17 files changed, 2284 insertions(+), 6 deletions(-)
create mode 100644 crypto/aegis128x4.c
create mode 100644 include/crypto/aegis128x4.h
create mode 100644 lib/crypto/aegis128x4.c
create mode 100644 lib/crypto/tests/aegis128x4_kunit.c
create mode 100644 lib/crypto/x86/aegis128x4-vaes-avx512.S
create mode 100644 lib/crypto/x86/aegis128x4.h
base-commit: 33b4f31b1aea3f62b61c892daefc0e6a77859032
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH 1/5] lib/crypto: tests: Make the AEAD dst clearing check effective
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
@ 2026-11-12 12:54 ` Antonio Quartulli
2026-11-13 3:07 ` Eric Biggers
2026-11-12 12:54 ` [RFC PATCH 2/5] lib/crypto: aegis128x4: Add AEGIS-128X4 library Antonio Quartulli
` (5 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Antonio Quartulli @ 2026-11-12 12:54 UTC (permalink / raw)
To: linux-crypto
Cc: Antonio Quartulli, Eric Biggers, Ard Biesheuvel,
Jason A . Donenfeld, Herbert Xu
From: Antonio Quartulli <antonio@mandelbit.com>
The basic validation test checks that decryption clears dst on
authentication failure, but its plaintext is all zeroes, so a
decryption that leaves the recovered plaintext in dst passes the check
too. Use a random plaintext instead.
Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
---
lib/crypto/tests/aead-test-template.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lib/crypto/tests/aead-test-template.h b/lib/crypto/tests/aead-test-template.h
index 0c0138d2fa8a..894a927c6f39 100644
--- a/lib/crypto/tests/aead-test-template.h
+++ b/lib/crypto/tests/aead-test-template.h
@@ -257,6 +257,11 @@ aead_alloc_basic_validation_test_ctx(struct kunit *test)
alloc_buf(test, sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
+ /*
+ * Use a nonzero plaintext, so that the check that decryption clears
+ * dst on authentication failure can actually fail.
+ */
+ rand_bytes(ctx->pt, sizeof(ctx->pt));
ctx->raw_key_buf_end =
aead_alloc_random_data_guarded(test, AEAD_MAX_KEY_LEN) +
AEAD_MAX_KEY_LEN;
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [RFC PATCH 2/5] lib/crypto: aegis128x4: Add AEGIS-128X4 library
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 1/5] lib/crypto: tests: Make the AEAD dst clearing check effective Antonio Quartulli
@ 2026-11-12 12:54 ` Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 3/5] lib/crypto: tests: Add KUnit test suite for AEGIS-128X4 Antonio Quartulli
` (4 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Antonio Quartulli @ 2026-11-12 12:54 UTC (permalink / raw)
To: linux-crypto
Cc: Antonio Quartulli, Eric Biggers, Ard Biesheuvel,
Jason A . Donenfeld, Herbert Xu
From: Antonio Quartulli <antonio@mandelbit.com>
Add a library implementation of AEGIS-128X4, the 4-way parallel variant
of AEGIS-128L specified in RFC 10032. The API mirrors the AES-GCM one:
one-shot encryption and decryption, plus an incremental API for callers
with nonlinear data.
The state keeps the four AEGIS-128L instances interleaved by 16-byte
block, the layout of a 512-bit vector implementation, so that
architecture-optimized code can take over between any two calls.
Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
---
include/crypto/aegis128x4.h | 275 +++++++++++++++++++
lib/crypto/Kconfig | 9 +
lib/crypto/Makefile | 5 +
lib/crypto/aegis128x4.c | 509 ++++++++++++++++++++++++++++++++++++
4 files changed, 798 insertions(+)
create mode 100644 include/crypto/aegis128x4.h
create mode 100644 lib/crypto/aegis128x4.c
diff --git a/include/crypto/aegis128x4.h b/include/crypto/aegis128x4.h
new file mode 100644
index 000000000000..e89054a24470
--- /dev/null
+++ b/include/crypto/aegis128x4.h
@@ -0,0 +1,275 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * AEGIS-128X4 authenticated encryption and decryption (RFC 10032)
+ *
+ * Copyright 2026 Antonio Quartulli <antonio@mandelbit.com>
+ */
+#ifndef _CRYPTO_AEGIS128X4_H
+#define _CRYPTO_AEGIS128X4_H
+
+#include <linux/string.h>
+#include <linux/types.h>
+
+#define AEGIS128X4_KEY_SIZE 16
+#define AEGIS128X4_NONCE_SIZE 16
+#define AEGIS128X4_MAX_AUTHTAG_SIZE 32
+
+/* Number of parallel AEGIS-128L instances */
+#define AEGIS128X4_LANES 4
+
+/* Bytes absorbed and en/decrypted by each state update */
+#define AEGIS128X4_BLOCK_SIZE (32 * AEGIS128X4_LANES)
+
+/**
+ * struct aegis128x4_key - A key prepared for AEGIS-128X4
+ */
+struct aegis128x4_key {
+ /* private: */
+ u8 key[AEGIS128X4_KEY_SIZE];
+ size_t authtag_len; /* Length of authentication tags in bytes */
+};
+
+/**
+ * aegis128x4_zeroize_key() - Zeroize an aegis128x4_key structure
+ * @key: The aegis128x4_key to zeroize
+ */
+static inline void aegis128x4_zeroize_key(struct aegis128x4_key *key)
+{
+ memzero_explicit(key, sizeof(*key));
+}
+
+/*
+ * The state: eight words, each holding the corresponding 16-byte block of the
+ * AEGIS-128L instance i at byte offset 16 * i.
+ */
+union aegis128x4_state {
+ u8 b[8][16 * AEGIS128X4_LANES];
+ __le32 w32[8][4 * AEGIS128X4_LANES];
+ u64 w64[8][2 * AEGIS128X4_LANES];
+};
+
+/**
+ * struct aegis128x4_ctx - Context for incrementally en/decrypting a message
+ */
+struct aegis128x4_ctx {
+ /* private: */
+ union aegis128x4_state state;
+ /*
+ * The associated data or the plaintext of the incomplete block, which
+ * is absorbed once complete or at the end of its phase
+ */
+ u8 buf[AEGIS128X4_BLOCK_SIZE] __aligned(__alignof__(u64));
+ /*
+ * Pointer to the key, which is assumed to live at least as long as this
+ * struct.
+ */
+ const struct aegis128x4_key *key;
+ /* Number of associated data bytes processed so far */
+ u64 ad_len;
+ /* Number of en/decrypted bytes processed so far */
+ u64 data_len;
+ /* Whether the associated data was absorbed and en/decryption began */
+ bool data;
+};
+
+/**
+ * aegis128x4_zeroize_ctx() - Zeroize an aegis128x4_ctx structure
+ * @ctx: The aegis128x4_ctx to zeroize
+ */
+static inline void aegis128x4_zeroize_ctx(struct aegis128x4_ctx *ctx)
+{
+ memzero_explicit(ctx, sizeof(*ctx));
+}
+
+/**
+ * aegis128x4_preparekey() - Prepare a key for AEGIS-128X4
+ * @key: (output) The key structure to initialize
+ * @in_key: The raw AEGIS-128X4 key
+ * @key_len: Length of the raw key in bytes: 16
+ * @authtag_len: Length of the authentication tag in bytes: 16 or 32
+ *
+ * Users should use aegis128x4_zeroize_key() to zeroize the key struct at the
+ * end of its lifetime. (But if this function fails, zeroization is
+ * unnecessary.)
+ *
+ * Context: Any context.
+ * Return:
+ * * 0 on success
+ * * -EINVAL if either of the lengths is invalid
+ */
+int __must_check aegis128x4_preparekey(struct aegis128x4_key *key,
+ const u8 *in_key, size_t key_len,
+ size_t authtag_len);
+
+/**
+ * aegis128x4_encrypt() - Encrypt a message with AEGIS-128X4
+ * @dst: The destination ciphertext data. Can be in-place or out-of-place.
+ * For other overlaps the behavior is unspecified.
+ * @src: The source plaintext data
+ * @data_len: Length of plaintext in bytes (and ciphertext excluding the tag):
+ * less than 2^61
+ * @authtag: The output authentication tag. Length is the authtag_len that was
+ * passed to aegis128x4_preparekey(). Usually protocols put the tag at
+ * the end of the ciphertext, in which case this should be set to
+ * @dst + @data_len and @dst must have room for the tag.
+ * @ad: The associated data
+ * @ad_len: Length of associated data in bytes: less than 2^61
+ * @nonce: The 16-byte nonce. All (key, nonce) pairs used MUST be distinct.
+ * @key: The key, already prepared using aegis128x4_preparekey()
+ *
+ * Context: Any context.
+ */
+void aegis128x4_encrypt(u8 *dst, const u8 *src, size_t data_len, u8 *authtag,
+ const u8 *ad, size_t ad_len,
+ const u8 nonce[at_least AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key);
+
+/**
+ * aegis128x4_decrypt() - Decrypt a message with AEGIS-128X4
+ * @dst: The destination plaintext data. Can be in-place or out-of-place.
+ * For other overlaps the behavior is unspecified.
+ * @src: The source ciphertext data
+ * @data_len: Length of plaintext in bytes (and ciphertext excluding the tag):
+ * less than 2^61
+ * @authtag: The stored authentication tag. Length is the authtag_len that was
+ * passed to aegis128x4_preparekey(). Usually protocols put the tag at
+ * the end of the ciphertext, in which case this should be set to
+ * @src + @data_len.
+ * @ad: The associated data
+ * @ad_len: Length of associated data in bytes: less than 2^61
+ * @nonce: The 16-byte nonce
+ * @key: The key, already prepared using aegis128x4_preparekey()
+ *
+ * Context: Any context.
+ * Return:
+ * * 0 on success. This is the only case where any decrypted or associated data
+ * can be used.
+ * * -EBADMSG if the message is inauthentic
+ */
+int __must_check aegis128x4_decrypt(u8 *dst, const u8 *src, size_t data_len,
+ const u8 *authtag, const u8 *ad,
+ size_t ad_len,
+ const u8 nonce[at_least AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key);
+
+/**
+ * aegis128x4_init() - Initialize context for incremental AEGIS-128X4
+ * encryption or decryption
+ * @ctx: The context to initialize
+ * @nonce: The 16-byte nonce. All (key, nonce) pairs used for encryption MUST
+ * be distinct.
+ * @key: The key, already prepared using aegis128x4_preparekey(). Note that a
+ * pointer to the key is saved in the context, so the key must live at
+ * least as long as the context.
+ *
+ * The context should be zeroized at the end of its lifetime. Normally that
+ * happens in aegis128x4_encrypt_final() or aegis128x4_decrypt_final(), but
+ * callers that abandon a context without finalizing it should explicitly
+ * zeroize it.
+ *
+ * IMPORTANT: Callers that are decrypting data MUST NOT assume that any
+ * decrypted or associated data is authentic until the authentication tag has
+ * been verified. This incremental API is provided solely to support callers
+ * that can't efficiently use the one-shot functions due to using a nonlinear
+ * data layout.
+ *
+ * For incremental AEGIS-128X4 encryption, use:
+ *
+ * 1. aegis128x4_init()
+ * 2. aegis128x4_auth_update() (any number of times)
+ * 3. aegis128x4_encrypt_update() (any number of times)
+ * 4. aegis128x4_encrypt_final()
+ *
+ * For incremental AEGIS-128X4 decryption, use:
+ *
+ * 1. aegis128x4_init()
+ * 2. aegis128x4_auth_update() (any number of times)
+ * 3. aegis128x4_decrypt_update() (any number of times)
+ * 4. aegis128x4_decrypt_final()
+ *
+ * Context: Any context.
+ */
+void aegis128x4_init(struct aegis128x4_ctx *ctx,
+ const u8 nonce[at_least AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key);
+
+/**
+ * aegis128x4_auth_update() - Incrementally process AEGIS-128X4 associated data
+ * @ctx: An AEGIS-128X4 context
+ * @ad: The associated data
+ * @len: Number of bytes provided. The caller must ensure that the total
+ * associated data length stays below 2^61.
+ *
+ * IMPORTANT: Callers MUST NOT assume that any decrypted or associated data is
+ * authentic until the authentication tag has been verified.
+ *
+ * Context: Any context.
+ */
+void aegis128x4_auth_update(struct aegis128x4_ctx *ctx, const u8 *ad,
+ size_t len);
+
+/**
+ * aegis128x4_encrypt_update() - Incrementally encrypt data with AEGIS-128X4
+ * @ctx: An AEGIS-128X4 context
+ * @dst: The destination buffer. Can be in-place or out-of-place. For other
+ * overlaps the behavior is unspecified.
+ * @src: The source plaintext data
+ * @len: Number of bytes to encrypt. The caller must ensure that the total
+ * number of bytes encrypted stays below 2^61.
+ *
+ * This can be called only after all associated data has been processed.
+ *
+ * Context: Any context.
+ */
+void aegis128x4_encrypt_update(struct aegis128x4_ctx *ctx, u8 *dst,
+ const u8 *src, size_t len);
+
+/**
+ * aegis128x4_decrypt_update() - Incrementally decrypt data with AEGIS-128X4
+ * @ctx: An AEGIS-128X4 context
+ * @dst: The destination buffer. Can be in-place or out-of-place. For other
+ * overlaps the behavior is unspecified.
+ * @src: The source ciphertext data (not including auth tag)
+ * @len: Number of bytes to decrypt. The caller must ensure that the total
+ * number of bytes decrypted stays below 2^61.
+ *
+ * This can be called only after all associated data has been processed.
+ *
+ * IMPORTANT: Callers MUST NOT assume that any decrypted or associated data is
+ * authentic until the authentication tag has been verified.
+ *
+ * Context: Any context.
+ */
+void aegis128x4_decrypt_update(struct aegis128x4_ctx *ctx, u8 *dst,
+ const u8 *src, size_t len);
+
+/**
+ * aegis128x4_encrypt_final() - Finish encrypting a message with AEGIS-128X4
+ * @ctx: An AEGIS-128X4 context
+ * @authtag: The output authentication tag. Length is the authtag_len that was
+ * passed to aegis128x4_preparekey().
+ *
+ * This also zeroizes @ctx, so the caller doesn't need to do it.
+ *
+ * Context: Any context.
+ */
+void aegis128x4_encrypt_final(struct aegis128x4_ctx *ctx, u8 *authtag);
+
+/**
+ * aegis128x4_decrypt_final() - Finish decrypting a message with AEGIS-128X4
+ * @ctx: An AEGIS-128X4 context
+ * @authtag: The stored authentication tag. Length is the authtag_len that was
+ * passed to aegis128x4_preparekey().
+ *
+ * This also zeroizes @ctx, so the caller doesn't need to do it.
+ *
+ * Context: Any context.
+ * Return:
+ * * 0 on success. This is the only case where any decrypted or associated data
+ * can be used.
+ * * -EBADMSG if the message is inauthentic
+ */
+int __must_check aegis128x4_decrypt_final(struct aegis128x4_ctx *ctx,
+ const u8 *authtag);
+
+#endif /* _CRYPTO_AEGIS128X4_H */
diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig
index 4644dee78ae3..3e53dd570b78 100644
--- a/lib/crypto/Kconfig
+++ b/lib/crypto/Kconfig
@@ -6,6 +6,15 @@ config CRYPTO_HASH_INFO
config CRYPTO_LIB_UTILS
tristate
+config CRYPTO_LIB_AEGIS128X4
+ tristate
+ select CRYPTO_LIB_AES
+ select CRYPTO_LIB_UTILS
+ help
+ The AEGIS-128X4 authenticated encryption algorithm (RFC 10032).
+ Select this if your module uses any of the functions from
+ <crypto/aegis128x4.h>.
+
config CRYPTO_LIB_AES
tristate
# Select dependencies of modes that are part of libaes.
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
index 171b926afc39..be43ab974c0f 100644
--- a/lib/crypto/Makefile
+++ b/lib/crypto/Makefile
@@ -21,6 +21,11 @@ libcryptoutils-y := memneq.o utils.o
################################################################################
+obj-$(CONFIG_CRYPTO_LIB_AEGIS128X4) += libaegis128x4.o
+libaegis128x4-y := aegis128x4.o
+
+################################################################################
+
obj-$(CONFIG_CRYPTO_LIB_AES) += libaes.o
libaes-y := aes.o
ifeq ($(CONFIG_CRYPTO_LIB_AES_ARCH),y)
diff --git a/lib/crypto/aegis128x4.c b/lib/crypto/aegis128x4.c
new file mode 100644
index 000000000000..4d5a46dc97e5
--- /dev/null
+++ b/lib/crypto/aegis128x4.c
@@ -0,0 +1,509 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AEGIS-128X4 authenticated encryption and decryption (RFC 10032)
+ *
+ * AEGIS-128X4 runs four AEGIS-128L instances in parallel, which differ only
+ * by the context separator mixed in during initialization, and combines their
+ * authentication tags. It is designed for CPUs that can apply the AES round
+ * function to four blocks at once.
+ *
+ * Copyright 2026 Antonio Quartulli <antonio@mandelbit.com>
+ */
+
+#include <crypto/aegis128x4.h>
+#include <crypto/aes.h>
+#include <crypto/utils.h>
+#include <linux/cache.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/unaligned.h>
+
+#define LANES AEGIS128X4_LANES
+#define BLOCK_SIZE AEGIS128X4_BLOCK_SIZE
+#define HALF_SIZE (BLOCK_SIZE / 2)
+
+/* Maximum associated data and en/decrypted data lengths in bytes */
+#define AEGIS128X4_MAX_LEN ((1ULL << 61) - 1)
+
+static const u8 aegis_c0[16] = {
+ 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d,
+ 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, 0x79, 0x62,
+};
+
+static const u8 aegis_c1[16] = {
+ 0xdb, 0x3d, 0x18, 0x55, 0x6d, 0xc2, 0x2f, 0xf1,
+ 0x20, 0x11, 0x31, 0x42, 0x73, 0xb5, 0x28, 0xdd,
+};
+
+#ifdef CONFIG_CRYPTO_LIB_AEGIS128X4_ARCH
+#include "aegis128x4.h" /* $(SRCARCH)/aegis128x4.h */
+#else
+static bool aegis128x4_init_arch(union aegis128x4_state *st,
+ const u8 key[AEGIS128X4_KEY_SIZE],
+ const u8 nonce[AEGIS128X4_NONCE_SIZE])
+{
+ return false;
+}
+
+static bool aegis128x4_absorb_arch(union aegis128x4_state *st, const u8 *src,
+ size_t nblocks)
+{
+ return false;
+}
+
+static bool aegis128x4_enc_arch(union aegis128x4_state *st, u8 *dst,
+ const u8 *src, size_t nblocks)
+{
+ return false;
+}
+
+static bool aegis128x4_dec_arch(union aegis128x4_state *st, u8 *dst,
+ const u8 *src, size_t nblocks)
+{
+ return false;
+}
+
+static bool aegis128x4_final_arch(union aegis128x4_state *st, u8 *authtag,
+ u64 ad_len, u64 data_len, size_t authtag_len)
+{
+ return false;
+}
+
+static bool aegis128x4_crypt_arch(u8 *dst, const u8 *src, size_t data_len,
+ u8 *authtag, const u8 *ad, size_t ad_len,
+ const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key, bool enc)
+{
+ return false;
+}
+#endif
+
+/* Prefetch the AES table to mitigate cache-timing attacks. */
+static __always_inline void aegis128x4_prefetch_tab(void)
+{
+ for (size_t i = 0; i < sizeof(aes_enc_tab); i += L1_CACHE_BYTES)
+ *(volatile const u8 *)((const u8 *)aes_enc_tab + i);
+ barrier();
+}
+
+/*
+ * dst ^= AESRound(src, 0), i.e. dst = AESRound(src, dst), on the 16-byte block
+ * of each lane. @dst and @src must be different state words.
+ */
+static __always_inline void aegis128x4_aesround(__le32 dst[4 * LANES],
+ const __le32 src[4 * LANES])
+{
+ const u32 *t = aes_enc_tab;
+
+ for (int i = 0; i < LANES; i++) {
+ const u8 *s = (const u8 *)&src[4 * i];
+ __le32 *d = &dst[4 * i];
+
+ d[0] ^= cpu_to_le32(t[s[0]] ^ rol32(t[s[5]], 8) ^
+ rol32(t[s[10]], 16) ^ rol32(t[s[15]], 24));
+ d[1] ^= cpu_to_le32(t[s[4]] ^ rol32(t[s[9]], 8) ^
+ rol32(t[s[14]], 16) ^ rol32(t[s[3]], 24));
+ d[2] ^= cpu_to_le32(t[s[8]] ^ rol32(t[s[13]], 8) ^
+ rol32(t[s[2]], 16) ^ rol32(t[s[7]], 24));
+ d[3] ^= cpu_to_le32(t[s[12]] ^ rol32(t[s[1]], 8) ^
+ rol32(t[s[6]], 16) ^ rol32(t[s[11]], 24));
+ }
+}
+
+/* The Update function: absorb @m0 and @m1, each HALF_SIZE bytes. */
+static void aegis128x4_update(union aegis128x4_state *st, const u8 *m0,
+ const u8 *m1)
+{
+ __le32 s7[4 * LANES];
+
+ /*
+ * Each new word j is AESRound(S[j - 1], S[j]), with the message
+ * XOR'ed into words 0 and 4. Compute them from the last one down, so
+ * that S[j - 1] is still the old value, except for word 0, which needs
+ * the old word 7.
+ */
+ memcpy(s7, st->w32[7], sizeof(s7));
+ for (int j = 7; j > 0; j--)
+ aegis128x4_aesround(st->w32[j], st->w32[j - 1]);
+ aegis128x4_aesround(st->w32[0], s7);
+ crypto_xor(st->b[0], m0, HALF_SIZE);
+ crypto_xor(st->b[4], m1, HALF_SIZE);
+ memzero_explicit(s7, sizeof(s7));
+}
+
+/* Compute the keystream of the next block from the current state. */
+static void aegis128x4_keystream(const union aegis128x4_state *st,
+ u8 z[BLOCK_SIZE])
+{
+ for (int i = 0; i < 2 * LANES; i++) {
+ u64 z0 = st->w64[1][i] ^ st->w64[6][i] ^
+ (st->w64[2][i] & st->w64[3][i]);
+ u64 z1 = st->w64[2][i] ^ st->w64[5][i] ^
+ (st->w64[6][i] & st->w64[7][i]);
+
+ memcpy(&z[8 * i], &z0, 8);
+ memcpy(&z[HALF_SIZE + 8 * i], &z1, 8);
+ }
+}
+
+static void aegis128x4_init_generic(union aegis128x4_state *st,
+ const u8 key[AEGIS128X4_KEY_SIZE],
+ const u8 nonce[AEGIS128X4_NONCE_SIZE])
+{
+ u8 key_v[HALF_SIZE], nonce_v[HALF_SIZE];
+
+ for (int i = 0; i < LANES; i++) {
+ u8 *k = &key_v[16 * i], *n = &nonce_v[16 * i];
+
+ memcpy(k, key, 16);
+ memcpy(n, nonce, 16);
+ crypto_xor_cpy(&st->b[0][16 * i], k, n, 16);
+ memcpy(&st->b[1][16 * i], aegis_c1, 16);
+ memcpy(&st->b[2][16 * i], aegis_c0, 16);
+ memcpy(&st->b[3][16 * i], aegis_c1, 16);
+ crypto_xor_cpy(&st->b[4][16 * i], k, n, 16);
+ crypto_xor_cpy(&st->b[5][16 * i], k, aegis_c0, 16);
+ crypto_xor_cpy(&st->b[6][16 * i], k, aegis_c1, 16);
+ crypto_xor_cpy(&st->b[7][16 * i], k, aegis_c0, 16);
+ }
+
+ for (int r = 0; r < 10; r++) {
+ /* ctx[i]: the lane index, then the highest lane index */
+ for (int i = 0; i < LANES; i++) {
+ st->b[3][16 * i] ^= i;
+ st->b[3][16 * i + 1] ^= LANES - 1;
+ st->b[7][16 * i] ^= i;
+ st->b[7][16 * i + 1] ^= LANES - 1;
+ }
+ aegis128x4_update(st, nonce_v, key_v);
+ }
+ memzero_explicit(key_v, sizeof(key_v));
+}
+
+static void aegis128x4_init_state(union aegis128x4_state *st,
+ const u8 key[AEGIS128X4_KEY_SIZE],
+ const u8 nonce[AEGIS128X4_NONCE_SIZE])
+{
+ if (!aegis128x4_init_arch(st, key, nonce)) {
+ aegis128x4_prefetch_tab();
+ aegis128x4_init_generic(st, key, nonce);
+ }
+}
+
+/* Absorb @nblocks full blocks. */
+static void aegis128x4_absorb(union aegis128x4_state *st, const u8 *src,
+ size_t nblocks)
+{
+ if (!nblocks || aegis128x4_absorb_arch(st, src, nblocks))
+ return;
+
+ aegis128x4_prefetch_tab();
+ for (; nblocks; nblocks--, src += BLOCK_SIZE)
+ aegis128x4_update(st, src, src + HALF_SIZE);
+}
+
+/* En/decrypt @nblocks full blocks. */
+static void aegis128x4_crypt_blocks(union aegis128x4_state *st, u8 *dst,
+ const u8 *src, size_t nblocks, bool enc)
+{
+ u8 z[BLOCK_SIZE], p[BLOCK_SIZE];
+
+ if (!nblocks || (enc ? aegis128x4_enc_arch(st, dst, src, nblocks) :
+ aegis128x4_dec_arch(st, dst, src, nblocks)))
+ return;
+
+ aegis128x4_prefetch_tab();
+ for (; nblocks; nblocks--, src += BLOCK_SIZE, dst += BLOCK_SIZE) {
+ aegis128x4_keystream(st, z);
+ /*
+ * The state absorbs the plaintext, so read it before writing
+ * @dst, which may be the same buffer.
+ */
+ if (enc) {
+ aegis128x4_update(st, src, src + HALF_SIZE);
+ crypto_xor_cpy(dst, src, z, BLOCK_SIZE);
+ } else {
+ crypto_xor_cpy(p, src, z, BLOCK_SIZE);
+ memcpy(dst, p, BLOCK_SIZE);
+ aegis128x4_update(st, p, p + HALF_SIZE);
+ }
+ }
+ memzero_explicit(z, sizeof(z));
+ memzero_explicit(p, sizeof(p));
+}
+
+static void aegis128x4_final_generic(union aegis128x4_state *st, u8 *authtag,
+ u64 ad_len, u64 data_len,
+ size_t authtag_len)
+{
+ u8 t[HALF_SIZE], tag[AEGIS128X4_MAX_AUTHTAG_SIZE] = {};
+
+ for (int i = 0; i < LANES; i++) {
+ put_unaligned_le64(ad_len * 8, &t[16 * i]);
+ put_unaligned_le64(data_len * 8, &t[16 * i + 8]);
+ }
+ crypto_xor(t, st->b[2], HALF_SIZE);
+
+ for (int r = 0; r < 7; r++)
+ aegis128x4_update(st, t, t);
+
+ for (int i = 0; i < LANES; i++) {
+ if (authtag_len == 16) {
+ for (int j = 0; j < 7; j++)
+ crypto_xor(tag, &st->b[j][16 * i], 16);
+ } else {
+ for (int j = 0; j < 4; j++)
+ crypto_xor(tag, &st->b[j][16 * i], 16);
+ for (int j = 4; j < 8; j++)
+ crypto_xor(&tag[16], &st->b[j][16 * i], 16);
+ }
+ }
+ memcpy(authtag, tag, authtag_len);
+ memzero_explicit(t, sizeof(t));
+ memzero_explicit(tag, sizeof(tag));
+}
+
+static void aegis128x4_final_state(union aegis128x4_state *st, u8 *authtag,
+ u64 ad_len, u64 data_len,
+ size_t authtag_len)
+{
+ if (!aegis128x4_final_arch(st, authtag, ad_len, data_len,
+ authtag_len)) {
+ aegis128x4_prefetch_tab();
+ aegis128x4_final_generic(st, authtag, ad_len, data_len,
+ authtag_len);
+ }
+}
+
+int aegis128x4_preparekey(struct aegis128x4_key *key, const u8 *in_key,
+ size_t key_len, size_t authtag_len)
+{
+ if (key_len != AEGIS128X4_KEY_SIZE ||
+ (authtag_len != 16 && authtag_len != 32))
+ return -EINVAL;
+ memcpy(key->key, in_key, AEGIS128X4_KEY_SIZE);
+ key->authtag_len = authtag_len;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(aegis128x4_preparekey);
+
+void aegis128x4_init(struct aegis128x4_ctx *ctx,
+ const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key)
+{
+ aegis128x4_init_state(&ctx->state, key->key, nonce);
+ ctx->key = key;
+ ctx->ad_len = 0;
+ ctx->data_len = 0;
+ ctx->data = false;
+}
+EXPORT_SYMBOL_GPL(aegis128x4_init);
+
+void aegis128x4_auth_update(struct aegis128x4_ctx *ctx, const u8 *ad,
+ size_t len)
+{
+ size_t pos = ctx->ad_len % BLOCK_SIZE;
+
+ WARN_ON_ONCE(ctx->data);
+ ctx->ad_len += len;
+ if (pos) {
+ size_t n = min(len, BLOCK_SIZE - pos);
+
+ memcpy(&ctx->buf[pos], ad, n);
+ if (pos + n < BLOCK_SIZE)
+ return;
+ aegis128x4_absorb(&ctx->state, ctx->buf, 1);
+ ad += n;
+ len -= n;
+ }
+ aegis128x4_absorb(&ctx->state, ad, len / BLOCK_SIZE);
+ memcpy(ctx->buf, ad + round_down(len, BLOCK_SIZE), len % BLOCK_SIZE);
+}
+EXPORT_SYMBOL_GPL(aegis128x4_auth_update);
+
+/* Absorb the zero-padded incomplete block of @len bytes, if any. */
+static void aegis128x4_flush(struct aegis128x4_ctx *ctx, size_t len)
+{
+ size_t pos = len % BLOCK_SIZE;
+
+ if (pos) {
+ memset(&ctx->buf[pos], 0, BLOCK_SIZE - pos);
+ aegis128x4_absorb(&ctx->state, ctx->buf, 1);
+ }
+}
+
+static void aegis128x4_crypt_update(struct aegis128x4_ctx *ctx, u8 *dst,
+ const u8 *src, size_t len, bool enc)
+{
+ size_t pos = ctx->data_len % BLOCK_SIZE;
+ size_t nblocks, tail;
+ u8 z[BLOCK_SIZE];
+
+ if (!ctx->data) {
+ aegis128x4_flush(ctx, ctx->ad_len);
+ ctx->data = true;
+ }
+ ctx->data_len += len;
+
+ /*
+ * The incomplete block keeps its plaintext in ctx->buf. Its keystream
+ * depends only on the state, which changes once it's complete.
+ */
+ if (pos) {
+ size_t n = min(len, BLOCK_SIZE - pos);
+
+ aegis128x4_keystream(&ctx->state, z);
+ if (enc) {
+ memcpy(&ctx->buf[pos], src, n);
+ crypto_xor_cpy(dst, &ctx->buf[pos], &z[pos], n);
+ } else {
+ crypto_xor_cpy(&ctx->buf[pos], src, &z[pos], n);
+ memcpy(dst, &ctx->buf[pos], n);
+ }
+ if (pos + n < BLOCK_SIZE)
+ goto out;
+ aegis128x4_absorb(&ctx->state, ctx->buf, 1);
+ src += n;
+ dst += n;
+ len -= n;
+ }
+
+ nblocks = len / BLOCK_SIZE;
+ aegis128x4_crypt_blocks(&ctx->state, dst, src, nblocks, enc);
+ src += nblocks * BLOCK_SIZE;
+ dst += nblocks * BLOCK_SIZE;
+
+ tail = len % BLOCK_SIZE;
+ if (tail) {
+ aegis128x4_keystream(&ctx->state, z);
+ if (enc) {
+ memcpy(ctx->buf, src, tail);
+ crypto_xor_cpy(dst, ctx->buf, z, tail);
+ } else {
+ crypto_xor_cpy(ctx->buf, src, z, tail);
+ memcpy(dst, ctx->buf, tail);
+ }
+ }
+out:
+ memzero_explicit(z, sizeof(z));
+}
+
+void aegis128x4_encrypt_update(struct aegis128x4_ctx *ctx, u8 *dst,
+ const u8 *src, size_t len)
+{
+ aegis128x4_crypt_update(ctx, dst, src, len, true);
+}
+EXPORT_SYMBOL_GPL(aegis128x4_encrypt_update);
+
+void aegis128x4_decrypt_update(struct aegis128x4_ctx *ctx, u8 *dst,
+ const u8 *src, size_t len)
+{
+ aegis128x4_crypt_update(ctx, dst, src, len, false);
+}
+EXPORT_SYMBOL_GPL(aegis128x4_decrypt_update);
+
+static void aegis128x4_compute_tag(struct aegis128x4_ctx *ctx, u8 *authtag)
+{
+ if (!ctx->data)
+ aegis128x4_flush(ctx, ctx->ad_len);
+ else
+ aegis128x4_flush(ctx, ctx->data_len);
+ aegis128x4_final_state(&ctx->state, authtag, ctx->ad_len,
+ ctx->data_len, ctx->key->authtag_len);
+}
+
+void aegis128x4_encrypt_final(struct aegis128x4_ctx *ctx, u8 *authtag)
+{
+ WARN_ON_ONCE(ctx->ad_len > AEGIS128X4_MAX_LEN);
+ WARN_ON_ONCE(ctx->data_len > AEGIS128X4_MAX_LEN);
+
+ aegis128x4_compute_tag(ctx, authtag);
+ aegis128x4_zeroize_ctx(ctx);
+}
+EXPORT_SYMBOL_GPL(aegis128x4_encrypt_final);
+
+int aegis128x4_decrypt_final(struct aegis128x4_ctx *ctx, const u8 *authtag)
+{
+ size_t authtag_len = ctx->key->authtag_len;
+ u8 expected[AEGIS128X4_MAX_AUTHTAG_SIZE];
+ int err;
+
+ if (WARN_ON_ONCE(ctx->ad_len > AEGIS128X4_MAX_LEN) ||
+ WARN_ON_ONCE(ctx->data_len > AEGIS128X4_MAX_LEN)) {
+ aegis128x4_zeroize_ctx(ctx);
+ return -EBADMSG;
+ }
+
+ aegis128x4_compute_tag(ctx, expected);
+ err = crypto_memneq(expected, authtag, authtag_len) ? -EBADMSG : 0;
+ aegis128x4_zeroize_ctx(ctx);
+ memzero_explicit(expected, sizeof(expected));
+ return err;
+}
+EXPORT_SYMBOL_GPL(aegis128x4_decrypt_final);
+
+void aegis128x4_encrypt(u8 *dst, const u8 *src, size_t data_len, u8 *authtag,
+ const u8 *ad, size_t ad_len,
+ const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key)
+{
+ struct aegis128x4_ctx ctx;
+
+ if (aegis128x4_crypt_arch(dst, src, data_len, authtag, ad, ad_len,
+ nonce, key, true))
+ return;
+
+ aegis128x4_init(&ctx, nonce, key);
+ aegis128x4_auth_update(&ctx, ad, ad_len);
+ aegis128x4_encrypt_update(&ctx, dst, src, data_len);
+ aegis128x4_encrypt_final(&ctx, authtag);
+}
+EXPORT_SYMBOL_GPL(aegis128x4_encrypt);
+
+int aegis128x4_decrypt(u8 *dst, const u8 *src, size_t data_len,
+ const u8 *authtag, const u8 *ad, size_t ad_len,
+ const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key)
+{
+ struct aegis128x4_ctx ctx;
+ u8 expected[AEGIS128X4_MAX_AUTHTAG_SIZE];
+ int err;
+
+ if (aegis128x4_crypt_arch(dst, src, data_len, expected, ad, ad_len,
+ nonce, key, false)) {
+ err = crypto_memneq(expected, authtag, key->authtag_len) ?
+ -EBADMSG : 0;
+ memzero_explicit(expected, sizeof(expected));
+ } else {
+ aegis128x4_init(&ctx, nonce, key);
+ aegis128x4_auth_update(&ctx, ad, ad_len);
+ aegis128x4_decrypt_update(&ctx, dst, src, data_len);
+ err = aegis128x4_decrypt_final(&ctx, authtag);
+ }
+ if (unlikely(err) && data_len) {
+ /*
+ * Clear the inauthentic decrypted data so that callers won't
+ * receive it even if they fail to correctly handle errors.
+ */
+ memzero_explicit(dst, data_len);
+ }
+ return err;
+}
+EXPORT_SYMBOL_GPL(aegis128x4_decrypt);
+
+#ifdef aegis128x4_mod_init_arch
+static int __init aegis128x4_mod_init(void)
+{
+ aegis128x4_mod_init_arch();
+ return 0;
+}
+subsys_initcall(aegis128x4_mod_init);
+
+static void __exit aegis128x4_mod_exit(void)
+{
+}
+module_exit(aegis128x4_mod_exit);
+#endif
+
+MODULE_DESCRIPTION("AEGIS-128X4 authenticated encryption algorithm");
+MODULE_LICENSE("GPL");
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [RFC PATCH 3/5] lib/crypto: tests: Add KUnit test suite for AEGIS-128X4
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 1/5] lib/crypto: tests: Make the AEAD dst clearing check effective Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 2/5] lib/crypto: aegis128x4: Add AEGIS-128X4 library Antonio Quartulli
@ 2026-11-12 12:54 ` Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 4/5] lib/crypto: x86/aegis128x4: Add VAES-AVX512 optimized code Antonio Quartulli
` (3 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Antonio Quartulli @ 2026-11-12 12:54 UTC (permalink / raw)
To: linux-crypto
Cc: Antonio Quartulli, Eric Biggers, Ard Biesheuvel,
Jason A . Donenfeld, Herbert Xu
From: Antonio Quartulli <antonio@mandelbit.com>
Add a KUnit test suite for the AEGIS-128X4 library, using the AEAD test
template and the AEGIS-128X4 test vectors from RFC 10032.
python-cryptography doesn't support AEGIS, so add an implementation of
AEGIS-128X written from the RFC to gen-aead-testvecs.py, to generate the
Monte Carlo checksum.
Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
---
lib/crypto/.kunitconfig | 1 +
lib/crypto/tests/Kconfig | 10 +
lib/crypto/tests/Makefile | 1 +
lib/crypto/tests/aegis128x4_kunit.c | 352 ++++++++++++++++++++++++++++
scripts/crypto/gen-aead-testvecs.py | 166 ++++++++++++-
5 files changed, 524 insertions(+), 6 deletions(-)
create mode 100644 lib/crypto/tests/aegis128x4_kunit.c
diff --git a/lib/crypto/.kunitconfig b/lib/crypto/.kunitconfig
index 6d83badf4292..61a28c1554cb 100644
--- a/lib/crypto/.kunitconfig
+++ b/lib/crypto/.kunitconfig
@@ -2,6 +2,7 @@ CONFIG_KUNIT=y
CONFIG_CRYPTO_LIB_ENABLE_ALL_FOR_KUNIT=y
+CONFIG_CRYPTO_LIB_AEGIS128X4_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_AES_CCM_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_AES_GCM_KUNIT_TEST=y
diff --git a/lib/crypto/tests/Kconfig b/lib/crypto/tests/Kconfig
index 5a86a02d25b7..2584aa74c450 100644
--- a/lib/crypto/tests/Kconfig
+++ b/lib/crypto/tests/Kconfig
@@ -1,5 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-or-later
+config CRYPTO_LIB_AEGIS128X4_KUNIT_TEST
+ tristate "KUnit tests for AEGIS-128X4" if !KUNIT_ALL_TESTS
+ depends on KUNIT && CRYPTO_LIB_AEGIS128X4
+ default KUNIT_ALL_TESTS
+ select CRYPTO_LIB_BENCHMARK_VISIBLE
+ # This test uses BLAKE2s, but that's always built-in.
+ help
+ KUnit tests for the AEGIS-128X4 authenticated encryption algorithm.
+
config CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST
tristate "KUnit tests for AES-CMAC, AES-XCBC-MAC, and AES-CBC-MAC" if !KUNIT_ALL_TESTS
depends on KUNIT && CRYPTO_LIB_AES_CBC_MACS
@@ -179,6 +188,7 @@ config CRYPTO_LIB_SM3_KUNIT_TEST
config CRYPTO_LIB_ENABLE_ALL_FOR_KUNIT
tristate "Enable all crypto library code for KUnit tests"
depends on KUNIT
+ select CRYPTO_LIB_AEGIS128X4
select CRYPTO_LIB_AES_CBC
select CRYPTO_LIB_AES_CBC_MACS
select CRYPTO_LIB_AES_CCM
diff --git a/lib/crypto/tests/Makefile b/lib/crypto/tests/Makefile
index 40c9624eaaa8..73e9851dbb14 100644
--- a/lib/crypto/tests/Makefile
+++ b/lib/crypto/tests/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-or-later
+obj-$(CONFIG_CRYPTO_LIB_AEGIS128X4_KUNIT_TEST) += aegis128x4_kunit.o
obj-$(CONFIG_CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST) += aes_cbc_macs_kunit.o
obj-$(CONFIG_CRYPTO_LIB_AES_CCM_KUNIT_TEST) += aes_ccm_kunit.o
obj-$(CONFIG_CRYPTO_LIB_AES_GCM_KUNIT_TEST) += aes_gcm_kunit.o
diff --git a/lib/crypto/tests/aegis128x4_kunit.c b/lib/crypto/tests/aegis128x4_kunit.c
new file mode 100644
index 000000000000..1e271e4a1756
--- /dev/null
+++ b/lib/crypto/tests/aegis128x4_kunit.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit test suite for AEGIS-128X4
+ *
+ * Copyright 2026 Antonio Quartulli <antonio@mandelbit.com>
+ */
+#include <crypto/aegis128x4.h>
+#include <crypto/blake2s.h>
+#include "test-utils.h"
+
+/* AEGIS-128X4 test vectors from RFC 10032, Appendix A.5 */
+static const struct aegis128x4_testvec {
+ const char *name;
+ const char *key;
+ const char *nonce;
+ const char *ad;
+ size_t ad_len;
+ const char *ptext;
+ const char *ctext;
+ size_t data_len;
+ const char *tag;
+ size_t tag_len;
+} aegis128x4_testvecs[] = {
+ {
+ .name = "RFC 10032 A.5.2 (128-bit tag)",
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .nonce = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .ad = "",
+ .ad_len = 0,
+ .ptext = "",
+ .ctext = "",
+ .data_len = 0,
+ .tag = "\x5b\xef\x76\x2d\x09\x47\xc0\x04"
+ "\x55\xb9\x7b\xb3\xaf\x30\xdf\xa3",
+ .tag_len = 16,
+ },
+ {
+ .name = "RFC 10032 A.5.2 (256-bit tag)",
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .nonce = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .ad = "",
+ .ad_len = 0,
+ .ptext = "",
+ .ctext = "",
+ .data_len = 0,
+ .tag = "\xa4\xb2\x54\x37\xf4\xbe\x93\xcf"
+ "\xa8\x56\xa2\xf2\x7e\x44\x16\xb4"
+ "\x2c\xac\x79\xfd\x46\x98\xf2\xcd"
+ "\xbe\x6a\xf2\x56\x73\xe1\x0a\x68",
+ .tag_len = 32,
+ },
+ {
+ .name = "RFC 10032 A.5.3 (128-bit tag)",
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .nonce = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .ad = "\x01\x02\x03\x04\x01\x02\x03\x04",
+ .ad_len = 8,
+ .ptext = "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07",
+ .ctext = "\xe8\x36\x11\x85\x62\xf4\x47\x9c"
+ "\x9d\x35\xc1\x73\x56\xa8\x33\x11"
+ "\x4c\x21\xf9\xaa\x39\xe4\xdd\xa5"
+ "\xe5\xc8\x7f\x41\x52\xa0\x0f\xce"
+ "\x9a\x7c\x38\xf8\x32\xea\xfe\x8b"
+ "\x1c\x12\xf8\xa7\xcf\x12\xa8\x1a"
+ "\x1a\xd8\xa9\xc2\x4b\xa9\xde\xdf"
+ "\xbd\xaa\x58\x6f\xfe\xa6\x7d\xdc"
+ "\x80\x1e\xa9\x7d\x9a\xb4\xa8\x72"
+ "\xf4\x2d\x0e\x35\x2e\x27\x13\xda"
+ "\xcd\x60\x9f\x94\x42\xc1\x75\x17"
+ "\xc5\xa2\x9d\xaf\x3e\x2a\x3f\xac"
+ "\x4f\xf6\xb1\x38\x0c\x4e\x46\xdf"
+ "\x7b\x08\x6a\xf6\xce\x6b\xc1\xed"
+ "\x59\x4b\x8d\xd6\x4a\xed\x2a\x7e",
+ .data_len = 120,
+ .tag = "\x0e\x56\xab\x94\xe2\xe8\x5d\xb8"
+ "\x0f\x9d\x54\x01\x0c\xaa\xbf\xb4",
+ .tag_len = 16,
+ },
+ {
+ .name = "RFC 10032 A.5.3 (256-bit tag)",
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .nonce = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .ad = "\x01\x02\x03\x04\x01\x02\x03\x04",
+ .ad_len = 8,
+ .ptext = "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07",
+ .ctext = "\xe8\x36\x11\x85\x62\xf4\x47\x9c"
+ "\x9d\x35\xc1\x73\x56\xa8\x33\x11"
+ "\x4c\x21\xf9\xaa\x39\xe4\xdd\xa5"
+ "\xe5\xc8\x7f\x41\x52\xa0\x0f\xce"
+ "\x9a\x7c\x38\xf8\x32\xea\xfe\x8b"
+ "\x1c\x12\xf8\xa7\xcf\x12\xa8\x1a"
+ "\x1a\xd8\xa9\xc2\x4b\xa9\xde\xdf"
+ "\xbd\xaa\x58\x6f\xfe\xa6\x7d\xdc"
+ "\x80\x1e\xa9\x7d\x9a\xb4\xa8\x72"
+ "\xf4\x2d\x0e\x35\x2e\x27\x13\xda"
+ "\xcd\x60\x9f\x94\x42\xc1\x75\x17"
+ "\xc5\xa2\x9d\xaf\x3e\x2a\x3f\xac"
+ "\x4f\xf6\xb1\x38\x0c\x4e\x46\xdf"
+ "\x7b\x08\x6a\xf6\xce\x6b\xc1\xed"
+ "\x59\x4b\x8d\xd6\x4a\xed\x2a\x7e",
+ .data_len = 120,
+ .tag = "\x69\xab\xf0\xf6\x4a\x13\x7d\xd6"
+ "\xe1\x22\x47\x8d\x77\x7e\x98\xbc"
+ "\x42\x28\x23\x00\x6c\xf5\x7f\x5e"
+ "\xe8\x22\xdd\x78\x39\x72\x30\xb2",
+ .tag_len = 32,
+ },
+};
+
+static void test_aegis128x4_one_test_vector(struct kunit *test,
+ const struct aegis128x4_testvec *tv)
+{
+ u8 *ctext = alloc_buf(test, tv->data_len);
+ u8 *decrypted = alloc_buf(test, tv->data_len);
+ u8 *tag = alloc_buf(test, tv->tag_len);
+ struct aegis128x4_key key;
+ int err;
+
+ err = aegis128x4_preparekey(&key, tv->key, AEGIS128X4_KEY_SIZE,
+ tv->tag_len);
+ KUNIT_ASSERT_EQ_MSG(test, 0, err, "Failed to prepare key for %s",
+ tv->name);
+
+ aegis128x4_encrypt(ctext, tv->ptext, tv->data_len, tag, tv->ad,
+ tv->ad_len, tv->nonce, &key);
+ KUNIT_ASSERT_MEMEQ_MSG(test, tv->ctext, ctext, tv->data_len,
+ "Wrong ciphertext for %s", tv->name);
+ KUNIT_ASSERT_MEMEQ_MSG(test, tag, tv->tag, tv->tag_len,
+ "Wrong tag for %s", tv->name);
+
+ err = aegis128x4_decrypt(decrypted, ctext, tv->data_len, tag, tv->ad,
+ tv->ad_len, tv->nonce, &key);
+ KUNIT_ASSERT_EQ_MSG(test, 0, err, "Decryption failed for %s", tv->name);
+ KUNIT_ASSERT_MEMEQ_MSG(test, tv->ptext, decrypted, tv->data_len,
+ "Wrong plaintext for %s", tv->name);
+}
+
+static void test_aegis128x4_test_vectors(struct kunit *test)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(aegis128x4_testvecs); i++)
+ test_aegis128x4_one_test_vector(test, &aegis128x4_testvecs[i]);
+}
+
+/* The (data, associated data) lengths of AEGIS128X4_LONG_LENS in the script */
+static const struct {
+ size_t data_len;
+ size_t ad_len;
+} aegis128x4_long_lens[] = {
+ { 4097, 0 },
+ { 8320, 4100 },
+ { 12345, 129 },
+};
+
+/* This value was generated by gen-aead-testvecs.py. */
+static const u8 aegis128x4_long_checksum[BLAKE2S_HASH_SIZE] = {
+ 0x0a, 0x18, 0x21, 0x25, 0x48, 0xe7, 0x99, 0x53, 0x09, 0x00, 0x0d,
+ 0xf5, 0x61, 0x1b, 0xa4, 0x11, 0xe1, 0x91, 0xef, 0xf5, 0x60, 0x84,
+ 0x79, 0x26, 0x4c, 0x20, 0xb2, 0x0d, 0x22, 0x70, 0x10, 0xfb,
+};
+
+/*
+ * Test messages longer than the 4 KiB that the x86 code processes at a time,
+ * out-of-place, in-place and incrementally in pieces of random length.
+ */
+static void test_aegis128x4_long(struct kunit *test)
+{
+ const size_t max_len = 12345;
+ u8 *pt = alloc_buf(test, max_len);
+ u8 *ad = alloc_buf(test, max_len);
+ u8 *ct = alloc_buf(test, max_len);
+ u8 *buf = alloc_buf(test, max_len);
+ u8 tag[AEGIS128X4_MAX_AUTHTAG_SIZE], tag2[AEGIS128X4_MAX_AUTHTAG_SIZE];
+ u8 raw_key[AEGIS128X4_KEY_SIZE], nonce[AEGIS128X4_NONCE_SIZE];
+ u8 checksum[BLAKE2S_HASH_SIZE];
+ struct blake2s_ctx checksum_ctx;
+ struct aegis128x4_key key;
+ struct aegis128x4_ctx ctx;
+ size_t pos, n;
+ int err;
+
+ blake2s_init(&checksum_ctx, BLAKE2S_HASH_SIZE);
+ for (size_t i = 0; i < ARRAY_SIZE(aegis128x4_long_lens); i++) {
+ size_t data_len = aegis128x4_long_lens[i].data_len;
+ size_t ad_len = aegis128x4_long_lens[i].ad_len;
+
+ rand_bytes_seeded_from_len(pt, data_len);
+ rand_bytes_seeded_from_len(ad, ad_len);
+ rand_bytes_seeded_from_len(raw_key, sizeof(raw_key));
+ rand_bytes_seeded_from_len(nonce, sizeof(nonce));
+ err = aegis128x4_preparekey(&key, raw_key, sizeof(raw_key),
+ sizeof(tag));
+ KUNIT_ASSERT_EQ(test, 0, err);
+
+ aegis128x4_encrypt(ct, pt, data_len, tag, ad, ad_len, nonce,
+ &key);
+ blake2s_update(&checksum_ctx, ct, data_len);
+ blake2s_update(&checksum_ctx, tag, sizeof(tag));
+
+ memcpy(buf, pt, data_len);
+ aegis128x4_encrypt(buf, buf, data_len, tag2, ad, ad_len, nonce,
+ &key);
+ KUNIT_ASSERT_MEMEQ(test, ct, buf, data_len);
+ KUNIT_ASSERT_MEMEQ(test, tag, tag2, sizeof(tag));
+
+ aegis128x4_init(&ctx, nonce, &key);
+ for (pos = 0; pos < ad_len; pos += n) {
+ n = min(ad_len - pos, 1 + rand_length(1000));
+ aegis128x4_auth_update(&ctx, &ad[pos], n);
+ }
+ for (pos = 0; pos < data_len; pos += n) {
+ n = min(data_len - pos, 1 + rand_length(5000));
+ aegis128x4_encrypt_update(&ctx, &buf[pos], &pt[pos], n);
+ }
+ aegis128x4_encrypt_final(&ctx, tag2);
+ KUNIT_ASSERT_MEMEQ(test, ct, buf, data_len);
+ KUNIT_ASSERT_MEMEQ(test, tag, tag2, sizeof(tag));
+
+ memcpy(buf, ct, data_len);
+ err = aegis128x4_decrypt(buf, buf, data_len, tag, ad, ad_len,
+ nonce, &key);
+ KUNIT_ASSERT_EQ(test, 0, err);
+ KUNIT_ASSERT_MEMEQ(test, pt, buf, data_len);
+ }
+ blake2s_final(&checksum_ctx, checksum);
+ KUNIT_EXPECT_MEMEQ(test, checksum, aegis128x4_long_checksum,
+ BLAKE2S_HASH_SIZE);
+}
+
+static int aegis128x4_init_test(struct aegis128x4_ctx *ctx, u64 data_len,
+ u64 ad_len, const u8 *nonce, size_t nonce_len,
+ const struct aegis128x4_key *key)
+{
+ if (nonce_len != AEGIS128X4_NONCE_SIZE)
+ return -EINVAL;
+ /*
+ * Ignore data_len and ad_len. Incremental AEGIS-128X4 doesn't need
+ * them at initialization time.
+ */
+ aegis128x4_init(ctx, nonce, key);
+ return 0;
+}
+
+static int aegis128x4_encrypt_test(u8 *dst, const u8 *src, size_t data_len,
+ u8 *authtag, const u8 *ad, size_t ad_len,
+ const u8 *nonce, size_t nonce_len,
+ const struct aegis128x4_key *key)
+{
+ if (nonce_len != AEGIS128X4_NONCE_SIZE)
+ return -EINVAL;
+ /* aegis128x4_encrypt() returns void. */
+ aegis128x4_encrypt(dst, src, data_len, authtag, ad, ad_len, nonce, key);
+ return 0;
+}
+
+static int aegis128x4_decrypt_test(u8 *dst, const u8 *src, size_t data_len,
+ const u8 *authtag, const u8 *ad,
+ size_t ad_len, const u8 *nonce,
+ size_t nonce_len,
+ const struct aegis128x4_key *key)
+{
+ if (nonce_len != AEGIS128X4_NONCE_SIZE)
+ return -EINVAL;
+ return aegis128x4_decrypt(dst, src, data_len, authtag, ad, ad_len,
+ nonce, key);
+}
+
+static const size_t aegis128x4_valid_key_lens[] = { AEGIS128X4_KEY_SIZE };
+#define AEAD_MAX_KEY_LEN AEGIS128X4_KEY_SIZE
+#define AEAD_VALID_KEY_LENS aegis128x4_valid_key_lens
+
+static const size_t aegis128x4_valid_nonce_lens[] = { AEGIS128X4_NONCE_SIZE };
+#define AEAD_MAX_NONCE_LEN AEGIS128X4_NONCE_SIZE
+#define AEAD_VALID_NONCE_LENS aegis128x4_valid_nonce_lens
+
+static const size_t aegis128x4_valid_tag_lens[] = { 16, 32 };
+#define AEAD_MAX_TAG_LEN AEGIS128X4_MAX_AUTHTAG_SIZE
+#define AEAD_VALID_TAG_LENS aegis128x4_valid_tag_lens
+
+#define AEAD_KEY aegis128x4_key
+#define AEAD_CTX aegis128x4_ctx
+#define AEAD_PREPAREKEY aegis128x4_preparekey
+#define AEAD_ENCRYPT aegis128x4_encrypt_test
+#define AEAD_DECRYPT aegis128x4_decrypt_test
+
+#define AEAD_INIT aegis128x4_init_test
+#define AEAD_AUTH_UPDATE aegis128x4_auth_update
+#define AEAD_ENCRYPT_UPDATE aegis128x4_encrypt_update
+#define AEAD_DECRYPT_UPDATE aegis128x4_decrypt_update
+#define AEAD_ENCRYPT_FINAL aegis128x4_encrypt_final
+#define AEAD_DECRYPT_FINAL aegis128x4_decrypt_final
+
+/* This value was generated by gen-aead-testvecs.py. */
+static const u8 aegis128x4_monte_carlo_checksum[BLAKE2S_HASH_SIZE] = {
+ 0x57, 0xf2, 0x44, 0xb4, 0x43, 0x71, 0x7c, 0x34, 0x94, 0x93, 0x22,
+ 0xaa, 0xaf, 0x75, 0x5b, 0xa9, 0x5c, 0x23, 0x26, 0xbb, 0x4f, 0xf2,
+ 0x61, 0x43, 0xa6, 0x92, 0xc7, 0x46, 0x3a, 0x17, 0x58, 0x67,
+};
+#define AEAD_MONTE_CARLO_CHECKSUM aegis128x4_monte_carlo_checksum
+
+#include "aead-test-template.h"
+
+static struct kunit_case aegis128x4_test_cases[] = {
+ KUNIT_CASE(test_aegis128x4_test_vectors),
+ KUNIT_CASE(test_aegis128x4_long),
+ AEAD_KUNIT_CASES,
+ {},
+};
+
+static struct kunit_suite aegis128x4_test_suite = {
+ .name = "aegis128x4",
+ .test_cases = aegis128x4_test_cases,
+};
+kunit_test_suite(aegis128x4_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests and benchmark for AEGIS-128X4");
+MODULE_LICENSE("GPL");
diff --git a/scripts/crypto/gen-aead-testvecs.py b/scripts/crypto/gen-aead-testvecs.py
index d77d646f75b5..6917be65f5ab 100755
--- a/scripts/crypto/gen-aead-testvecs.py
+++ b/scripts/crypto/gen-aead-testvecs.py
@@ -3,13 +3,12 @@
#
# Script that generates known-good data used in the AEAD tests.
#
-# Requires that python-cryptography be installed.
+# Requires that python-cryptography be installed, except for AEGIS-128X4.
#
# Copyright 2026 Google LLC
import hashlib
import sys
-import cryptography.hazmat.primitives.ciphers.aead
# Deterministically generate 'length' random bytes.
@@ -22,6 +21,129 @@ def rand_bytes(length):
return bytes(out)
+# Multiply two elements of GF(2^8), using the AES polynomial.
+def gf256_mul(a, b):
+ product = 0
+ for _ in range(8):
+ if b & 1:
+ product ^= a
+ a = (a << 1) ^ (0x11B if a & 0x80 else 0)
+ b >>= 1
+ return product
+
+
+# The AES S-box: the multiplicative inverse in GF(2^8), then the affine map.
+def gen_aes_sbox():
+ sbox = []
+ for x in range(256):
+ inv = next((y for y in range(256) if gf256_mul(x, y) == 1), 0)
+ s = inv
+ for i in range(1, 5):
+ s ^= ((inv << i) | (inv >> (8 - i))) & 0xFF
+ sbox.append(s ^ 0x63)
+ return sbox
+
+
+AES_SBOX = gen_aes_sbox()
+
+
+def xor_bytes(a, b):
+ return bytes(x ^ y for x, y in zip(a, b))
+
+
+def and_bytes(a, b):
+ return bytes(x & y for x, y in zip(a, b))
+
+
+# The AES round function: SubBytes, ShiftRows, MixColumns and AddRoundKey.
+def aes_round(block, round_key):
+ s = [AES_SBOX[block[(i + 4 * (i % 4)) % 16]] for i in range(16)]
+ out = []
+ for c in range(4):
+ col = s[4 * c : 4 * c + 4]
+ for r in range(4):
+ out.append(
+ gf256_mul(col[r], 2)
+ ^ gf256_mul(col[(r + 1) % 4], 3)
+ ^ col[(r + 2) % 4]
+ ^ col[(r + 3) % 4]
+ )
+ return xor_bytes(out, round_key)
+
+
+# AEGIS-128X encryption with parallelism degree 'd', as specified in RFC 10032
+# section 5.4, since python-cryptography doesn't support AEGIS.
+def aegis128x_encrypt(d, key, nonce, ad, pt, tag_len):
+ c0 = bytes.fromhex("000101020305080d1522375990e97962")
+ c1 = bytes.fromhex("db3d18556dc22ff12011314273b528dd")
+ rate = 32 * d
+ half = rate // 2
+
+ def update(v, m0, m1):
+ for i, s in enumerate(v):
+ v[i] = [
+ aes_round(s[7], xor_bytes(s[0], m0[16 * i : 16 * i + 16])),
+ aes_round(s[0], s[1]),
+ aes_round(s[1], s[2]),
+ aes_round(s[2], s[3]),
+ aes_round(s[3], xor_bytes(s[4], m1[16 * i : 16 * i + 16])),
+ aes_round(s[4], s[5]),
+ aes_round(s[5], s[6]),
+ aes_round(s[6], s[7]),
+ ]
+
+ # Init
+ kn = xor_bytes(key, nonce)
+ kc0 = xor_bytes(key, c0)
+ kc1 = xor_bytes(key, c1)
+ v = [[kn, c1, c0, c1, kn, kc0, kc1, kc0] for _ in range(d)]
+ for _ in range(10):
+ for i in range(d):
+ ctx = bytes([i, d - 1]) + bytes(14)
+ v[i][3] = xor_bytes(v[i][3], ctx)
+ v[i][7] = xor_bytes(v[i][7], ctx)
+ update(v, nonce * d, key * d)
+
+ # Absorb
+ padded = ad + bytes(-len(ad) % rate)
+ for i in range(0, len(padded), rate):
+ update(v, padded[i : i + half], padded[i + half : i + rate])
+
+ # Enc
+ ct = b""
+ padded = pt + bytes(-len(pt) % rate)
+ for i in range(0, len(padded), rate):
+ z0 = b"".join(
+ xor_bytes(xor_bytes(s[1], s[6]), and_bytes(s[2], s[3])) for s in v
+ )
+ z1 = b"".join(
+ xor_bytes(xor_bytes(s[2], s[5]), and_bytes(s[6], s[7])) for s in v
+ )
+ block = padded[i : i + rate]
+ ct += xor_bytes(block, z0 + z1)
+ update(v, block[:half], block[half:])
+
+ # Finalize
+ u = (len(ad) * 8).to_bytes(8, "little")
+ u += (len(pt) * 8).to_bytes(8, "little")
+ t = b"".join(xor_bytes(s[2], u) for s in v)
+ for _ in range(7):
+ update(v, t, t)
+ if tag_len == 16:
+ tag = bytes(16)
+ for s in v:
+ for j in range(7):
+ tag = xor_bytes(tag, s[j])
+ else:
+ tag0 = tag1 = bytes(16)
+ for s in v:
+ for j in range(4):
+ tag0 = xor_bytes(tag0, s[j])
+ tag1 = xor_bytes(tag1, s[j + 4])
+ tag = tag0 + tag1
+ return ct[: len(pt)] + tag
+
+
# Deterministically generate many different AEAD inputs using exactly the same
# method that the test uses; encrypt them using an independent implementation of
# the algorithm; compute the checksum of all the resulting (ciphertext, authtag)
@@ -32,7 +154,14 @@ def gen_monte_carlo_checksum(alg):
ad_len = data_len % 293
pt = rand_bytes(data_len)
ad = rand_bytes(ad_len)
- if alg == "aes-ccm":
+ if alg == "aegis128x4":
+ key = rand_bytes(16)
+ nonce = rand_bytes(16)
+ tag_len = [16, 32][data_len % 2]
+ ct_and_tag = aegis128x_encrypt(4, key, nonce, ad, pt, tag_len)
+ elif alg == "aes-ccm":
+ import cryptography.hazmat.primitives.ciphers.aead
+
key_len = [16, 24, 32][data_len % 3]
key = rand_bytes(key_len)
nonce = rand_bytes([7, 8, 9, 10, 11, 12, 13][data_len % 7])
@@ -42,6 +171,8 @@ def gen_monte_carlo_checksum(alg):
)
ct_and_tag = ccm.encrypt(nonce, pt, ad)
elif alg == "aes-gcm":
+ import cryptography.hazmat.primitives.ciphers.aead
+
key_len = [16, 24, 32][data_len % 3]
key = rand_bytes(key_len)
nonce = rand_bytes(12)
@@ -53,7 +184,27 @@ def gen_monte_carlo_checksum(alg):
blake2s.update(ct_and_tag)
- name = f"{alg.replace('-', '_')}_monte_carlo_checksum"
+ print_checksum(f"{alg.replace('-', '_')}_monte_carlo_checksum", blake2s)
+
+
+# Lengths of the (data, associated data) pairs in test_aegis128x4_long(), which
+# cross the 4 KiB units that the x86 code splits its work into.
+AEGIS128X4_LONG_LENS = ((4097, 0), (8320, 4100), (12345, 129))
+
+
+# Checksum of the (ciphertext, authtag) pairs of test_aegis128x4_long().
+def gen_aegis128x4_long_checksum():
+ blake2s = hashlib.blake2s()
+ for data_len, ad_len in AEGIS128X4_LONG_LENS:
+ pt = rand_bytes(data_len)
+ ad = rand_bytes(ad_len)
+ key = rand_bytes(16)
+ nonce = rand_bytes(16)
+ blake2s.update(aegis128x_encrypt(4, key, nonce, ad, pt, 32))
+ print_checksum("aegis128x4_long_checksum", blake2s)
+
+
+def print_checksum(name, blake2s):
value = blake2s.digest()
print(f"static const u8 {name}[BLAKE2S_HASH_SIZE] = {{")
for i in range(0, len(value), 11):
@@ -62,8 +213,11 @@ def gen_monte_carlo_checksum(alg):
print("};")
-if len(sys.argv) != 2 or sys.argv[1] not in ("aes-ccm", "aes-gcm"):
- sys.stderr.write("Usage: gen-aead-testvecs.py [aes-ccm|aes-gcm]\n")
+ALGS = ("aegis128x4", "aes-ccm", "aes-gcm")
+if len(sys.argv) != 2 or sys.argv[1] not in ALGS:
+ sys.stderr.write(f"Usage: gen-aead-testvecs.py [{'|'.join(ALGS)}]\n")
sys.exit(1)
gen_monte_carlo_checksum(sys.argv[1])
+if sys.argv[1] == "aegis128x4":
+ gen_aegis128x4_long_checksum()
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [RFC PATCH 4/5] lib/crypto: x86/aegis128x4: Add VAES-AVX512 optimized code
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
` (2 preceding siblings ...)
2026-11-12 12:54 ` [RFC PATCH 3/5] lib/crypto: tests: Add KUnit test suite for AEGIS-128X4 Antonio Quartulli
@ 2026-11-12 12:54 ` Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 5/5] crypto: aegis128x4 - Add AEGIS-128X4 AEAD algorithm Antonio Quartulli
` (2 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Antonio Quartulli @ 2026-11-12 12:54 UTC (permalink / raw)
To: linux-crypto
Cc: Antonio Quartulli, Eric Biggers, Ard Biesheuvel,
Jason A . Donenfeld, Herbert Xu
From: Antonio Quartulli <antonio@mandelbit.com>
Add an x86_64 implementation of AEGIS-128X4 using VAES and AVX-512,
ported to assembly from the libaegis AVX-512 code by Frank Denis, which
uses C intrinsics and is MIT licensed, so the file is dual-licensed
GPL-2.0-only OR MIT. Each state word is a zmm register holding the
corresponding block of the four AEGIS-128L instances, so that the update
function takes eight VAESENC instructions.
Messages of up to 4 KiB are processed by one call that keeps the state
in registers. Longer messages and the incremental API go through the
per-block functions, in kernel-mode FPU sections of at most 4 KiB.
There is no 256-bit code yet, so unlike AES-GCM this code is used on
CPUs with X86_FEATURE_PREFER_YMM too.
Encryption throughput from the KUnit benchmark on a Ryzen 9 9950X,
in MB/s:
data_len generic VAES-AVX512
16 10 581
64 41 2351
256 159 8986
1420 576 34521
4096 987 46761
16384 1308 79588
Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
---
lib/crypto/Kconfig | 5 +
lib/crypto/Makefile | 4 +
lib/crypto/x86/aegis128x4-vaes-avx512.S | 470 ++++++++++++++++++++++++
lib/crypto/x86/aegis128x4.h | 167 +++++++++
4 files changed, 646 insertions(+)
create mode 100644 lib/crypto/x86/aegis128x4-vaes-avx512.S
create mode 100644 lib/crypto/x86/aegis128x4.h
diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig
index 3e53dd570b78..195c047843ac 100644
--- a/lib/crypto/Kconfig
+++ b/lib/crypto/Kconfig
@@ -15,6 +15,11 @@ config CRYPTO_LIB_AEGIS128X4
Select this if your module uses any of the functions from
<crypto/aegis128x4.h>.
+config CRYPTO_LIB_AEGIS128X4_ARCH
+ bool
+ depends on CRYPTO_LIB_AEGIS128X4 && !UML && !KMSAN
+ default y if X86_64
+
config CRYPTO_LIB_AES
tristate
# Select dependencies of modes that are part of libaes.
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
index be43ab974c0f..cbb8b9c93de8 100644
--- a/lib/crypto/Makefile
+++ b/lib/crypto/Makefile
@@ -23,6 +23,10 @@ libcryptoutils-y := memneq.o utils.o
obj-$(CONFIG_CRYPTO_LIB_AEGIS128X4) += libaegis128x4.o
libaegis128x4-y := aegis128x4.o
+ifeq ($(CONFIG_CRYPTO_LIB_AEGIS128X4_ARCH),y)
+CFLAGS_aegis128x4.o += -I$(src)/$(SRCARCH)
+libaegis128x4-$(CONFIG_X86) += x86/aegis128x4-vaes-avx512.o
+endif
################################################################################
diff --git a/lib/crypto/x86/aegis128x4-vaes-avx512.S b/lib/crypto/x86/aegis128x4-vaes-avx512.S
new file mode 100644
index 000000000000..080f83a14ca9
--- /dev/null
+++ b/lib/crypto/x86/aegis128x4-vaes-avx512.S
@@ -0,0 +1,470 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+/*
+ * VAES + AVX512 implementation of AEGIS-128X4
+ *
+ * AEGIS-128X4 is the 4-way parallel variant of AEGIS-128L defined in
+ * RFC 10032. Each of the eight state words is a 512-bit
+ * vector holding the corresponding word of four independent AEGIS-128L
+ * states, one per 128-bit lane, so that every AES round in the update function
+ * is computed with a single VAESENC on a zmm register.
+ *
+ * Ported to assembly from the libaegis AVX-512 implementation, which uses C
+ * intrinsics (https://github.com/aegis-aead/libaegis). See RFC 10032 for the
+ * algorithm.
+ *
+ * Copyright (c) 2023-2026 Frank Denis
+ * Copyright (c) 2026 Antonio Quartulli <antonio@mandelbit.com>
+ */
+
+#include <linux/linkage.h>
+
+#define STATE0 %zmm0
+#define STATE1 %zmm1
+#define STATE2 %zmm2
+#define STATE3 %zmm3
+#define STATE4 %zmm4
+#define STATE5 %zmm5
+#define STATE6 %zmm6
+#define STATE7 %zmm7
+#define MSG0 %zmm8
+#define MSG1 %zmm9
+#define T0 %zmm10
+#define T1 %zmm11
+#define T2 %zmm12
+#define T3 %zmm13
+#define ZERO %zmm15
+#define ZERO_XMM %xmm15
+
+/* Size of the AEGIS-128X4 rate, i.e. of the data absorbed by one update */
+#define RATE 128
+
+.section .rodata.cst32.aegis128x4_const, "aM", @progbits, 32
+.align 16
+.Laegis128x4_const_0:
+ .byte 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d
+ .byte 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, 0x79, 0x62
+.Laegis128x4_const_1:
+ .byte 0xdb, 0x3d, 0x18, 0x55, 0x6d, 0xc2, 0x2f, 0xf1
+ .byte 0x20, 0x11, 0x31, 0x42, 0x73, 0xb5, 0x28, 0xdd
+
+/*
+ * Per-lane context injected during initialization: byte 0 is the lane index,
+ * byte 1 is the number of lanes minus one.
+ */
+.section .rodata.cst64.aegis128x4_ctx, "aM", @progbits, 64
+.align 64
+.Laegis128x4_ctx:
+ .octa 0x0300
+ .octa 0x0301
+ .octa 0x0302
+ .octa 0x0303
+
+.text
+
+/*
+ * aegis128x4_update
+ * input:
+ * STATE[0-7] - input state
+ * m0, m1 - message vectors (128 bytes in total)
+ * ZERO - all-zeroes vector
+ * output:
+ * STATE[0-7] - output state (same positions)
+ * changed:
+ * T0, T1
+ *
+ * AESENC(x, k) == AESENC(x, 0) ^ k, therefore the two words that absorb the
+ * message are computed as AESENC(x, 0) ^ S ^ M with a single VPTERNLOGQ, which
+ * also allows updating every state word in place.
+ */
+.macro aegis128x4_update m0, m1
+ vaesenc ZERO, STATE7, T0
+ vaesenc ZERO, STATE3, T1
+ vaesenc STATE7, STATE6, STATE7
+ vaesenc STATE6, STATE5, STATE6
+ vaesenc STATE5, STATE4, STATE5
+ vpternlogq $0x96, \m1, T1, STATE4
+ vaesenc STATE3, STATE2, STATE3
+ vaesenc STATE2, STATE1, STATE2
+ vaesenc STATE1, STATE0, STATE1
+ vpternlogq $0x96, \m0, T0, STATE0
+.endm
+
+/*
+ * Initialize STATE[0-7] from the key at KEYP and the nonce at IVP, and set
+ * ZERO. Changed: MSG0, MSG1, T0-T2.
+ */
+.macro aegis128x4_init
+ vpxor ZERO_XMM, ZERO_XMM, ZERO_XMM
+
+ /* broadcast nonce and key to the four lanes: */
+ vbroadcasti32x4 (IVP), MSG0
+ vbroadcasti32x4 (KEYP), MSG1
+
+ /* load the constants: */
+ vbroadcasti32x4 .Laegis128x4_const_0(%rip), STATE2
+ vbroadcasti32x4 .Laegis128x4_const_1(%rip), STATE1
+ vmovdqa64 .Laegis128x4_ctx(%rip), T2
+
+ vpxorq MSG0, MSG1, STATE0 /* key ^ nonce */
+ vmovdqa64 STATE0, STATE4 /* key ^ nonce */
+ vmovdqa64 STATE1, STATE3 /* c1 */
+ vpxorq STATE2, MSG1, STATE5 /* key ^ c0 */
+ vpxorq STATE1, MSG1, STATE6 /* key ^ c1 */
+ vmovdqa64 STATE5, STATE7 /* key ^ c0 */
+
+ /* update 10 times with nonce / key: */
+ .rept 10
+ vpxorq T2, STATE3, STATE3
+ vpxorq T2, STATE7, STATE7
+ aegis128x4_update MSG0, MSG1
+ .endr
+.endm
+
+/*
+ * Encrypt (\enc=1) or decrypt (\enc=0) the 128 bytes in MSG0/MSG1 in place
+ * using the keystream of the current state:
+ * z0 = S1 ^ S6 ^ (S2 & S3)
+ * z1 = S2 ^ S5 ^ (S6 & S7)
+ * On encryption the result goes to T2/T3 and MSG0/MSG1 keep the plaintext.
+ * On decryption MSG0/MSG1 are replaced by the plaintext.
+ */
+.macro crypt_msg enc
+.if \enc
+ vmovdqa64 STATE2, T2
+ vmovdqa64 STATE6, T3
+ vpternlogq $0x6a, MSG0, STATE3, T2 /* (A & B) ^ C */
+ vpternlogq $0x6a, MSG1, STATE7, T3
+ vpternlogq $0x96, STATE6, STATE1, T2
+ vpternlogq $0x96, STATE5, STATE2, T3
+.else
+ vpternlogq $0x78, STATE3, STATE2, MSG0 /* A ^ (B & C) */
+ vpternlogq $0x78, STATE7, STATE6, MSG1
+ vpternlogq $0x96, STATE6, STATE1, MSG0
+ vpternlogq $0x96, STATE5, STATE2, MSG1
+.endif
+.endm
+
+/*
+ * Encrypt or decrypt the 128-byte block at SRC into DST and absorb the
+ * plaintext. Changed: MSG0, MSG1, T0-T3.
+ */
+.macro crypt_block enc
+ vmovdqu64 0x00(SRC), MSG0
+ vmovdqu64 0x40(SRC), MSG1
+ crypt_msg \enc
+.if \enc
+ vmovdqu64 T2, 0x00(DST)
+ vmovdqu64 T3, 0x40(DST)
+.else
+ vmovdqu64 MSG0, 0x00(DST)
+ vmovdqu64 MSG1, 0x40(DST)
+.endif
+ aegis128x4_update MSG0, MSG1
+.endm
+
+/*
+ * Encrypt or decrypt the partial block at SRC into DST and absorb the
+ * zero-padded plaintext. %k1 and %k2 must hold the masks set up by
+ * tail_masks. Changed: MSG0, MSG1, T0-T3.
+ */
+.macro crypt_tail enc
+ /* masked loads zero the padding and never fault past the end */
+ vmovdqu8 0x00(SRC), MSG0{%k1}{z}
+ vmovdqu8 0x40(SRC), MSG1{%k2}{z}
+ crypt_msg \enc
+.if \enc
+ vmovdqu8 T2, 0x00(DST){%k1}
+ vmovdqu8 T3, 0x40(DST){%k2}
+.else
+ vmovdqu8 MSG0, 0x00(DST){%k1}
+ vmovdqu8 MSG1, 0x40(DST){%k2}
+ /* zero the keystream bytes past the end before absorbing: */
+ vmovdqu8 MSG0, MSG0{%k1}{z}
+ vmovdqu8 MSG1, MSG1{%k2}{z}
+.endif
+ aegis128x4_update MSG0, MSG1
+.endm
+
+/*
+ * Encrypt or decrypt LEN bytes (nonzero multiple of RATE) from SRC to DST,
+ * advancing SRC and DST. LEN is zero on exit.
+ */
+.macro crypt_loop enc
+.Lcrypt\@:
+ crypt_block \enc
+ add $RATE, SRC
+ add $RATE, DST
+ sub $RATE, LEN
+ jnz .Lcrypt\@
+.endm
+
+/* Absorb the 128-byte block at \src. */
+.macro ad_block src
+ vmovdqu64 0x00(\src), MSG0
+ vmovdqu64 0x40(\src), MSG1
+ aegis128x4_update MSG0, MSG1
+.endm
+
+.macro load_state
+ vmovdqu64 0x000(STATEP), STATE0
+ vmovdqu64 0x040(STATEP), STATE1
+ vmovdqu64 0x080(STATEP), STATE2
+ vmovdqu64 0x0c0(STATEP), STATE3
+ vmovdqu64 0x100(STATEP), STATE4
+ vmovdqu64 0x140(STATEP), STATE5
+ vmovdqu64 0x180(STATEP), STATE6
+ vmovdqu64 0x1c0(STATEP), STATE7
+.endm
+
+.macro store_state
+ vmovdqu64 STATE0, 0x000(STATEP)
+ vmovdqu64 STATE1, 0x040(STATEP)
+ vmovdqu64 STATE2, 0x080(STATEP)
+ vmovdqu64 STATE3, 0x0c0(STATEP)
+ vmovdqu64 STATE4, 0x100(STATEP)
+ vmovdqu64 STATE5, 0x140(STATEP)
+ vmovdqu64 STATE6, 0x180(STATEP)
+ vmovdqu64 STATE7, 0x1c0(STATEP)
+.endm
+
+/*
+ * Set %k1 and %k2 to the byte masks covering the first \len bytes of a
+ * 128-byte block, split into its two 64-byte halves. 1 <= \len <= 127.
+ * \len is a 32-bit register and \len64 its 64-bit alias; both are clobbered,
+ * as well as %rax and %r11.
+ */
+.macro tail_masks len, len64
+ mov $-1, %rax
+ bzhi \len64, %rax, %r11 /* bzhi only uses bits [7:0] */
+ kmovq %r11, %k1
+ xor %r11d, %r11d
+ sub $64, \len
+ cmovl %r11d, \len
+ bzhi \len64, %rax, %rax
+ kmovq %rax, %k2
+.endm
+
+/*
+ * XOR the four 128-bit lanes of the zmm register \z together into the xmm
+ * register \x (the low part of \z). \y is the ymm alias of \z.
+ */
+.macro fold_lanes z, y, x, tmpy, tmpx
+ vextracti64x4 $1, \z, \tmpy
+ vpxor \tmpy, \y, \y
+ vextracti128 $1, \y, \tmpx
+ vpxor \tmpx, \x, \x
+.endm
+
+/*
+ * Finalize and store the AUTHSIZE-byte (16 or 32) tag at TAG, then return.
+ * ASSOCLEN and CRYPTLEN are 64-bit registers. Changed: %rax, %r9, MSG0 and
+ * all the state.
+ */
+.macro aegis128x4_final_and_ret
+ /* t = S2 ^ (LE64(assoclen * 8) || LE64(cryptlen * 8)) in each lane */
+ mov ASSOCLEN, %rax
+ shl $3, %rax
+ mov CRYPTLEN, %r9
+ shl $3, %r9
+ vmovq %rax, %xmm8
+ vpinsrq $1, %r9, %xmm8, %xmm8
+ vshufi64x2 $0, MSG0, MSG0, MSG0
+ vpxorq STATE2, MSG0, MSG0
+
+ .rept 7
+ aegis128x4_update MSG0, MSG0
+ .endr
+
+ cmp $16, AUTHSIZE
+ jne .Lfinal_256\@
+
+ /* tag = fold(S0 ^ S1 ^ S2 ^ S3 ^ S4 ^ S5 ^ S6) */
+ vpternlogq $0x96, STATE2, STATE1, STATE0
+ vpternlogq $0x96, STATE4, STATE3, STATE0
+ vpternlogq $0x96, STATE6, STATE5, STATE0
+ fold_lanes STATE0, %ymm0, %xmm0, %ymm10, %xmm10
+ vmovdqu %xmm0, 0x00(TAG)
+ vzeroupper // This is needed after using ymm or zmm registers.
+ RET
+
+.Lfinal_256\@:
+ /* tag = fold(S0 ^ S1 ^ S2 ^ S3) || fold(S4 ^ S5 ^ S6 ^ S7) */
+ vpternlogq $0x96, STATE2, STATE1, STATE0
+ vpxorq STATE3, STATE0, STATE0
+ vpternlogq $0x96, STATE6, STATE5, STATE4
+ vpxorq STATE7, STATE4, STATE4
+ fold_lanes STATE0, %ymm0, %xmm0, %ymm10, %xmm10
+ fold_lanes STATE4, %ymm4, %xmm4, %ymm11, %xmm11
+ vmovdqu %xmm0, 0x00(TAG)
+ vmovdqu %xmm4, 0x10(TAG)
+ vzeroupper // This is needed after using ymm or zmm registers.
+ RET
+.endm
+
+/*
+ * void aegis128x4_init_vaes_avx512(union aegis128x4_state *state,
+ * const u8 key[AEGIS128X4_KEY_SIZE],
+ * const u8 nonce[AEGIS128X4_NONCE_SIZE]);
+ */
+SYM_FUNC_START(aegis128x4_init_vaes_avx512)
+ .set STATEP, %rdi
+ .set KEYP, %rsi
+ .set IVP, %rdx
+
+ aegis128x4_init
+ store_state
+ vzeroupper // This is needed after using ymm or zmm registers.
+ RET
+SYM_FUNC_END(aegis128x4_init_vaes_avx512)
+
+/*
+ * void aegis128x4_absorb_vaes_avx512(union aegis128x4_state *state,
+ * const u8 *src, size_t len);
+ *
+ * len must be a multiple of 128.
+ */
+SYM_FUNC_START(aegis128x4_absorb_vaes_avx512)
+ .set STATEP, %rdi
+ .set SRC, %rsi
+ .set LEN, %rdx
+
+ test LEN, LEN
+ jz .Lad_out
+
+ vpxor ZERO_XMM, ZERO_XMM, ZERO_XMM
+ load_state
+.Lad_loop:
+ ad_block SRC
+ add $RATE, SRC
+ sub $RATE, LEN
+ jnz .Lad_loop
+
+ store_state
+ vzeroupper // This is needed after using ymm or zmm registers.
+.Lad_out:
+ RET
+SYM_FUNC_END(aegis128x4_absorb_vaes_avx512)
+
+/*
+ * void aegis128x4_enc_vaes_avx512(union aegis128x4_state *state,
+ * u8 *dst, const u8 *src, size_t len);
+ * void aegis128x4_dec_vaes_avx512(union aegis128x4_state *state,
+ * u8 *dst, const u8 *src, size_t len);
+ *
+ * len must be nonzero and a multiple of 128.
+ */
+.macro aegis128x4_crypt enc
+ .set STATEP, %rdi
+ .set DST, %rsi
+ .set SRC, %rdx
+ .set LEN, %rcx
+
+ vpxor ZERO_XMM, ZERO_XMM, ZERO_XMM
+ load_state
+ crypt_loop \enc
+ store_state
+ vzeroupper // This is needed after using ymm or zmm registers.
+ RET
+.endm
+
+SYM_FUNC_START(aegis128x4_enc_vaes_avx512)
+ aegis128x4_crypt 1
+SYM_FUNC_END(aegis128x4_enc_vaes_avx512)
+
+SYM_FUNC_START(aegis128x4_dec_vaes_avx512)
+ aegis128x4_crypt 0
+SYM_FUNC_END(aegis128x4_dec_vaes_avx512)
+
+/*
+ * void aegis128x4_final_vaes_avx512(union aegis128x4_state *state, u8 *tag,
+ * u64 assoclen, u64 cryptlen,
+ * unsigned int authsize);
+ *
+ * authsize must be 16 or 32.
+ */
+SYM_FUNC_START(aegis128x4_final_vaes_avx512)
+ .set STATEP, %rdi
+ .set TAG, %rsi
+ .set ASSOCLEN, %rdx
+ .set CRYPTLEN, %rcx
+ .set AUTHSIZE, %r8d
+
+ vpxor ZERO_XMM, ZERO_XMM, ZERO_XMM
+ load_state
+ aegis128x4_final_and_ret
+SYM_FUNC_END(aegis128x4_final_vaes_avx512)
+
+/*
+ * void aegis128x4_encrypt_vaes_avx512(const u8 key[AEGIS128X4_KEY_SIZE],
+ * const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ * const u8 *ad, size_t adlen,
+ * const u8 *src, u8 *dst, size_t len,
+ * u8 *tag, unsigned int authsize);
+ * void aegis128x4_decrypt_vaes_avx512(...same arguments...);
+ *
+ * En/decrypt a whole message, keeping the state in registers throughout.
+ * authsize must be 16 or 32, and the computed tag is stored at tag.
+ */
+.macro aegis128x4_oneshot enc
+ .set KEYP, %rdi
+ .set IVP, %rsi
+ .set ADP, %rdx
+ .set ASSOCLEN, %r10
+ .set SRC, %r8
+ .set DST, %r9
+ .set LEN, %rcx
+
+ mov %rcx, ASSOCLEN
+ aegis128x4_init /* KEYP and IVP are free after this */
+
+ /* absorb the associated data: */
+ cmp $RATE, %rcx
+ jb .Lad_tail\@
+.Lad_loop\@:
+ ad_block ADP
+ add $RATE, ADP
+ sub $RATE, %rcx
+ cmp $RATE, %rcx
+ jae .Lad_loop\@
+.Lad_tail\@:
+ test %rcx, %rcx
+ jz .Lad_done\@
+ tail_masks %ecx, %rcx
+ vmovdqu8 0x00(ADP), MSG0{%k1}{z}
+ vmovdqu8 0x40(ADP), MSG1{%k2}{z}
+ aegis128x4_update MSG0, MSG1
+.Lad_done\@:
+
+ /*
+ * en/decrypt the payload; len, tag and authsize are passed on the
+ * stack at 8(%rsp), 16(%rsp) and 24(%rsp) respectively.
+ */
+ mov 8(%rsp), LEN
+ mov LEN, %rsi
+ and $RATE - 1, %esi
+ and $~(RATE - 1), LEN
+ jz .Lcrypt_tail\@
+ crypt_loop \enc
+.Lcrypt_tail\@:
+ test %esi, %esi
+ jz .Lcrypt_done\@
+ tail_masks %esi, %rsi
+ crypt_tail \enc
+.Lcrypt_done\@:
+
+ .set TAG, %rdi
+ .set CRYPTLEN, %rcx
+ .set AUTHSIZE, %r8d
+ mov 16(%rsp), TAG
+ mov 8(%rsp), CRYPTLEN
+ mov 24(%rsp), AUTHSIZE
+ aegis128x4_final_and_ret
+.endm
+
+SYM_FUNC_START(aegis128x4_encrypt_vaes_avx512)
+ aegis128x4_oneshot 1
+SYM_FUNC_END(aegis128x4_encrypt_vaes_avx512)
+
+SYM_FUNC_START(aegis128x4_decrypt_vaes_avx512)
+ aegis128x4_oneshot 0
+SYM_FUNC_END(aegis128x4_decrypt_vaes_avx512)
diff --git a/lib/crypto/x86/aegis128x4.h b/lib/crypto/x86/aegis128x4.h
new file mode 100644
index 000000000000..dcb9853062fd
--- /dev/null
+++ b/lib/crypto/x86/aegis128x4.h
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * AEGIS-128X4 optimized using VAES and AVX-512
+ *
+ * Copyright 2026 Antonio Quartulli <antonio@mandelbit.com>
+ */
+
+#include <asm/fpu/api.h>
+#include <linux/jump_label.h>
+#include <linux/sizes.h>
+
+static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_vaes_avx512);
+
+asmlinkage void
+aegis128x4_init_vaes_avx512(union aegis128x4_state *state,
+ const u8 key[AEGIS128X4_KEY_SIZE],
+ const u8 nonce[AEGIS128X4_NONCE_SIZE]);
+asmlinkage void aegis128x4_absorb_vaes_avx512(union aegis128x4_state *state,
+ const u8 *src, size_t len);
+asmlinkage void aegis128x4_enc_vaes_avx512(union aegis128x4_state *state,
+ u8 *dst, const u8 *src, size_t len);
+asmlinkage void aegis128x4_dec_vaes_avx512(union aegis128x4_state *state,
+ u8 *dst, const u8 *src, size_t len);
+asmlinkage void aegis128x4_final_vaes_avx512(union aegis128x4_state *state,
+ u8 *tag, u64 assoclen,
+ u64 cryptlen,
+ unsigned int authsize);
+asmlinkage void
+aegis128x4_encrypt_vaes_avx512(const u8 key[AEGIS128X4_KEY_SIZE],
+ const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ const u8 *ad, size_t ad_len, const u8 *src,
+ u8 *dst, size_t len, u8 *tag,
+ unsigned int authsize);
+asmlinkage void
+aegis128x4_decrypt_vaes_avx512(const u8 key[AEGIS128X4_KEY_SIZE],
+ const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ const u8 *ad, size_t ad_len, const u8 *src,
+ u8 *dst, size_t len, u8 *tag,
+ unsigned int authsize);
+
+static bool aegis128x4_simd_usable(void)
+{
+ return static_branch_likely(&have_vaes_avx512) && irq_fpu_usable();
+}
+
+static bool aegis128x4_init_arch(union aegis128x4_state *st,
+ const u8 key[AEGIS128X4_KEY_SIZE],
+ const u8 nonce[AEGIS128X4_NONCE_SIZE])
+{
+ if (!aegis128x4_simd_usable())
+ return false;
+ kernel_fpu_begin_mask(0);
+ aegis128x4_init_vaes_avx512(st, key, nonce);
+ kernel_fpu_end();
+ return true;
+}
+
+static bool aegis128x4_absorb_arch(union aegis128x4_state *st, const u8 *src,
+ size_t nblocks)
+{
+ size_t len = nblocks * AEGIS128X4_BLOCK_SIZE;
+
+ if (!len)
+ return true;
+ if (!aegis128x4_simd_usable())
+ return false;
+ do {
+ size_t n = min_t(size_t, len, SZ_4K);
+
+ kernel_fpu_begin_mask(0);
+ aegis128x4_absorb_vaes_avx512(st, src, n);
+ kernel_fpu_end();
+ src += n;
+ len -= n;
+ } while (len);
+ return true;
+}
+
+static bool aegis128x4_crypt_blocks_arch(union aegis128x4_state *st, u8 *dst,
+ const u8 *src, size_t nblocks,
+ bool enc)
+{
+ size_t len = nblocks * AEGIS128X4_BLOCK_SIZE;
+
+ if (!len)
+ return true;
+ if (!aegis128x4_simd_usable())
+ return false;
+ do {
+ size_t n = min_t(size_t, len, SZ_4K);
+
+ kernel_fpu_begin_mask(0);
+ if (enc)
+ aegis128x4_enc_vaes_avx512(st, dst, src, n);
+ else
+ aegis128x4_dec_vaes_avx512(st, dst, src, n);
+ kernel_fpu_end();
+ src += n;
+ dst += n;
+ len -= n;
+ } while (len);
+ return true;
+}
+
+static bool aegis128x4_enc_arch(union aegis128x4_state *st, u8 *dst,
+ const u8 *src, size_t nblocks)
+{
+ return aegis128x4_crypt_blocks_arch(st, dst, src, nblocks, true);
+}
+
+static bool aegis128x4_dec_arch(union aegis128x4_state *st, u8 *dst,
+ const u8 *src, size_t nblocks)
+{
+ return aegis128x4_crypt_blocks_arch(st, dst, src, nblocks, false);
+}
+
+static bool aegis128x4_final_arch(union aegis128x4_state *st, u8 *authtag,
+ u64 ad_len, u64 data_len, size_t authtag_len)
+{
+ if (!aegis128x4_simd_usable())
+ return false;
+ kernel_fpu_begin_mask(0);
+ aegis128x4_final_vaes_avx512(st, authtag, ad_len, data_len,
+ authtag_len);
+ kernel_fpu_end();
+ return true;
+}
+
+/*
+ * Process a whole message in one call, keeping the state in registers, if it
+ * is short enough for a single kernel-mode FPU section. Longer messages go
+ * through the incremental functions, which split them.
+ */
+static bool aegis128x4_crypt_arch(u8 *dst, const u8 *src, size_t data_len,
+ u8 *authtag, const u8 *ad, size_t ad_len,
+ const u8 nonce[AEGIS128X4_NONCE_SIZE],
+ const struct aegis128x4_key *key, bool enc)
+{
+ if (ad_len + data_len > SZ_4K || !aegis128x4_simd_usable())
+ return false;
+ kernel_fpu_begin_mask(0);
+ if (enc)
+ aegis128x4_encrypt_vaes_avx512(key->key, nonce, ad, ad_len, src,
+ dst, data_len, authtag,
+ key->authtag_len);
+ else
+ aegis128x4_decrypt_vaes_avx512(key->key, nonce, ad, ad_len, src,
+ dst, data_len, authtag,
+ key->authtag_len);
+ kernel_fpu_end();
+ return true;
+}
+
+#define aegis128x4_mod_init_arch aegis128x4_mod_init_arch
+static void aegis128x4_mod_init_arch(void)
+{
+ /*
+ * There is no 256-bit code yet, so unlike AES-GCM this is also used on
+ * CPUs with X86_FEATURE_PREFER_YMM: the alternative is the much slower
+ * generic code.
+ */
+ if (boot_cpu_has(X86_FEATURE_VAES) &&
+ boot_cpu_has(X86_FEATURE_AVX2) &&
+ boot_cpu_has(X86_FEATURE_AVX512BW) &&
+ boot_cpu_has(X86_FEATURE_BMI2))
+ static_branch_enable(&have_vaes_avx512);
+}
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [RFC PATCH 5/5] crypto: aegis128x4 - Add AEGIS-128X4 AEAD algorithm
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
` (3 preceding siblings ...)
2026-11-12 12:54 ` [RFC PATCH 4/5] lib/crypto: x86/aegis128x4: Add VAES-AVX512 optimized code Antonio Quartulli
@ 2026-11-12 12:54 ` Antonio Quartulli
2026-11-12 16:20 ` [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Ard Biesheuvel
2026-11-12 16:50 ` Jason A. Donenfeld
6 siblings, 0 replies; 12+ messages in thread
From: Antonio Quartulli @ 2026-11-12 12:54 UTC (permalink / raw)
To: linux-crypto
Cc: Antonio Quartulli, Eric Biggers, Ard Biesheuvel,
Jason A . Donenfeld, Herbert Xu
From: Antonio Quartulli <antonio@mandelbit.com>
Add the "aegis128x4" AEAD algorithm, as a wrapper around the AEGIS-128X4
library, together with the RFC 10032 test vectors for the self-tests.
Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
---
crypto/Kconfig | 11 +++
crypto/Makefile | 1 +
crypto/aegis128x4.c | 173 ++++++++++++++++++++++++++++++++++++++++++++
crypto/testmgr.c | 7 ++
crypto/testmgr.h | 119 ++++++++++++++++++++++++++++++
5 files changed, 311 insertions(+)
create mode 100644 crypto/aegis128x4.c
diff --git a/crypto/Kconfig b/crypto/Kconfig
index 981d5dc422d4..d97f049ac54b 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -760,6 +760,17 @@ config CRYPTO_AEGIS128_SIMD
Architecture: arm or arm64 using:
- NEON (Advanced SIMD) extension
+config CRYPTO_AEGIS128X4
+ tristate "AEGIS-128X4"
+ select CRYPTO_AEAD
+ select CRYPTO_LIB_AEGIS128X4
+ help
+ AEGIS-128X4 AEAD algorithm (RFC 10032), the 4-way parallel variant of
+ AEGIS-128L.
+
+ This is a wrapper around the AEGIS-128X4 library, which has an
+ optimized implementation for x86_64 CPUs with VAES and AVX-512.
+
config CRYPTO_CHACHA20POLY1305
tristate "ChaCha20-Poly1305"
select CRYPTO_CHACHA20
diff --git a/crypto/Makefile b/crypto/Makefile
index 33bb5ad595e8..cc4a41719221 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -116,6 +116,7 @@ aegis128-cflags-$(CONFIG_CC_IS_GCC) += -ffixed-q16 -ffixed-q17 -ffixed-q18 \
CFLAGS_aegis128-neon-inner.o += $(aegis128-cflags-y)
aegis128-$(CONFIG_CRYPTO_AEGIS128_SIMD) += aegis128-neon.o aegis128-neon-inner.o
endif
+obj-$(CONFIG_CRYPTO_AEGIS128X4) += aegis128x4.o
obj-$(CONFIG_CRYPTO_PCRYPT) += pcrypt.o
obj-$(CONFIG_CRYPTO_CRYPTD) += cryptd.o
diff --git a/crypto/aegis128x4.c b/crypto/aegis128x4.c
new file mode 100644
index 000000000000..57410fa71032
--- /dev/null
+++ b/crypto/aegis128x4.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Crypto API support for AEGIS-128X4, using the library
+ *
+ * Copyright 2026 Antonio Quartulli <antonio@mandelbit.com>
+ */
+
+#include <crypto/aegis128x4.h>
+#include <crypto/internal/aead.h>
+#include <crypto/scatterwalk.h>
+#include <linux/module.h>
+
+static_assert(__alignof__(struct aegis128x4_key) <= CRYPTO_MINALIGN);
+
+static int crypto_aegis128x4_setkey(struct crypto_aead *tfm, const u8 *in_key,
+ unsigned int key_len)
+{
+ struct aegis128x4_key *key = crypto_aead_ctx(tfm);
+
+ return aegis128x4_preparekey(key, in_key, key_len,
+ crypto_aead_authsize(tfm));
+}
+
+static int crypto_aegis128x4_setauthsize(struct crypto_aead *tfm,
+ unsigned int authsize)
+{
+ struct aegis128x4_key *key = crypto_aead_ctx(tfm);
+
+ if (authsize != 16 && authsize != 32)
+ return -EINVAL;
+ /* Synchronize the tag length to the struct aegis128x4_key. */
+ key->authtag_len = authsize;
+ return 0;
+}
+
+static void crypto_aegis128x4_process_ad(struct aegis128x4_ctx *ctx,
+ struct scatterlist *src,
+ unsigned int assoclen)
+{
+ struct scatter_walk walk;
+
+ scatterwalk_start(&walk, src);
+ while (assoclen) {
+ unsigned int n = scatterwalk_next(&walk, assoclen);
+
+ aegis128x4_auth_update(ctx, walk.addr, n);
+ scatterwalk_done_src(&walk, n);
+ assoclen -= n;
+ }
+}
+
+/*
+ * En/decrypt @len bytes from @src to @dst, starting @pos bytes into both. The
+ * incremental functions accept any length, so each piece is passed directly.
+ * The scatterlists must describe either entirely different memory or entirely
+ * the same memory.
+ */
+static void crypto_aegis128x4_crypt(struct aegis128x4_ctx *ctx,
+ struct scatterlist *dst,
+ struct scatterlist *src, unsigned int pos,
+ unsigned int len, bool enc)
+{
+ struct scatter_walk dst_walk, src_walk;
+
+ if (!len)
+ return;
+
+ scatterwalk_start_at_pos(&dst_walk, dst, pos);
+ scatterwalk_start_at_pos(&src_walk, src, pos);
+ do {
+ unsigned int n = min(scatterwalk_clamp(&dst_walk, len),
+ scatterwalk_clamp(&src_walk, len));
+ const u8 *src_virt;
+ u8 *dst_virt;
+
+ scatterwalk_map(&dst_walk);
+ dst_virt = dst_walk.addr;
+ /* map a highmem page only once when in-place */
+ if (IS_ENABLED(CONFIG_HIGHMEM) &&
+ offset_in_page(src_walk.offset) ==
+ offset_in_page(dst_walk.offset) &&
+ sg_page(src_walk.sg) + src_walk.offset / PAGE_SIZE ==
+ sg_page(dst_walk.sg) + dst_walk.offset / PAGE_SIZE) {
+ src_virt = dst_virt;
+ } else {
+ scatterwalk_map(&src_walk);
+ src_virt = src_walk.addr;
+ }
+ if (enc)
+ aegis128x4_encrypt_update(ctx, dst_virt, src_virt, n);
+ else
+ aegis128x4_decrypt_update(ctx, dst_virt, src_virt, n);
+ if (src_virt != dst_virt)
+ scatterwalk_unmap(&src_walk);
+ scatterwalk_advance(&src_walk, n);
+ scatterwalk_done_dst(&dst_walk, n);
+ len -= n;
+ } while (len);
+}
+
+static int crypto_aegis128x4_encrypt(struct aead_request *req)
+{
+ struct crypto_aead *tfm = crypto_aead_reqtfm(req);
+ const struct aegis128x4_key *key = crypto_aead_ctx(tfm);
+ u8 authtag[AEGIS128X4_MAX_AUTHTAG_SIZE];
+ struct aegis128x4_ctx ctx;
+
+ aegis128x4_init(&ctx, req->iv, key);
+ crypto_aegis128x4_process_ad(&ctx, req->src, req->assoclen);
+ crypto_aegis128x4_crypt(&ctx, req->dst, req->src, req->assoclen,
+ req->cryptlen, true);
+ aegis128x4_encrypt_final(&ctx, authtag);
+ memcpy_to_sglist(req->dst, req->assoclen + req->cryptlen, authtag,
+ key->authtag_len);
+ memzero_explicit(authtag, sizeof(authtag));
+ return 0;
+}
+
+static int crypto_aegis128x4_decrypt(struct aead_request *req)
+{
+ struct crypto_aead *tfm = crypto_aead_reqtfm(req);
+ const struct aegis128x4_key *key = crypto_aead_ctx(tfm);
+ u8 authtag[AEGIS128X4_MAX_AUTHTAG_SIZE];
+ struct aegis128x4_ctx ctx;
+ unsigned int data_len;
+ int err;
+
+ /* crypto_aead_decrypt() already checked cryptlen >= authtag_len. */
+ data_len = req->cryptlen - key->authtag_len;
+
+ aegis128x4_init(&ctx, req->iv, key);
+ crypto_aegis128x4_process_ad(&ctx, req->src, req->assoclen);
+ crypto_aegis128x4_crypt(&ctx, req->dst, req->src, req->assoclen,
+ data_len, false);
+ memcpy_from_sglist(authtag, req->src, req->assoclen + data_len,
+ key->authtag_len);
+ err = aegis128x4_decrypt_final(&ctx, authtag);
+ memzero_explicit(authtag, sizeof(authtag));
+ return err;
+}
+
+static struct aead_alg crypto_aegis128x4_alg = {
+ .base.cra_name = "aegis128x4",
+ .base.cra_driver_name = "aegis128x4-lib",
+ .base.cra_priority = 300,
+ .base.cra_blocksize = 1,
+ .base.cra_ctxsize = sizeof(struct aegis128x4_key),
+ .base.cra_module = THIS_MODULE,
+ .setkey = crypto_aegis128x4_setkey,
+ .setauthsize = crypto_aegis128x4_setauthsize,
+ .encrypt = crypto_aegis128x4_encrypt,
+ .decrypt = crypto_aegis128x4_decrypt,
+ .ivsize = AEGIS128X4_NONCE_SIZE,
+ .maxauthsize = AEGIS128X4_MAX_AUTHTAG_SIZE,
+ .chunksize = AEGIS128X4_BLOCK_SIZE,
+};
+
+static int __init crypto_aegis128x4_mod_init(void)
+{
+ return crypto_register_aead(&crypto_aegis128x4_alg);
+}
+module_init(crypto_aegis128x4_mod_init);
+
+static void __exit crypto_aegis128x4_mod_exit(void)
+{
+ crypto_unregister_aead(&crypto_aegis128x4_alg);
+}
+module_exit(crypto_aegis128x4_mod_exit);
+
+MODULE_DESCRIPTION("AEGIS-128X4 AEAD algorithm");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CRYPTO("aegis128x4");
+MODULE_ALIAS_CRYPTO("aegis128x4-lib");
diff --git a/crypto/testmgr.c b/crypto/testmgr.c
index 9419d61d0602..dd6e3c2cabec 100644
--- a/crypto/testmgr.c
+++ b/crypto/testmgr.c
@@ -4068,6 +4068,13 @@ static const struct alg_test_desc alg_test_descs[] = {
.suite = {
.aead = __VECS(aegis128_tv_template)
}
+ }, {
+ .alg = "aegis128x4",
+ .generic_driver = "aegis128x4-lib",
+ .test = alg_test_aead,
+ .suite = {
+ .aead = __VECS(aegis128x4_tv_template)
+ }
}, {
.alg = "authenc(hmac(md5),cbc(aes))",
.generic_driver = "authenc(hmac-md5-lib,cbc(aes-lib))",
diff --git a/crypto/testmgr.h b/crypto/testmgr.h
index 0ee15a8fae27..6bc819bc0c67 100644
--- a/crypto/testmgr.h
+++ b/crypto/testmgr.h
@@ -23755,6 +23755,125 @@ static const struct aead_testvec aegis128_tv_template[] = {
},
};
+/* AEGIS-128X4 test vectors from RFC 10032, Appendix A.5 */
+static const struct aead_testvec aegis128x4_tv_template[] = {
+ {
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .klen = 16,
+ .iv = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .assoc = "",
+ .alen = 0,
+ .ptext = "",
+ .plen = 0,
+ .ctext = "\x5b\xef\x76\x2d\x09\x47\xc0\x04"
+ "\x55\xb9\x7b\xb3\xaf\x30\xdf\xa3",
+ .clen = 16,
+ }, {
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .klen = 16,
+ .iv = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .assoc = "",
+ .alen = 0,
+ .ptext = "",
+ .plen = 0,
+ .ctext = "\xa4\xb2\x54\x37\xf4\xbe\x93\xcf"
+ "\xa8\x56\xa2\xf2\x7e\x44\x16\xb4"
+ "\x2c\xac\x79\xfd\x46\x98\xf2\xcd"
+ "\xbe\x6a\xf2\x56\x73\xe1\x0a\x68",
+ .clen = 32,
+ }, {
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .klen = 16,
+ .iv = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .assoc = "\x01\x02\x03\x04\x01\x02\x03\x04",
+ .alen = 8,
+ .ptext = "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07",
+ .plen = 120,
+ .ctext = "\xe8\x36\x11\x85\x62\xf4\x47\x9c"
+ "\x9d\x35\xc1\x73\x56\xa8\x33\x11"
+ "\x4c\x21\xf9\xaa\x39\xe4\xdd\xa5"
+ "\xe5\xc8\x7f\x41\x52\xa0\x0f\xce"
+ "\x9a\x7c\x38\xf8\x32\xea\xfe\x8b"
+ "\x1c\x12\xf8\xa7\xcf\x12\xa8\x1a"
+ "\x1a\xd8\xa9\xc2\x4b\xa9\xde\xdf"
+ "\xbd\xaa\x58\x6f\xfe\xa6\x7d\xdc"
+ "\x80\x1e\xa9\x7d\x9a\xb4\xa8\x72"
+ "\xf4\x2d\x0e\x35\x2e\x27\x13\xda"
+ "\xcd\x60\x9f\x94\x42\xc1\x75\x17"
+ "\xc5\xa2\x9d\xaf\x3e\x2a\x3f\xac"
+ "\x4f\xf6\xb1\x38\x0c\x4e\x46\xdf"
+ "\x7b\x08\x6a\xf6\xce\x6b\xc1\xed"
+ "\x59\x4b\x8d\xd6\x4a\xed\x2a\x7e"
+ "\x0e\x56\xab\x94\xe2\xe8\x5d\xb8"
+ "\x0f\x9d\x54\x01\x0c\xaa\xbf\xb4",
+ .clen = 136,
+ }, {
+ .key = "\x00\x01\x02\x03\x04\x05\x06\x07"
+ "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ .klen = 16,
+ .iv = "\x10\x11\x12\x13\x14\x15\x16\x17"
+ "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+ .assoc = "\x01\x02\x03\x04\x01\x02\x03\x04",
+ .alen = 8,
+ .ptext = "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07"
+ "\x04\x05\x06\x07\x04\x05\x06\x07",
+ .plen = 120,
+ .ctext = "\xe8\x36\x11\x85\x62\xf4\x47\x9c"
+ "\x9d\x35\xc1\x73\x56\xa8\x33\x11"
+ "\x4c\x21\xf9\xaa\x39\xe4\xdd\xa5"
+ "\xe5\xc8\x7f\x41\x52\xa0\x0f\xce"
+ "\x9a\x7c\x38\xf8\x32\xea\xfe\x8b"
+ "\x1c\x12\xf8\xa7\xcf\x12\xa8\x1a"
+ "\x1a\xd8\xa9\xc2\x4b\xa9\xde\xdf"
+ "\xbd\xaa\x58\x6f\xfe\xa6\x7d\xdc"
+ "\x80\x1e\xa9\x7d\x9a\xb4\xa8\x72"
+ "\xf4\x2d\x0e\x35\x2e\x27\x13\xda"
+ "\xcd\x60\x9f\x94\x42\xc1\x75\x17"
+ "\xc5\xa2\x9d\xaf\x3e\x2a\x3f\xac"
+ "\x4f\xf6\xb1\x38\x0c\x4e\x46\xdf"
+ "\x7b\x08\x6a\xf6\xce\x6b\xc1\xed"
+ "\x59\x4b\x8d\xd6\x4a\xed\x2a\x7e"
+ "\x69\xab\xf0\xf6\x4a\x13\x7d\xd6"
+ "\xe1\x22\x47\x8d\x77\x7e\x98\xbc"
+ "\x42\x28\x23\x00\x6c\xf5\x7f\x5e"
+ "\xe8\x22\xdd\x78\x39\x72\x30\xb2",
+ .clen = 152,
+ }
+};
+
static const struct drbg_testvec drbg_nopr_hmac_sha512_tv_template[] = {
/*
* Borrowed from the first applicable test vector from ACVP:
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
` (4 preceding siblings ...)
2026-11-12 12:54 ` [RFC PATCH 5/5] crypto: aegis128x4 - Add AEGIS-128X4 AEAD algorithm Antonio Quartulli
@ 2026-11-12 16:20 ` Ard Biesheuvel
2026-11-12 16:44 ` Jason A. Donenfeld
2026-11-12 16:50 ` Jason A. Donenfeld
6 siblings, 1 reply; 12+ messages in thread
From: Ard Biesheuvel @ 2026-11-12 16:20 UTC (permalink / raw)
To: Antonio Quartulli, linux-crypto
Cc: Antonio Quartulli, Eric Biggers, Jason A . Donenfeld, Herbert Xu
Hello Antonio,
On Thu, 12 Nov 2026, at 13:54, Antonio Quartulli wrote:
> From: Antonio Quartulli <antonio@mandelbit.com>
>
> This series adds AEGIS-128X4, the 4-way parallel variant of AEGIS-128L
> specified in RFC 10032 (Informational, September 2026), as a crypto
> library with an x86_64 VAES-AVX512 implementation, a KUnit test suite
> and benchmark, and a crypto API wrapper.
>
> AEGIS-128X4 runs four AEGIS-128L instances, one per 128-bit lane of a
> 512-bit vector, so that each state update is eight VAESENC instructions
> on zmm registers. On CPUs with VAES and AVX-512 it is very fast even for
> short messages, which is what makes it attractive for network protocols.
>
> The intended user is ovpn, the OpenVPN data channel offload driver. In
> single-stream ovpn tests over a 100 Gbit/s link, with both ciphers used
> through the crypto API, the sending CPU spent 1266 cycles per packet with
> AEGIS-128X4 against 1448 with AES-GCM over TCP, and 1032 against 1122
> with UDP GSO input. It is posted as an RFC because ovpn can't use it
> yet.
>
We used to have implementations of aegis128l and aegis256 in the kernel as
well (along with morus), but we removed them on the basis that it was
aegis128 that won the competition.
As far as I know, nobody actually uses any of that code, though, so I am
quite to surprised to see now that it was aegis128 that was dropped
from the portfolio, and aegis128l and aegis256 have made a miraculous
return, along with some other variants that appear to be tied to the
register width. What happens when 8-way vector registers become
commonplace? Create yet another aegis variant that case use them
efficiently?
So I'm struggling to see why we should take this RFC seriously, to be
honest ...
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4
2026-11-12 16:20 ` [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Ard Biesheuvel
@ 2026-11-12 16:44 ` Jason A. Donenfeld
2026-11-12 22:54 ` Antonio Quartulli
0 siblings, 1 reply; 12+ messages in thread
From: Jason A. Donenfeld @ 2026-11-12 16:44 UTC (permalink / raw)
To: Ard Biesheuvel
Cc: Antonio Quartulli, linux-crypto, Antonio Quartulli, Eric Biggers,
Herbert Xu
On Thu, Nov 12, 2026 at 5:20 PM Ard Biesheuvel <ardb@kernel.org> wrote:
> > This series adds AEGIS-128X4, the 4-way parallel variant of AEGIS-128L
> > specified in RFC 10032 (Informational, September 2026), as a crypto
> > library with an x86_64 VAES-AVX512 implementation, a KUnit test suite
> > and benchmark, and a crypto API wrapper.
> >
> > AEGIS-128X4 runs four AEGIS-128L instances, one per 128-bit lane of a
> > 512-bit vector, so that each state update is eight VAESENC instructions
> > on zmm registers. On CPUs with VAES and AVX-512 it is very fast even for
> > short messages, which is what makes it attractive for network protocols.
> >
> > The intended user is ovpn, the OpenVPN data channel offload driver. In
> > single-stream ovpn tests over a 100 Gbit/s link, with both ciphers used
> > through the crypto API, the sending CPU spent 1266 cycles per packet with
> > AEGIS-128X4 against 1448 with AES-GCM over TCP, and 1032 against 1122
> > with UDP GSO input. It is posted as an RFC because ovpn can't use it
> > yet.
> >
>
> We used to have implementations of aegis128l and aegis256 in the kernel as
> well (along with morus), but we removed them on the basis that it was
> aegis128 that won the competition.
>
> As far as I know, nobody actually uses any of that code, though, so I am
> quite to surprised to see now that it was aegis128 that was dropped
> from the portfolio, and aegis128l and aegis256 have made a miraculous
> return, along with some other variants that appear to be tied to the
> register width. What happens when 8-way vector registers become
> commonplace? Create yet another aegis variant that case use them
> efficiently?
>
> So I'm struggling to see why we should take this RFC seriously, to be
> honest ...
I'm also not really sure what's going on. CAESAR competition selected
aegis128 and listed 128l and 256 as "additional finalists". And then
RFC10032 has written up 128L, 256, 128X, 256X, but not the original
128 that was selected. And then none of this has been picked up by
NIST. Meanwhile, NIST's light weight crypto competition selected
ASCON, which as the "lightweight" as opposed to "high performance"
pick in CAESAR. So I dunno! I've been waiting for the dust to settle
for a little bit.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
` (5 preceding siblings ...)
2026-11-12 16:20 ` [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Ard Biesheuvel
@ 2026-11-12 16:50 ` Jason A. Donenfeld
6 siblings, 0 replies; 12+ messages in thread
From: Jason A. Donenfeld @ 2026-11-12 16:50 UTC (permalink / raw)
To: Antonio Quartulli
Cc: linux-crypto, Antonio Quartulli, Eric Biggers, Ard Biesheuvel,
Herbert Xu
On Thu, Nov 12, 2026 at 1:54 PM Antonio Quartulli <antonio@openvpn.net> wrote:
> through the crypto API, the sending CPU spent 1266 cycles per packet with
> AEGIS-128X4 against 1448 with AES-GCM over TCP, and 1032 against 1122
> with UDP GSO input. It is posted as an RFC because ovpn can't use it
> yet.
12.5% and 8.7% speedups over AES-GCM are sort of way lower than what I
would have hoped. I suppose it indicates that most of the expense
isn't crypto? Or that the wins aren't really realized on 1.5k packets?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4
2026-11-12 16:44 ` Jason A. Donenfeld
@ 2026-11-12 22:54 ` Antonio Quartulli
2026-11-13 2:06 ` Eric Biggers
0 siblings, 1 reply; 12+ messages in thread
From: Antonio Quartulli @ 2026-11-12 22:54 UTC (permalink / raw)
To: Jason A. Donenfeld, Ard Biesheuvel
Cc: Antonio Quartulli, linux-crypto, Eric Biggers, Herbert Xu,
Ralf Lici
On 12/11/2026 17:44, Jason A. Donenfeld wrote:
> On Thu, Nov 12, 2026 at 5:20 PM Ard Biesheuvel <ardb@kernel.org> wrote:
>>> This series adds AEGIS-128X4, the 4-way parallel variant of AEGIS-128L
>>> specified in RFC 10032 (Informational, September 2026), as a crypto
>>> library with an x86_64 VAES-AVX512 implementation, a KUnit test suite
>>> and benchmark, and a crypto API wrapper.
>>>
>>> AEGIS-128X4 runs four AEGIS-128L instances, one per 128-bit lane of a
>>> 512-bit vector, so that each state update is eight VAESENC instructions
>>> on zmm registers. On CPUs with VAES and AVX-512 it is very fast even for
>>> short messages, which is what makes it attractive for network protocols.
>>>
>>> The intended user is ovpn, the OpenVPN data channel offload driver. In
>>> single-stream ovpn tests over a 100 Gbit/s link, with both ciphers used
>>> through the crypto API, the sending CPU spent 1266 cycles per packet with
>>> AEGIS-128X4 against 1448 with AES-GCM over TCP, and 1032 against 1122
>>> with UDP GSO input. It is posted as an RFC because ovpn can't use it
>>> yet.
>>>
>>
>> We used to have implementations of aegis128l and aegis256 in the kernel as
>> well (along with morus), but we removed them on the basis that it was
>> aegis128 that won the competition.
>>
>> As far as I know, nobody actually uses any of that code, though, so I am
>> quite to surprised to see now that it was aegis128 that was dropped
>> from the portfolio, and aegis128l and aegis256 have made a miraculous
>> return, along with some other variants that appear to be tied to the
>> register width. What happens when 8-way vector registers become
>> commonplace? Create yet another aegis variant that case use them
>> efficiently?
>>
>> So I'm struggling to see why we should take this RFC seriously, to be
>> honest ...
>
> I'm also not really sure what's going on. CAESAR competition selected
> aegis128 and listed 128l and 256 as "additional finalists". And then
> RFC10032 has written up 128L, 256, 128X, 256X, but not the original
> 128 that was selected. And then none of this has been picked up by
> NIST. Meanwhile, NIST's light weight crypto competition selected
> ASCON, which as the "lightweight" as opposed to "high performance"
> pick in CAESAR. So I dunno! I've been waiting for the dust to settle
> for a little bit.
To be honest I did not try to figure out what was going on, but for sure
there was "some confusion".
By looking at RFC10032 and its references, it seems that various studies
were conducted on AEGIS128 after it won CAESAR and they highlighted some
deficiencies. I am assuming that this is why it was decided to skip it
during the standardization process and focus on its stronger siblings.
This said, our decision to work on AEGIS128X4 came from the fact that it
was expected to be faster than AES-GCM (current default for ovpn),
especially when working on chunks larger than a standard packet (i.e.
TSO/GSO/GRO case).
With RFC10032 standardizing it (and its siblings), we thought this would
be the beginning of the AEGIS era, with more use cases popping up.
RFC10032 was published last September - why wouldn't you take this
seriously at this point?
I am not the one writing ciphers and judging them during competitions,
therefore I think the 8-way question should not be for me :-)
As a side note, our tests on a prototype ovpn using AEGIS128X4 confirmed
higher throughput, therefore we were hoping to have a chance to see it
merged.
Regards,
--
Antonio Quartulli
CEO and Co-Founder
Mandelbit Srl
https://www.mandelbit.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4
2026-11-12 22:54 ` Antonio Quartulli
@ 2026-11-13 2:06 ` Eric Biggers
0 siblings, 0 replies; 12+ messages in thread
From: Eric Biggers @ 2026-11-13 2:06 UTC (permalink / raw)
To: Antonio Quartulli
Cc: Jason A. Donenfeld, Ard Biesheuvel, Antonio Quartulli,
linux-crypto, Herbert Xu, Ralf Lici
On Thu, Nov 12, 2026 at 11:54:05PM +0100, Antonio Quartulli wrote:
> > > We used to have implementations of aegis128l and aegis256 in the kernel as
> > > well (along with morus), but we removed them on the basis that it was
> > > aegis128 that won the competition.
> > >
> > > As far as I know, nobody actually uses any of that code, though, so I am
> > > quite to surprised to see now that it was aegis128 that was dropped
> > > from the portfolio, and aegis128l and aegis256 have made a miraculous
> > > return, along with some other variants that appear to be tied to the
> > > register width. What happens when 8-way vector registers become
> > > commonplace? Create yet another aegis variant that case use them
> > > efficiently?
> > >
> > > So I'm struggling to see why we should take this RFC seriously, to be
> > > honest ...
> >
> > I'm also not really sure what's going on. CAESAR competition selected
> > aegis128 and listed 128l and 256 as "additional finalists". And then
> > RFC10032 has written up 128L, 256, 128X, 256X, but not the original
> > 128 that was selected. And then none of this has been picked up by
> > NIST. Meanwhile, NIST's light weight crypto competition selected
> > ASCON, which as the "lightweight" as opposed to "high performance"
> > pick in CAESAR. So I dunno! I've been waiting for the dust to settle
> > for a little bit.
>
> To be honest I did not try to figure out what was going on, but for sure
> there was "some confusion".
>
> By looking at RFC10032 and its references, it seems that various studies
> were conducted on AEGIS128 after it won CAESAR and they highlighted some
> deficiencies. I am assuming that this is why it was decided to skip it
> during the standardization process and focus on its stronger siblings.
>
> This said, our decision to work on AEGIS128X4 came from the fact that it was
> expected to be faster than AES-GCM (current default for ovpn), especially
> when working on chunks larger than a standard packet (i.e. TSO/GSO/GRO
> case).
>
> With RFC10032 standardizing it (and its siblings), we thought this would be
> the beginning of the AEGIS era, with more use cases popping up.
>
> RFC10032 was published last September - why wouldn't you take this seriously
> at this point?
The kernel previously carried 5 AEGIS and MORUS algorithms, and that was
a mistake. We ended up having to maintain all of them, with heavily
duplicated code (and often buggy, generating syzbot reports).
Eventually we were able to drop 4 -- after 2 were even cryptographically
broken. Now it seems even the last 1 was a mistake too.
Now we have RFC 10032, which specifies an unbounded number of
incompatible AEAD algorithms in four families. It leaves the degree of
parallelism "D" unspecified, with only a few examples given in the
"Operational Considerations" section -- not the actual specification.
> I am not the one writing ciphers and judging them during competitions,
> therefore I think the 8-way question should not be for me :-)
I'm afraid it is for you, as you're the one proposing that the kernel
carry this particular variant. This proposal does not seem to consider
any of the other incompatible variants specified in RFC 10032, including
256-bit key variants which are generally preferred these days. It also
still has the flaw of a fixed degree of parallelism. Finally, it also
ignores the kernel's existing legacy AEGIS-128 code, which we still have
to support too, and doesn't attempt to share any code with it.
Fragmentation is a huge problem, especially when code cannot be shared
and each algorithm has to be optimized for each architecture.
> As a side note, our tests on a prototype ovpn using AEGIS128X4 confirmed
> higher throughput, therefore we were hoping to have a chance to see it
> merged.
Anyway, we do not support things just because they have an RFC. There
has to be a technical reason.
Being faster than AES-GCM is indeed a technical reason, so there is
still a chance this could be worth doing.
It's just not very convincing when AES-GCM is already so fast. It got
3-4x faster on x86_64 in the last few years, due to the VAES
instructions and the code I wrote to use them, and of course AES-GCM's
natural parallelism (there's no need for "AES-GCMX4").
Moreover, from the benchmark numbers you gave, it seems most of the
remaining overhead is not in the crypto algorithm itself anyway.
So perhaps it's best to focus optimizations elsewhere in the networking
stack for now, maybe work on switching from crypto_aead to lib/crypto/
to eliminate the known-problematic crypto_aead overhead too, and wait
for the dust to settle on AEGIS a bit?
If we are going to support this, we would at least need to know that it
is actually worthwhile and will be "the one", rather than the first of
many variants springing up with duplicated code.
- Eric
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 1/5] lib/crypto: tests: Make the AEAD dst clearing check effective
2026-11-12 12:54 ` [RFC PATCH 1/5] lib/crypto: tests: Make the AEAD dst clearing check effective Antonio Quartulli
@ 2026-11-13 3:07 ` Eric Biggers
0 siblings, 0 replies; 12+ messages in thread
From: Eric Biggers @ 2026-11-13 3:07 UTC (permalink / raw)
To: Antonio Quartulli
Cc: linux-crypto, Antonio Quartulli, Ard Biesheuvel,
Jason A . Donenfeld, Herbert Xu
On Thu, Nov 12, 2026 at 01:54:26PM +0100, Antonio Quartulli wrote:
> From: Antonio Quartulli <antonio@mandelbit.com>
>
> The basic validation test checks that decryption clears dst on
> authentication failure, but its plaintext is all zeroes, so a
> decryption that leaves the recovered plaintext in dst passes the check
> too. Use a random plaintext instead.
>
> Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
> ---
> lib/crypto/tests/aead-test-template.h | 5 +++++
> 1 file changed, 5 insertions(+)
Good catch, thanks! I applied this patch to libcrypto-next.
- Eric
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-11-13 3:07 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-12 12:54 [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 1/5] lib/crypto: tests: Make the AEAD dst clearing check effective Antonio Quartulli
2026-11-13 3:07 ` Eric Biggers
2026-11-12 12:54 ` [RFC PATCH 2/5] lib/crypto: aegis128x4: Add AEGIS-128X4 library Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 3/5] lib/crypto: tests: Add KUnit test suite for AEGIS-128X4 Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 4/5] lib/crypto: x86/aegis128x4: Add VAES-AVX512 optimized code Antonio Quartulli
2026-11-12 12:54 ` [RFC PATCH 5/5] crypto: aegis128x4 - Add AEGIS-128X4 AEAD algorithm Antonio Quartulli
2026-11-12 16:20 ` [RFC PATCH 0/5] lib/crypto: Add AEGIS-128X4 Ard Biesheuvel
2026-11-12 16:44 ` Jason A. Donenfeld
2026-11-12 22:54 ` Antonio Quartulli
2026-11-13 2:06 ` Eric Biggers
2026-11-12 16:50 ` Jason A. Donenfeld
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox