diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
index 229be66167e1..704af1deb7c1 100644
--- a/drivers/net/ovpn/Makefile
+++ b/drivers/net/ovpn/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_OVPN) := ovpn.o
 ovpn-y += bind.o
 ovpn-y += crypto.o
 ovpn-y += crypto_aead.o
+ovpn-y += crypto_key.o
 ovpn-y += main.o
 ovpn-y += io.o
 ovpn-y += netlink.o
diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c
index 90580e32052f..239d95492574 100644
--- a/drivers/net/ovpn/crypto.c
+++ b/drivers/net/ovpn/crypto.c
@@ -15,7 +15,6 @@
 #include "ovpnpriv.h"
 #include "main.h"
 #include "pktid.h"
-#include "crypto_aead.h"
 #include "crypto.h"
 
 static void ovpn_ks_destroy_rcu(struct rcu_head *head)
@@ -23,7 +22,7 @@ static void ovpn_ks_destroy_rcu(struct rcu_head *head)
 	struct ovpn_crypto_key_slot *ks;
 
 	ks = container_of(head, struct ovpn_crypto_key_slot, rcu);
-	ovpn_aead_crypto_key_slot_destroy(ks);
+	ovpn_crypto_key_slot_destroy(ks);
 }
 
 void ovpn_crypto_key_slot_release(struct kref *kref)
@@ -89,7 +88,7 @@ int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs,
 	    pkr->slot != OVPN_KEY_SLOT_SECONDARY)
 		return -EINVAL;
 
-	new = ovpn_aead_crypto_key_slot_new(&pkr->key);
+	new = ovpn_crypto_key_slot_new(&pkr->key);
 	if (IS_ERR(new))
 		return PTR_ERR(new);
 
@@ -202,7 +201,7 @@ int ovpn_crypto_config_get(struct ovpn_crypto_state *cs,
 		return -ENOENT;
 	}
 
-	keyconf->cipher_alg = ovpn_aead_crypto_alg(ks);
+	keyconf->cipher_alg = ovpn_crypto_key_slot_alg(ks);
 	keyconf->key_id = ks->key_id;
 	rcu_read_unlock();
 
diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h
index 2be7ff54fc40..3b21b42a25eb 100644
--- a/drivers/net/ovpn/crypto.h
+++ b/drivers/net/ovpn/crypto.h
@@ -136,6 +136,13 @@ static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks)
 int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs,
 			    const struct ovpn_peer_key_reset *pkr);
 
+struct ovpn_crypto_key_slot *
+ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc);
+
+void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks);
+
+enum ovpn_cipher_alg ovpn_crypto_key_slot_alg(struct ovpn_crypto_key_slot *ks);
+
 void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs,
 				 enum ovpn_key_slot slot);
 
diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c
index d2e3532934fb..5306c0d2b5c8 100644
--- a/drivers/net/ovpn/crypto_aead.c
+++ b/drivers/net/ovpn/crypto_aead.c
@@ -24,8 +24,6 @@
 #include "proto.h"
 #include "skb.h"
 
-#define ALG_NAME_AES		"gcm(aes)"
-#define ALG_NAME_CHACHAPOLY	"rfc7539(chacha20,poly1305)"
 #define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY	BIT_ULL(35)
 #define OVPN_AEAD_DECRYPT_FAILURE_LIMIT		BIT_ULL(36)
 #define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT	0
@@ -351,159 +349,3 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 	/* decrypt it */
 	return crypto_aead_decrypt(req);
 }
-
-/* Initialize a struct crypto_aead object */
-static struct crypto_aead *ovpn_aead_init(const char *title,
-					  const char *alg_name,
-					  const unsigned char *key,
-					  unsigned int keylen)
-{
-	struct crypto_aead *aead;
-	int ret;
-
-	aead = crypto_alloc_aead(alg_name, 0, 0);
-	if (IS_ERR(aead)) {
-		ret = PTR_ERR(aead);
-		pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret);
-		aead = NULL;
-		goto error;
-	}
-
-	ret = crypto_aead_setkey(aead, key, keylen);
-	if (ret) {
-		pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title,
-		       keylen, ret);
-		goto error;
-	}
-
-	ret = crypto_aead_setauthsize(aead, OVPN_AEAD_TAG_SIZE);
-	if (ret) {
-		pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title,
-		       ret);
-		goto error;
-	}
-
-	/* basic AEAD assumption
-	 * all current algorithms use OVPN_NONCE_SIZE.
-	 * ovpn_aead_crypto_tmp_size and ovpn_aead_encrypt/decrypt
-	 * expect this.
-	 */
-	if (crypto_aead_ivsize(aead) != OVPN_NONCE_SIZE) {
-		pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE);
-		ret = -EINVAL;
-		goto error;
-	}
-
-	pr_debug("********* Cipher %s (%s)\n", alg_name, title);
-	pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead));
-	pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead));
-	pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead));
-	pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead));
-	pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead));
-
-	return aead;
-
-error:
-	crypto_free_aead(aead);
-	return ERR_PTR(ret);
-}
-
-void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks)
-{
-	if (!ks)
-		return;
-
-	crypto_free_aead(ks->encrypt);
-	crypto_free_aead(ks->decrypt);
-	kfree(ks);
-}
-
-struct ovpn_crypto_key_slot *
-ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc)
-{
-	struct ovpn_crypto_key_slot *ks = NULL;
-	const char *alg_name;
-	int ret;
-
-	/* validate crypto alg */
-	switch (kc->cipher_alg) {
-	case OVPN_CIPHER_ALG_AES_GCM:
-		alg_name = ALG_NAME_AES;
-		break;
-	case OVPN_CIPHER_ALG_CHACHA20_POLY1305:
-		alg_name = ALG_NAME_CHACHAPOLY;
-		break;
-	default:
-		return ERR_PTR(-EOPNOTSUPP);
-	}
-
-	if (kc->encrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE ||
-	    kc->decrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE)
-		return ERR_PTR(-EINVAL);
-
-	/* build the key slot */
-	ks = kmalloc_obj(*ks);
-	if (!ks)
-		return ERR_PTR(-ENOMEM);
-
-	ks->encrypt = NULL;
-	ks->decrypt = NULL;
-	kref_init(&ks->refcount);
-	ks->key_id = kc->key_id;
-	ks->cipher_alg = kc->cipher_alg;
-	ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg);
-	ovpn_key_usage_init(&ks->usage_xmit);
-	ovpn_key_usage_init(&ks->usage_recv);
-	atomic64_set(&ks->decrypt_failures, 0);
-	ks->decrypt_failure_flags = 0;
-
-	ks->encrypt = ovpn_aead_init("encrypt", alg_name,
-				     kc->encrypt.cipher_key,
-				     kc->encrypt.cipher_key_size);
-	if (IS_ERR(ks->encrypt)) {
-		ret = PTR_ERR(ks->encrypt);
-		ks->encrypt = NULL;
-		goto destroy_ks;
-	}
-
-	ks->decrypt = ovpn_aead_init("decrypt", alg_name,
-				     kc->decrypt.cipher_key,
-				     kc->decrypt.cipher_key_size);
-	if (IS_ERR(ks->decrypt)) {
-		ret = PTR_ERR(ks->decrypt);
-		ks->decrypt = NULL;
-		goto destroy_ks;
-	}
-
-	memcpy(ks->nonce_tail_xmit, kc->encrypt.nonce_tail,
-	       OVPN_NONCE_TAIL_SIZE);
-	memcpy(ks->nonce_tail_recv, kc->decrypt.nonce_tail,
-	       OVPN_NONCE_TAIL_SIZE);
-
-	/* init packet ID generation/validation */
-	ovpn_pktid_xmit_init(&ks->pid_xmit);
-	ovpn_pktid_recv_init(&ks->pid_recv);
-
-	return ks;
-
-destroy_ks:
-	ovpn_aead_crypto_key_slot_destroy(ks);
-	return ERR_PTR(ret);
-}
-
-enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks)
-{
-	const char *alg_name;
-
-	if (!ks->encrypt)
-		return OVPN_CIPHER_ALG_NONE;
-
-	alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt));
-
-	if (!strcmp(alg_name, ALG_NAME_AES))
-		return OVPN_CIPHER_ALG_AES_GCM;
-	else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY))
-		return OVPN_CIPHER_ALG_CHACHA20_POLY1305;
-	else
-		return OVPN_CIPHER_ALG_NONE;
-}
diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h
index e5ff0b9b5ee3..57a7b88cd6c5 100644
--- a/drivers/net/ovpn/crypto_aead.h
+++ b/drivers/net/ovpn/crypto_aead.h
@@ -20,11 +20,6 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		      struct sk_buff *skb);
 
-struct ovpn_crypto_key_slot *
-ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc);
-void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks);
-
-enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks);
 bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks);
 
 #endif /* _NET_OVPN_OVPNAEAD_H_ */
diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c
new file mode 100644
index 000000000000..08fce700811f
--- /dev/null
+++ b/drivers/net/ovpn/crypto_key.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*  OpenVPN data channel offload
+ *
+ *  Copyright (C) 2026 OpenVPN, Inc.
+ *
+ *  Author:	Ralf Lici <ralf@mandelbit.com>
+ *		Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <crypto/aead.h>
+
+#include "ovpnpriv.h"
+#include "crypto.h"
+#include "pktid.h"
+#include "proto.h"
+
+#define ALG_NAME_AES		"gcm(aes)"
+#define ALG_NAME_CHACHAPOLY	"rfc7539(chacha20,poly1305)"
+
+/* initialize a struct crypto_aead object */
+static struct crypto_aead *ovpn_aead_init(const char *title,
+					  const char *alg_name,
+					  const unsigned char *key,
+					  unsigned int keylen)
+{
+	struct crypto_aead *aead;
+	int ret;
+
+	aead = crypto_alloc_aead(alg_name, 0, 0);
+	if (IS_ERR(aead)) {
+		ret = PTR_ERR(aead);
+		pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret);
+		aead = NULL;
+		goto error;
+	}
+
+	ret = crypto_aead_setkey(aead, key, keylen);
+	if (ret) {
+		pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title,
+		       keylen, ret);
+		goto error;
+	}
+
+	ret = crypto_aead_setauthsize(aead, OVPN_AEAD_TAG_SIZE);
+	if (ret) {
+		pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title,
+		       ret);
+		goto error;
+	}
+
+	/* Basic AEAD assumption: all current algorithms use OVPN_NONCE_SIZE.
+	 * ovpn_aead_crypto_tmp_size and ovpn_aead_encrypt/decrypt expect this.
+	 */
+	if (crypto_aead_ivsize(aead) != OVPN_NONCE_SIZE) {
+		pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	pr_debug("********* Cipher %s (%s)\n", alg_name, title);
+	pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead));
+	pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead));
+	pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead));
+	pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead));
+	pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead));
+
+	return aead;
+
+error:
+	crypto_free_aead(aead);
+	return ERR_PTR(ret);
+}
+
+void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks)
+{
+	if (!ks)
+		return;
+
+	crypto_free_aead(ks->encrypt);
+	crypto_free_aead(ks->decrypt);
+	kfree(ks);
+}
+
+struct ovpn_crypto_key_slot *
+ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc)
+{
+	struct ovpn_crypto_key_slot *ks = NULL;
+	const char *alg_name;
+	int ret;
+
+	/* validate crypto alg */
+	switch (kc->cipher_alg) {
+	case OVPN_CIPHER_ALG_AES_GCM:
+		alg_name = ALG_NAME_AES;
+		break;
+	case OVPN_CIPHER_ALG_CHACHA20_POLY1305:
+		alg_name = ALG_NAME_CHACHAPOLY;
+		break;
+	default:
+		return ERR_PTR(-EOPNOTSUPP);
+	}
+
+	if (kc->encrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE ||
+	    kc->decrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE)
+		return ERR_PTR(-EINVAL);
+
+	/* build the key slot */
+	ks = kmalloc_obj(*ks);
+	if (!ks)
+		return ERR_PTR(-ENOMEM);
+
+	ks->encrypt = NULL;
+	ks->decrypt = NULL;
+	kref_init(&ks->refcount);
+	ks->key_id = kc->key_id;
+	ks->cipher_alg = kc->cipher_alg;
+	ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg);
+	ovpn_key_usage_init(&ks->usage_xmit);
+	ovpn_key_usage_init(&ks->usage_recv);
+	atomic64_set(&ks->decrypt_failures, 0);
+	ks->decrypt_failure_flags = 0;
+
+	ks->encrypt = ovpn_aead_init("encrypt", alg_name,
+				     kc->encrypt.cipher_key,
+				     kc->encrypt.cipher_key_size);
+	if (IS_ERR(ks->encrypt)) {
+		ret = PTR_ERR(ks->encrypt);
+		ks->encrypt = NULL;
+		goto destroy_ks;
+	}
+
+	ks->decrypt = ovpn_aead_init("decrypt", alg_name,
+				     kc->decrypt.cipher_key,
+				     kc->decrypt.cipher_key_size);
+	if (IS_ERR(ks->decrypt)) {
+		ret = PTR_ERR(ks->decrypt);
+		ks->decrypt = NULL;
+		goto destroy_ks;
+	}
+
+	memcpy(ks->nonce_tail_xmit, kc->encrypt.nonce_tail,
+	       OVPN_NONCE_TAIL_SIZE);
+	memcpy(ks->nonce_tail_recv, kc->decrypt.nonce_tail,
+	       OVPN_NONCE_TAIL_SIZE);
+
+	/* init packet ID generation/validation */
+	ovpn_pktid_xmit_init(&ks->pid_xmit);
+	ovpn_pktid_recv_init(&ks->pid_recv);
+
+	return ks;
+
+destroy_ks:
+	ovpn_crypto_key_slot_destroy(ks);
+	return ERR_PTR(ret);
+}
+
+enum ovpn_cipher_alg ovpn_crypto_key_slot_alg(struct ovpn_crypto_key_slot *ks)
+{
+	const char *alg_name;
+
+	if (!ks->encrypt)
+		return OVPN_CIPHER_ALG_NONE;
+
+	alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt));
+
+	if (!strcmp(alg_name, ALG_NAME_AES))
+		return OVPN_CIPHER_ALG_AES_GCM;
+	else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY))
+		return OVPN_CIPHER_ALG_CHACHA20_POLY1305;
+	else
+		return OVPN_CIPHER_ALG_NONE;
+}
