You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by tw...@apache.org on 2025/09/06 19:58:48 UTC

(mina-sshd) branch dev_3.0 updated (bee4c73b1 -> fdac96c91)

This is an automated email from the ASF dual-hosted git repository.

twolf pushed a change to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


    from bee4c73b1 [maven-release-plugin] prepare for next development iteration
     new 01ad91edf Limit maximum padding for small channel data messages
     new 2299ba8fd Remove generics from key decoders
     new 1b2021fe8 Simplify ed25519 handling
     new 834a078ad More ed25519 simplifications
     new fdac96c91 Update CHANGES.md

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGES.md                                         |  33 +--
 CHANGES.md => docs/changes/3.0.0-M1.md             |  14 +-
 .../org/apache/sshd/cli/client/SshKeyScanMain.java |   2 +-
 .../sshd/cli/server/SshServerCliSupport.java       |   2 +-
 .../sshd/common/config/keys/BuiltinIdentities.java | 100 +------
 .../apache/sshd/common/config/keys/Identity.java   |   6 -
 .../common/config/keys/IdentityResourceLoader.java |  45 ---
 .../sshd/common/config/keys/IdentityUtils.java     |   5 +-
 .../sshd/common/config/keys/KeyEntryResolver.java  |  18 +-
 .../apache/sshd/common/config/keys/KeyUtils.java   |  40 +--
 .../common/config/keys/PrivateKeyEntryDecoder.java |  15 +-
 .../sshd/common/config/keys/PublicKeyEntry.java    |   4 +-
 .../common/config/keys/PublicKeyEntryDecoder.java  |  12 +-
 .../config/keys/PublicKeyRawDataDecoder.java       |  10 +-
 .../keys/impl/AbstractIdentityResourceLoader.java  |  25 +-
 .../config/keys/impl/AbstractKeyEntryResolver.java |  20 +-
 .../keys/impl/AbstractPrivateKeyEntryDecoder.java  |  14 +-
 .../keys/impl/AbstractPublicKeyEntryDecoder.java   |   8 +-
 .../config/keys/impl/DSSPublicKeyEntryDecoder.java |  14 +-
 .../keys/impl/ECDSAPublicKeyEntryDecoder.java      |  18 +-
 .../keys/impl/OpenSSHCertificateDecoder.java       |  28 +-
 .../config/keys/impl/RSAPublicKeyDecoder.java      |  23 +-
 .../keys/impl/SkECDSAPublicKeyEntryDecoder.java    |  15 +-
 .../keys/impl/SkED25519PublicKeyEntryDecoder.java  |  25 +-
 .../openssh/OpenSSHDSSPrivateKeyEntryDecoder.java  |  21 +-
 .../OpenSSHECDSAPrivateKeyEntryDecoder.java        |  18 +-
 .../openssh/OpenSSHKeyPairResourceParser.java      |  18 +-
 .../openssh/OpenSSHRSAPrivateKeyDecoder.java       |  18 +-
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  |   7 +-
 .../loader/ssh2/Ssh2PublicKeyEntryDecoder.java     |   4 +-
 .../openssh/OpenSSHKeyPairResourceWriter.java      |  10 +-
 .../sshd/common/signature/BuiltinSignatures.java   |   5 +-
 .../sshd/common/signature/SignatureFactory.java    |   3 +-
 .../sshd/common/signature/SignatureSkED25519.java  |   4 +-
 .../org/apache/sshd/common/util/buffer/Buffer.java |  22 +-
 .../buffer/keys/ED25519BufferPublicKeyParser.java  |   4 +-
 .../security/PublicKeyFactory.java}                |  16 +-
 .../util/security/SecurityEntityFactory.java       |  10 +-
 .../util/security/SecurityProviderChoice.java      |   7 +
 .../util/security/SecurityProviderRegistrar.java   |  14 +-
 .../sshd/common/util/security/SecurityUtils.java   | 183 +++---------
 ...tory.java => BouncyCastlePublicKeyFactory.java} |  37 ++-
 .../BouncyCastleSecurityProviderRegistrar.java     |  23 +-
 .../eddsa/Ed25519PEMResourceKeyParser.java         |  63 -----
 .../security/eddsa/Ed25519PublicKeyDecoder.java    |  43 ---
 .../eddsa/EdDSASecurityProviderRegistrar.java      |  82 +++++-
 .../security/eddsa/EdDSASecurityProviderUtils.java |  54 +---
 .../security/eddsa/NetI2pCryptoEdDSASupport.java   | 135 ---------
 .../OpenSSHEd25519PrivateKeyEntryDecoder.java      |  40 ---
 .../util/security/eddsa/SignatureEd25519.java      |  33 ---
 .../bouncycastle/BouncyCastleEdDSASupport.java     | 169 -----------
 ...arser.java => Ed25519PEMResourceKeyParser.java} |  36 ++-
 ...eyDecoder.java => Ed25519PublicKeyDecoder.java} |  29 +-
 .../util/security/eddsa/generic/EdDSASupport.java  | 198 -------------
 .../util/security/eddsa/generic/EdDSAUtils.java    | 308 ++++++++++++++++++++-
 ...a => OpenSSHEd25519PrivateKeyEntryDecoder.java} |  55 ++--
 ...SignatureEd25519.java => SignatureEd25519.java} |  15 +-
 .../common/config/keys/BuiltinIdentitiesTest.java  |   5 +
 .../keys/EcdsaPublicKeyEntryDecoderTest.java       |   4 +-
 .../sshd/common/config/keys/KeyRandomArtTest.java  |   9 +-
 .../OpenSSHKeyPairResourceParserDecodingTest.java  |   6 -
 .../pem/PKCS8PEMResourceKeyPairParserTest.java     |   4 +-
 .../BouncyCastleGeneratorHostKeyProviderTest.java  |   4 +-
 .../util/security/eddsa/EDDSAProviderTest.java     |  13 +-
 .../util/security/eddsa/Ed25519VectorsTest.java    | 304 +++++++++-----------
 .../eddsa/EdDSASecurityProviderRegistrarTest.java  |   4 +-
 .../SimpleGeneratorHostKeyProviderTest.java        |   2 +-
 .../pem/{pkcs8-eddsa.enc => pkcs8-ed25519.enc}     |   0
 .../pem/{pkcs8-eddsa.enc2 => pkcs8-ed25519.enc2}   |   0
 .../pem/{pkcs8-eddsa.pem => pkcs8-ed25519.pem}     |   0
 .../sshd/common/session/filters/CryptFilter.java   |  16 +-
 .../common/signature/SignatureFactoriesTest.java   |  13 +-
 .../apache/sshd/putty/AbstractPuttyKeyDecoder.java |  10 +-
 .../org/apache/sshd/putty/DSSPuttyKeyDecoder.java  |   6 +-
 .../apache/sshd/putty/ECDSAPuttyKeyDecoder.java    |   6 +-
 .../apache/sshd/putty/EdDSAPuttyKeyDecoder.java    |  11 +-
 .../sshd/putty/PuttyKeyPairResourceParser.java     |   7 +-
 .../java/org/apache/sshd/putty/PuttyKeyUtils.java  |  10 +-
 .../org/apache/sshd/putty/RSAPuttyKeyDecoder.java  |   6 +-
 .../sshd/putty/AbstractPuttyTestSupport.java       |   4 +-
 .../org/apache/sshd/putty/PuttyKeyUtilsTest.java   |   4 +-
 81 files changed, 1002 insertions(+), 1626 deletions(-)
 copy CHANGES.md => docs/changes/3.0.0-M1.md (86%)
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java
 copy sshd-common/src/main/java/org/apache/sshd/common/{config/keys/SshPublicKey.java => util/security/PublicKeyFactory.java} (65%)
 copy sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/{BouncyCastleRandomFactory.java => BouncyCastlePublicKeyFactory.java} (54%)
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
 rename sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/{GenericEd25519PEMResourceKeyParser.java => Ed25519PEMResourceKeyParser.java} (79%)
 rename sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/{GenericEd25519PublicKeyDecoder.java => Ed25519PublicKeyDecoder.java} (71%)
 delete mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
 rename sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/{GenericOpenSSHEd25519PrivateKeyEntryDecoder.java => OpenSSHEd25519PrivateKeyEntryDecoder.java} (78%)
 rename sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/{GenericSignatureEd25519.java => SignatureEd25519.java} (75%)
 rename sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/{pkcs8-eddsa.enc => pkcs8-ed25519.enc} (100%)
 rename sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/{pkcs8-eddsa.enc2 => pkcs8-ed25519.enc2} (100%)
 rename sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/{pkcs8-eddsa.pem => pkcs8-ed25519.pem} (100%)


(mina-sshd) 02/05: Remove generics from key decoders

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 2299ba8fd1c8de98ed18d77a715a3de73e46d523
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Sep 4 22:09:25 2025 +0200

    Remove generics from key decoders
    
    These classes attempted to statically enforce matching private and
    public key classes via generics, but this never worked correctly. In
    particular it fails for method parameters, and as result we had to
    use unchecked conversions or raw types, and had to suppress warnings
    because of that.
    
    Moreover, this forces key classes (or interfaces) into the generic
    parameters of subclasses, which gives trouble with key types that don't
    exist on all Java versions supported (like ed25519).
    
    So remove these generics and just use PrivateKey and PublicKey. We can
    do so since we also removed any operations to get a decoder based on a
    concrete key class -- this is bad practice anyway, and using either the
    key algorithm or the SSH key type to figure out what kind of key a
    particular instance is.
---
 .../sshd/common/config/keys/BuiltinIdentities.java | 100 ++--------
 .../apache/sshd/common/config/keys/Identity.java   |   6 -
 .../common/config/keys/IdentityResourceLoader.java |  45 -----
 .../sshd/common/config/keys/IdentityUtils.java     |   5 +-
 .../sshd/common/config/keys/KeyEntryResolver.java  |  18 +-
 .../apache/sshd/common/config/keys/KeyUtils.java   |  26 +--
 .../common/config/keys/PrivateKeyEntryDecoder.java |  15 +-
 .../sshd/common/config/keys/PublicKeyEntry.java    |   4 +-
 .../common/config/keys/PublicKeyEntryDecoder.java  |  12 +-
 .../config/keys/PublicKeyRawDataDecoder.java       |  10 +-
 .../keys/impl/AbstractIdentityResourceLoader.java  |  25 +--
 .../config/keys/impl/AbstractKeyEntryResolver.java |  20 +-
 .../keys/impl/AbstractPrivateKeyEntryDecoder.java  |  14 +-
 .../keys/impl/AbstractPublicKeyEntryDecoder.java   |   8 +-
 .../config/keys/impl/DSSPublicKeyEntryDecoder.java |  14 +-
 .../keys/impl/ECDSAPublicKeyEntryDecoder.java      |  18 +-
 .../keys/impl/OpenSSHCertificateDecoder.java       |  28 +--
 .../config/keys/impl/RSAPublicKeyDecoder.java      |  23 ++-
 .../keys/impl/SkECDSAPublicKeyEntryDecoder.java    |  15 +-
 .../keys/impl/SkED25519PublicKeyEntryDecoder.java  |  12 +-
 .../openssh/OpenSSHDSSPrivateKeyEntryDecoder.java  |  21 ++-
 .../OpenSSHECDSAPrivateKeyEntryDecoder.java        |  18 +-
 .../openssh/OpenSSHKeyPairResourceParser.java      |  18 +-
 .../openssh/OpenSSHRSAPrivateKeyDecoder.java       |  18 +-
 .../loader/ssh2/Ssh2PublicKeyEntryDecoder.java     |   4 +-
 .../openssh/OpenSSHKeyPairResourceWriter.java      |  10 +-
 .../util/security/SecurityProviderRegistrar.java   |   2 +-
 .../sshd/common/util/security/SecurityUtils.java   |  48 ++---
 .../BouncyCastleSecurityProviderRegistrar.java     |   2 +-
 .../security/eddsa/Ed25519PublicKeyDecoder.java    |   5 +-
 .../eddsa/EdDSASecurityProviderRegistrar.java      |   2 +-
 .../security/eddsa/EdDSASecurityProviderUtils.java |   4 +-
 .../security/eddsa/NetI2pCryptoEdDSASupport.java   |  40 +---
 .../OpenSSHEd25519PrivateKeyEntryDecoder.java      |   6 +-
 .../bouncycastle/BouncyCastleEdDSASupport.java     |  44 +----
 .../util/security/eddsa/generic/EdDSASupport.java  |  56 +++---
 .../util/security/eddsa/generic/EdDSAUtils.java    | 201 +++++++++++++++++++++
 .../generic/GenericEd25519PublicKeyDecoder.java    |  14 +-
 ...enericOpenSSHEd25519PrivateKeyEntryDecoder.java |  18 +-
 .../common/config/keys/BuiltinIdentitiesTest.java  |   5 +
 .../keys/EcdsaPublicKeyEntryDecoderTest.java       |   4 +-
 .../OpenSSHKeyPairResourceParserDecodingTest.java  |   6 -
 .../util/security/eddsa/Ed25519VectorsTest.java    |  35 ++--
 .../common/signature/SignatureFactoriesTest.java   |  10 +-
 .../apache/sshd/putty/AbstractPuttyKeyDecoder.java |  10 +-
 .../org/apache/sshd/putty/DSSPuttyKeyDecoder.java  |   6 +-
 .../apache/sshd/putty/ECDSAPuttyKeyDecoder.java    |   6 +-
 .../apache/sshd/putty/EdDSAPuttyKeyDecoder.java    |   6 +-
 .../sshd/putty/PuttyKeyPairResourceParser.java     |   7 +-
 .../java/org/apache/sshd/putty/PuttyKeyUtils.java  |  10 +-
 .../org/apache/sshd/putty/RSAPuttyKeyDecoder.java  |   6 +-
 .../sshd/putty/AbstractPuttyTestSupport.java       |   4 +-
 .../org/apache/sshd/putty/PuttyKeyUtilsTest.java   |   4 +-
 53 files changed, 499 insertions(+), 569 deletions(-)

diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
index 7d1bdfd01..8c9afcf45 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
@@ -21,19 +21,10 @@ package org.apache.sshd.common.config.keys;
 
 import java.security.Key;
 import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.NavigableSet;
-import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -49,14 +40,11 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public enum BuiltinIdentities implements Identity {
-    RSA(Constants.RSA, RSAPublicKey.class, RSAPrivateKey.class, KeyPairProvider.SSH_RSA),
-    DSA(Constants.DSA, DSAPublicKey.class, DSAPrivateKey.class, KeyPairProvider.SSH_DSS),
-    ECDSA(Constants.ECDSA, KeyUtils.EC_ALGORITHM, ECPublicKey.class, ECPrivateKey.class,
+    RSA(Constants.RSA, KeyPairProvider.SSH_RSA),
+    DSA(Constants.DSA, KeyPairProvider.SSH_DSS),
+    ECDSA(Constants.ECDSA, KeyUtils.EC_ALGORITHM,
           ECCurves.VALUES.stream().map(KeyTypeIndicator::getKeyType).collect(Collectors.toList())),
-    ED25119(Constants.ED25519, SecurityUtils.EDDSA,
-            SecurityUtils.getEDDSAPublicKeyType(),
-            SecurityUtils.getEDDSAPrivateKeyType(),
-            KeyPairProvider.SSH_ED25519) {
+    ED25119(Constants.ED25519, SecurityUtils.EDDSA, KeyPairProvider.SSH_ED25519) {
         @Override
         public boolean isSupported() {
             return SecurityUtils.isEDDSACurveSupported();
@@ -74,31 +62,22 @@ public enum BuiltinIdentities implements Identity {
 
     private final String name;
     private final String algorithm;
-    private final Class<? extends PublicKey> pubType;
-    private final Class<? extends PrivateKey> prvType;
     private final NavigableSet<String> types;
 
-    BuiltinIdentities(String type, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType, String keyType) {
-        this(type, type, pubType, prvType, keyType);
+    BuiltinIdentities(String type, String keyType) {
+        this(type, type, keyType);
     }
 
-    BuiltinIdentities(String name, String algorithm,
-                      Class<? extends PublicKey> pubType,
-                      Class<? extends PrivateKey> prvType,
-                      String keyType) {
-        this(name, algorithm, pubType, prvType,
+    BuiltinIdentities(String name, String algorithm, String keyType) {
+        this(name, algorithm,
              Collections.singletonList(
                      ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified")));
     }
 
     BuiltinIdentities(String name, String algorithm,
-                      Class<? extends PublicKey> pubType,
-                      Class<? extends PrivateKey> prvType,
                       Collection<String> keyTypes) {
         this.name = name.toLowerCase();
         this.algorithm = algorithm.toUpperCase();
-        this.pubType = pubType;
-        this.prvType = prvType;
         this.types = Collections.unmodifiableNavigableSet(
                 GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
                         ValidateUtils.checkNotNullAndNotEmpty(keyTypes, "No key type names provided")));
@@ -124,16 +103,6 @@ public enum BuiltinIdentities implements Identity {
         return algorithm;
     }
 
-    @Override
-    public final Class<? extends PublicKey> getPublicKeyType() {
-        return pubType;
-    }
-
-    @Override
-    public final Class<? extends PrivateKey> getPrivateKeyType() {
-        return prvType;
-    }
-
     /**
      * @param  name The identity name - ignored if {@code null}/empty
      * @return      The matching {@link BuiltinIdentities} whose {@link #getName()} value matches case
@@ -164,7 +133,7 @@ public enum BuiltinIdentities implements Identity {
 
     /**
      * @param  kp The {@link KeyPair} - ignored if {@code null}
-     * @return    The matching {@link BuiltinIdentities} provided <U>both</U> public and public keys are of the same
+     * @return    The matching {@link BuiltinIdentities} provided <U>both</U> public and private keys are of the same
      *            type - {@code null} if no match could be found
      * @see       #fromKey(Key)
      */
@@ -172,14 +141,7 @@ public enum BuiltinIdentities implements Identity {
         if (kp == null) {
             return null;
         }
-
-        BuiltinIdentities i1 = fromKey(kp.getPublic());
-        BuiltinIdentities i2 = fromKey(kp.getPrivate());
-        if (Objects.equals(i1, i2)) {
-            return i1;
-        } else {
-            return null; // some kind of mixed keys...
-        }
+        return fromKeyType(KeyUtils.getKeyType(kp));
     }
 
     /**
@@ -189,48 +151,14 @@ public enum BuiltinIdentities implements Identity {
      * @see        #fromKeyType(Class)
      */
     public static BuiltinIdentities fromKey(Key key) {
-        return fromKeyType((key == null) ? null : key.getClass());
-    }
-
-    /**
-     * @param  clazz The key type - ignored if {@code null} or not a {@link Key} class
-     * @return       The matching {@link BuiltinIdentities} whose either public or private key type matches the
-     *               requested one or {@code null} if no match found
-     * @see          #getPublicKeyType()
-     * @see          #getPrivateKeyType()
-     */
-    public static BuiltinIdentities fromKeyType(Class<?> clazz) {
-        if ((clazz == null) || (!Key.class.isAssignableFrom(clazz))) {
+        if (key == null) {
             return null;
         }
-
-        for (BuiltinIdentities id : VALUES) {
-            Class<?> pubType = id.getPublicKeyType();
-            Class<?> prvType = id.getPrivateKeyType();
-            // Ignore placeholder classes (e.g., if ed25519 is not supported)
-            if ((prvType == null) || (pubType == null)) {
-                continue;
-            }
-            if ((prvType == PrivateKey.class) || (pubType == PublicKey.class)) {
-                continue;
-            }
-            if (pubType.isAssignableFrom(clazz) || prvType.isAssignableFrom(clazz)) {
-                return id;
-            }
-        }
-
-        return null;
+        return fromKeyType(KeyUtils.getKeyType(key));
     }
 
-    /**
-     * @param  typeName The {@code OpenSSH} key type e.g., {@code ssh-rsa, ssh-dss, ecdsa-sha2-nistp384}. Ignored if
-     *                  {@code null}/empty.
-     * @return          The {@link BuiltinIdentities} that reported the type name as its {@link #getSupportedKeyTypes()}
-     *                  (case <U>insensitive</U>) - {@code null} if no match found
-     * @see             KeyTypeNamesSupport#findSupporterByKeyTypeName(String, Collection)
-     */
-    public static BuiltinIdentities fromKeyTypeName(String typeName) {
-        return KeyTypeNamesSupport.findSupporterByKeyTypeName(typeName, VALUES);
+    private static BuiltinIdentities fromKeyType(String type) {
+        return VALUES.stream().filter(b -> b.getSupportedKeyTypes().contains(type)).findFirst().orElse(null);
     }
 
     /**
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java
index cc805101d..48377e87f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java
@@ -19,9 +19,6 @@
 
 package org.apache.sshd.common.config.keys;
 
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
 import org.apache.sshd.common.AlgorithmNameProvider;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.OptionalFeature;
@@ -37,7 +34,4 @@ public interface Identity
         NamedResource,
         OptionalFeature,
         KeyTypeNamesSupport {
-    Class<? extends PublicKey> getPublicKeyType();
-
-    Class<? extends PrivateKey> getPrivateKeyType();
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java
deleted file mode 100644
index 026c69be7..000000000
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.common.config.keys;
-
-import java.math.BigInteger;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-/**
- * @param  <PUB> Type of {@link PublicKey}
- * @param  <PRV> Type of {@link PrivateKey}
- * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public interface IdentityResourceLoader<PUB extends PublicKey, PRV extends PrivateKey> extends KeyTypeNamesSupport {
-    /**
-     * A reasonable max. number of octets used for a {@link BigInteger} in the context of keys based on such numbers
-     */
-    int MAX_BIGINT_OCTETS_COUNT = Short.MAX_VALUE;
-
-    /**
-     * @return The {@link Class} of the {@link PublicKey} that is the result of decoding
-     */
-    Class<PUB> getPublicKeyType();
-
-    /**
-     * @return The {@link Class} of the {@link PrivateKey} that matches the public one
-     */
-    Class<PRV> getPrivateKeyType();
-}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
index f727165c4..5948e9da6 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
@@ -79,9 +79,6 @@ public final class IdentityUtils {
         Map<String, KeyPair> pairsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         ids.forEach((type, kp) -> {
             BuiltinIdentities id = BuiltinIdentities.fromName(type);
-            if (id == null) {
-                id = BuiltinIdentities.fromKeyPair(kp);
-            }
 
             if (supportedOnly && ((id == null) || (!id.isSupported()))) {
                 return;
@@ -144,7 +141,7 @@ public final class IdentityUtils {
 
             for (KeyPair kp : pairs) {
                 KeyPair prev = ids.put(type, kp);
-                ValidateUtils.checkTrue(prev == null, "Multiple keys for type=%s due to %s", type, path);
+                ValidateUtils.checkTrue(prev == null, "Multiple keys for type=%s in %s", type, path);
             }
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java
index 87bb58431..fb1dc74f4 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java
@@ -30,8 +30,6 @@ import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.Map;
 
@@ -39,12 +37,16 @@ import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 
 /**
- * @param  <PUB> Type of {@link PublicKey}
- * @param  <PRV> Type of {@link PrivateKey}
- * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface KeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey>
-        extends IdentityResourceLoader<PUB, PRV> {
+public interface KeyEntryResolver extends KeyTypeNamesSupport {
+
+    /**
+     * A reasonable max. number of octets used for a {@link java.math.BigInteger} in the context of keys based on such
+     * numbers
+     */
+    int MAX_BIGINT_OCTETS_COUNT = Short.MAX_VALUE;
+
     /**
      * @param  keySize                  Key size in bits
      * @return                          A {@link KeyPair} with the specified key size
@@ -119,7 +121,7 @@ public interface KeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey>
     }
 
     static BigInteger decodeBigInt(InputStream s) throws IOException {
-        return new BigInteger(readRLEBytes(s, IdentityResourceLoader.MAX_BIGINT_OCTETS_COUNT));
+        return new BigInteger(readRLEBytes(s, MAX_BIGINT_OCTETS_COUNT));
     }
 
     static byte[] readRLEBytes(InputStream s, int maxAllowed) throws IOException {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index 2babf02b7..4ff385c73 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -140,7 +140,7 @@ public final class KeyUtils {
 
     private static final AtomicReference<DigestFactory> DEFAULT_DIGEST_HOLDER = new AtomicReference<>();
 
-    private static final Map<String, PublicKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP
+    private static final Map<String, PublicKeyEntryDecoder> BY_KEY_TYPE_DECODERS_MAP
             = new ConcurrentHashMap<>();
 
     private static final Map<String, String> KEY_TYPE_ALIASES
@@ -342,7 +342,7 @@ public final class KeyUtils {
      * @see                             PublicKeyEntryDecoder<?, ?>#generateKeyPair(int)
      */
     public static KeyPair generateKeyPair(String keyType, int keySize) throws GeneralSecurityException {
-        PublicKeyEntryDecoder<?, ?> decoder = getPublicKeyEntryDecoder(keyType);
+        PublicKeyEntryDecoder decoder = getPublicKeyEntryDecoder(keyType);
         if (decoder == null) {
             throw new InvalidKeySpecException("No decoder for key type=" + keyType);
         }
@@ -354,9 +354,9 @@ public final class KeyUtils {
      * Registers the specified decoder for all the types it {@link PublicKeyEntryDecoder<?, ?>#getSupportedKeyTypes()
      * supports}
      *
-     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder<?, ?> decoder} to register
+     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register
      */
-    public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
+    public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder decoder) {
         Objects.requireNonNull(decoder, "No decoder specified");
 
         Collection<String> names
@@ -374,14 +374,14 @@ public final class KeyUtils {
      *                 {@link PublicKeyEntryDecoder<?, ?>#getSupportedKeyTypes() supported} ones.
      * @see            #unregisterPublicKeyEntryDecoderForKeyType(String)
      */
-    public static Set<String> unregisterPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
+    public static Set<String> unregisterPublicKeyEntryDecoder(PublicKeyEntryDecoder decoder) {
         Objects.requireNonNull(decoder, "No decoder specified");
 
         Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(
                 decoder.getSupportedKeyTypes(), "No supported key types");
         Set<String> removed = new HashSet<>();
         for (String n : names) {
-            PublicKeyEntryDecoder<?, ?> prev = unregisterPublicKeyEntryDecoderForKeyType(n);
+            PublicKeyEntryDecoder prev = unregisterPublicKeyEntryDecoderForKeyType(n);
             if (prev != null) {
                 removed.add(n);
             }
@@ -396,7 +396,7 @@ public final class KeyUtils {
      * @return         The unregistered {@link PublicKeyEntryDecoder<?, ?>} - {@code null} if none registered for this
      *                 key type
      */
-    public static PublicKeyEntryDecoder<?, ?> unregisterPublicKeyEntryDecoderForKeyType(String keyType) {
+    public static PublicKeyEntryDecoder unregisterPublicKeyEntryDecoderForKeyType(String keyType) {
         keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
         return BY_KEY_TYPE_DECODERS_MAP.remove(keyType);
     }
@@ -406,7 +406,7 @@ public final class KeyUtils {
      *                 {@code null}/empty
      * @return         The registered {@link PublicKeyEntryDecoder<?, ?>} or {code null} if not found
      */
-    public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(String keyType) {
+    public static PublicKeyEntryDecoder getPublicKeyEntryDecoder(String keyType) {
         if (GenericUtils.isEmpty(keyType)) {
             return null;
         }
@@ -419,13 +419,13 @@ public final class KeyUtils {
      *            the same decoder - {@code null} if no match found
      * @see       #getPublicKeyEntryDecoder(Key)
      */
-    public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(KeyPair kp) {
+    public static PublicKeyEntryDecoder getPublicKeyEntryDecoder(KeyPair kp) {
         if (kp == null) {
             return null;
         }
 
-        PublicKeyEntryDecoder<?, ?> d1 = getPublicKeyEntryDecoder(kp.getPublic());
-        PublicKeyEntryDecoder<?, ?> d2 = getPublicKeyEntryDecoder(kp.getPrivate());
+        PublicKeyEntryDecoder d1 = getPublicKeyEntryDecoder(kp.getPublic());
+        PublicKeyEntryDecoder d2 = getPublicKeyEntryDecoder(kp.getPrivate());
         if (d1 == d2) {
             return d1;
         } else {
@@ -435,9 +435,9 @@ public final class KeyUtils {
 
     /**
      * @param  key The {@link Key} (public or private) - ignored if {@code null}
-     * @return     The registered {@link PublicKeyEntryDecoder<?, ?>} for this key or {code null} if no match found
+     * @return     The registered {@link PublicKeyEntryDecoder} for this key or {code null} if no match found
      */
-    public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(Key key) {
+    public static PublicKeyEntryDecoder getPublicKeyEntryDecoder(Key key) {
         if (key == null) {
             return null;
         }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
index c66272099..3d5571323 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
@@ -42,8 +42,7 @@ import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
  * @param  <PRV> Type of {@link PrivateKey}
  * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
-        extends KeyEntryResolver<PUB, PRV>, PrivateKeyEntryResolver {
+public interface PrivateKeyEntryDecoder extends KeyEntryResolver, PrivateKeyEntryResolver {
 
     @Override
     default PrivateKey resolve(
@@ -69,13 +68,13 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
      * @throws IOException              If failed to decode the key
      * @throws GeneralSecurityException If failed to generate the key
      */
-    default PRV decodePrivateKey(
+    default PrivateKey decodePrivateKey(
             SessionContext session, FilePasswordProvider passwordProvider, byte... keyData)
             throws IOException, GeneralSecurityException {
         return decodePrivateKey(session, passwordProvider, keyData, 0, NumberUtils.length(keyData));
     }
 
-    default PRV decodePrivateKey(
+    default PrivateKey decodePrivateKey(
             SessionContext session, FilePasswordProvider passwordProvider, byte[] keyData, int offset, int length)
             throws IOException, GeneralSecurityException {
         if (length <= 0) {
@@ -87,7 +86,7 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
         }
     }
 
-    default PRV decodePrivateKey(
+    default PrivateKey decodePrivateKey(
             SessionContext session, FilePasswordProvider passwordProvider, InputStream keyData)
             throws IOException, GeneralSecurityException {
         // the actual data is preceded by a string that repeats the key type
@@ -116,7 +115,7 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
      * @throws IOException              If failed to read from the data stream
      * @throws GeneralSecurityException If failed to generate the key
      */
-    PRV decodePrivateKey(
+    PrivateKey decodePrivateKey(
             SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
             throws IOException, GeneralSecurityException;
 
@@ -132,7 +131,7 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
      *                     not supported
      * @throws IOException If failed to generate the encoding
      */
-    default String encodePrivateKey(SecureByteArrayOutputStream s, PRV key, PUB pubKey) throws IOException {
+    default String encodePrivateKey(SecureByteArrayOutputStream s, PrivateKey key, PublicKey pubKey) throws IOException {
         Objects.requireNonNull(key, "No private key provided");
         return null;
     }
@@ -148,7 +147,7 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
      * @return                          The recovered {@link PublicKey} - {@code null} if cannot recover it
      * @throws GeneralSecurityException If failed to generate the public key
      */
-    default PUB recoverPublicKey(PRV prvKey) throws GeneralSecurityException {
+    default PublicKey recoverPublicKey(PrivateKey prvKey) throws GeneralSecurityException {
         return null;
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
index f8345c07b..6acb57fa7 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
@@ -507,9 +507,7 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator {
             keyType = unsupported.getKeyType();
             bytes = unsupported.getKeyData();
         } else {
-            @SuppressWarnings("unchecked")
-            PublicKeyEntryDecoder<PublicKey, ?> decoder = (PublicKeyEntryDecoder<PublicKey, ?>) KeyUtils
-                    .getPublicKeyEntryDecoder(key);
+            PublicKeyEntryDecoder decoder = KeyUtils.getPublicKeyEntryDecoder(key);
             if (decoder == null) {
                 throw new StreamCorruptedException("Cannot retrieve decoder for key=" + key.getAlgorithm());
             }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
index ca1fdfc14..c77162b8f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
@@ -24,7 +24,6 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
-import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Collection;
@@ -38,12 +37,9 @@ import org.apache.sshd.common.util.ValidateUtils;
 /**
  * Represents a decoder of an {@code OpenSSH} encoded key data
  *
- * @param  <PUB> Type of {@link PublicKey}
- * @param  <PRV> Type of {@link PrivateKey}
- * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
-        extends KeyEntryResolver<PUB, PRV>, PublicKeyRawDataDecoder<PUB>, PublicKeyEntryResolver {
+public interface PublicKeyEntryDecoder extends KeyEntryResolver, PublicKeyRawDataDecoder, PublicKeyEntryResolver {
 
     @Override
     default PublicKey resolve(
@@ -59,7 +55,7 @@ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends Privat
     }
 
     @Override
-    default PUB decodePublicKeyByType(
+    default PublicKey decodePublicKeyByType(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         // the actual data is preceded by a string that repeats the key type
@@ -85,5 +81,5 @@ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends Privat
      * @return             The key type value - one of the {@link #getSupportedKeyTypes()}
      * @throws IOException If failed to generate the encoding
      */
-    String encodePublicKey(OutputStream s, PUB key) throws IOException;
+    String encodePublicKey(OutputStream s, PublicKey key) throws IOException;
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java
index 6f154096b..345b086b4 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java
@@ -33,7 +33,7 @@ import org.apache.sshd.common.util.NumberUtils;
  * @param  <PUB> Generic {@link PublicKey} type
  * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface PublicKeyRawDataDecoder<PUB extends PublicKey> {
+public interface PublicKeyRawDataDecoder {
     /**
      * @param  session                  The {@link SessionContext} for invoking this command - may be {@code null} if
      *                                  not invoked within a session context (e.g., offline tool or session unknown).
@@ -45,13 +45,13 @@ public interface PublicKeyRawDataDecoder<PUB extends PublicKey> {
      * @throws IOException              If failed to decode the key
      * @throws GeneralSecurityException If failed to generate the key
      */
-    default PUB decodePublicKey(
+    default PublicKey decodePublicKey(
             SessionContext session, String keyType, byte[] keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         return decodePublicKey(session, keyType, keyData, 0, NumberUtils.length(keyData), headers);
     }
 
-    default PUB decodePublicKey(
+    default PublicKey decodePublicKey(
             SessionContext session, String keyType, byte[] keyData, int offset, int length, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         if (length <= 0) {
@@ -63,7 +63,7 @@ public interface PublicKeyRawDataDecoder<PUB extends PublicKey> {
         }
     }
 
-    PUB decodePublicKeyByType(
+    PublicKey decodePublicKeyByType(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException;
 
@@ -78,6 +78,6 @@ public interface PublicKeyRawDataDecoder<PUB extends PublicKey> {
      * @throws IOException              If failed to read from the data stream
      * @throws GeneralSecurityException If failed to generate the key
      */
-    PUB decodePublicKey(SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
+    PublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException;
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java
index 6739551a2..5207f97c4 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java
@@ -18,14 +18,11 @@
  */
 package org.apache.sshd.common.config.keys.impl;
 
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.NavigableSet;
-import java.util.Objects;
 
-import org.apache.sshd.common.config.keys.IdentityResourceLoader;
+import org.apache.sshd.common.config.keys.KeyTypeNamesSupport;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -35,31 +32,17 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean;
  * @param  <PRV> Generic private key type
  * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public abstract class AbstractIdentityResourceLoader<PUB extends PublicKey, PRV extends PrivateKey>
+public abstract class AbstractIdentityResourceLoader
         extends AbstractLoggingBean
-        implements IdentityResourceLoader<PUB, PRV> {
-    private final Class<PUB> pubType;
-    private final Class<PRV> prvType;
+        implements KeyTypeNamesSupport {
     private final NavigableSet<String> types;
 
-    protected AbstractIdentityResourceLoader(Class<PUB> pubType, Class<PRV> prvType, Collection<String> keyTypes) {
-        this.pubType = Objects.requireNonNull(pubType, "No public key type specified");
-        this.prvType = Objects.requireNonNull(prvType, "No private key type specified");
+    protected AbstractIdentityResourceLoader(Collection<String> keyTypes) {
         this.types = Collections.unmodifiableNavigableSet(
                 GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
                         ValidateUtils.checkNotNullAndNotEmpty(keyTypes, "No key type names provided")));
     }
 
-    @Override
-    public final Class<PUB> getPublicKeyType() {
-        return pubType;
-    }
-
-    @Override
-    public final Class<PRV> getPrivateKeyType() {
-        return prvType;
-    }
-
     @Override
     public NavigableSet<String> getSupportedKeyTypes() {
         return types;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java
index d7c88a631..7d14e6b8b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java
@@ -33,27 +33,23 @@ import org.apache.sshd.common.config.keys.KeyEntryResolver;
  * @param  <PRV> Type of {@link PrivateKey}
  * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public abstract class AbstractKeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey>
-        extends AbstractIdentityResourceLoader<PUB, PRV>
-        implements KeyEntryResolver<PUB, PRV> {
-    protected AbstractKeyEntryResolver(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
-        super(pubType, prvType, names);
+public abstract class AbstractKeyEntryResolver extends AbstractIdentityResourceLoader implements KeyEntryResolver {
+    protected AbstractKeyEntryResolver(Collection<String> names) {
+        super(names);
     }
 
-    public PUB generatePublicKey(KeySpec keySpec) throws GeneralSecurityException {
+    public PublicKey generatePublicKey(KeySpec keySpec) throws GeneralSecurityException {
         KeyFactory factory = getKeyFactoryInstance();
-        Class<PUB> keyType = getPublicKeyType();
-        return keyType.cast(factory.generatePublic(keySpec));
+        return factory.generatePublic(keySpec);
     }
 
-    public PRV generatePrivateKey(KeySpec keySpec) throws GeneralSecurityException {
+    public PrivateKey generatePrivateKey(KeySpec keySpec) throws GeneralSecurityException {
         KeyFactory factory = getKeyFactoryInstance();
-        Class<PRV> keyType = getPrivateKeyType();
-        return keyType.cast(factory.generatePrivate(keySpec));
+        return factory.generatePrivate(keySpec);
     }
 
     @Override
     public String toString() {
-        return getPublicKeyType().getSimpleName() + ": " + getSupportedKeyTypes();
+        return getSupportedKeyTypes().toString();
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java
index 00157884a..bf6b8bc7e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java
@@ -19,21 +19,15 @@
 
 package org.apache.sshd.common.config.keys.impl;
 
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.util.Collection;
 
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 
 /**
- * @param  <PUB> Type of {@link PublicKey}
- * @param  <PRV> Type of {@link PrivateKey}
- * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public abstract class AbstractPrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
-        extends AbstractKeyEntryResolver<PUB, PRV>
-        implements PrivateKeyEntryDecoder<PUB, PRV> {
-    protected AbstractPrivateKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
-        super(pubType, prvType, names);
+public abstract class AbstractPrivateKeyEntryDecoder extends AbstractKeyEntryResolver implements PrivateKeyEntryDecoder {
+    protected AbstractPrivateKeyEntryDecoder(Collection<String> names) {
+        super(names);
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
index 71bbe8b3e..cf9cde054 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
@@ -35,11 +35,9 @@ import org.apache.sshd.common.util.MapEntryUtils;
  * @param  <PRV> Type of {@link PrivateKey}
  * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public abstract class AbstractPublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
-        extends AbstractKeyEntryResolver<PUB, PRV>
-        implements PublicKeyEntryDecoder<PUB, PRV> {
-    protected AbstractPublicKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
-        super(pubType, prvType, names);
+public abstract class AbstractPublicKeyEntryDecoder extends AbstractKeyEntryResolver implements PublicKeyEntryDecoder {
+    protected AbstractPublicKeyEntryDecoder(Collection<String> names) {
+        super(names);
     }
 
     protected final boolean parseBooleanHeader(Map<String, String> headers, String propertyKey, boolean defaultVal) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java
index b91b50722..3962a5fd5 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java
@@ -26,8 +26,8 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
+import java.security.PublicKey;
 import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.DSAPublicKey;
 import java.security.spec.DSAPublicKeySpec;
 import java.security.spec.InvalidKeySpecException;
@@ -39,21 +39,21 @@ import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAPublicKey, DSAPrivateKey> {
+public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder {
     public static final DSSPublicKeyEntryDecoder INSTANCE = new DSSPublicKeyEntryDecoder();
 
     public DSSPublicKeyEntryDecoder() {
-        super(DSAPublicKey.class, DSAPrivateKey.class,
-              Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS)));
+        super(Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS)));
     }
 
     @Override
-    public DSAPublicKey decodePublicKey(
+    public PublicKey decodePublicKey(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we were invoked directly
@@ -69,8 +69,8 @@ public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAP
     }
 
     @Override
-    public String encodePublicKey(OutputStream s, DSAPublicKey key) throws IOException {
-        Objects.requireNonNull(key, "No public key provided");
+    public String encodePublicKey(OutputStream s, PublicKey k) throws IOException {
+        DSAPublicKey key = ValidateUtils.checkInstanceOf(k, DSAPublicKey.class, "Key must be a DSAPublicKey");
 
         DSAParams keyParams = Objects.requireNonNull(key.getParams(), "No DSA params available");
         KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_DSS);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
index aab561584..24c572470 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
@@ -26,7 +26,7 @@ import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
-import java.security.interfaces.ECPrivateKey;
+import java.security.PublicKey;
 import java.security.interfaces.ECPublicKey;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
@@ -36,18 +36,18 @@ import java.util.Map;
 import java.util.Objects;
 
 import org.apache.sshd.common.cipher.ECCurves;
-import org.apache.sshd.common.config.keys.IdentityResourceLoader;
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<ECPublicKey, ECPrivateKey> {
-    public static final int MAX_ALLOWED_POINT_SIZE = IdentityResourceLoader.MAX_BIGINT_OCTETS_COUNT;
+public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder {
+    public static final int MAX_ALLOWED_POINT_SIZE = KeyEntryResolver.MAX_BIGINT_OCTETS_COUNT;
     public static final int MAX_CURVE_NAME_LENGTH = 1024;
 
     public static final ECDSAPublicKeyEntryDecoder INSTANCE = new ECDSAPublicKeyEntryDecoder();
@@ -58,11 +58,11 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
     public static final byte ECPOINT_COMPRESSED_VARIANT_3 = 0x02;
 
     public ECDSAPublicKeyEntryDecoder() {
-        super(ECPublicKey.class, ECPrivateKey.class, ECCurves.KEY_TYPES);
+        super(ECCurves.KEY_TYPES);
     }
 
     @Override
-    public ECPublicKey decodePublicKey(
+    public PublicKey decodePublicKey(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         ECCurves curve = ECCurves.fromKeyType(keyType);
@@ -73,7 +73,7 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
         return decodePublicKey(curve, keyData);
     }
 
-    ECPublicKey decodePublicKey(ECCurves curve, InputStream keyData) throws IOException, GeneralSecurityException {
+    PublicKey decodePublicKey(ECCurves curve, InputStream keyData) throws IOException, GeneralSecurityException {
         String keyCurveName = curve.getName();
         // see rfc5656 section 3.1
         String encCurveName = KeyEntryResolver.decodeString(keyData, MAX_CURVE_NAME_LENGTH);
@@ -101,8 +101,8 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
     }
 
     @Override
-    public String encodePublicKey(OutputStream s, ECPublicKey key) throws IOException {
-        Objects.requireNonNull(key, "No public key provided");
+    public String encodePublicKey(OutputStream s, PublicKey k) throws IOException {
+        ECPublicKey key = ValidateUtils.checkInstanceOf(k, ECPublicKey.class, "Key must be an ECPublicKey");
 
         ECParameterSpec params = Objects.requireNonNull(key.getParams(), "No EC parameters available");
         ECCurves curve = Objects.requireNonNull(ECCurves.fromCurveParameters(params), "Cannot determine curve");
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
index 8556d1537..25b2bc1cb 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
@@ -26,7 +26,7 @@ import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
@@ -36,6 +36,7 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.OpenSshCertificate;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.buffer.keys.OpenSSHCertPublicKeyParser;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -43,20 +44,19 @@ import org.apache.sshd.common.util.io.IoUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<OpenSshCertificate, PrivateKey> {
+public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder {
     public static final OpenSSHCertificateDecoder INSTANCE = new OpenSSHCertificateDecoder();
 
     public OpenSSHCertificateDecoder() {
-        super(OpenSshCertificate.class, PrivateKey.class,
-              Collections.unmodifiableList(Arrays.asList(
-                      KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS,
-                      KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS,
-                      KeyPairProvider.SSH_RSA_CERT,
-                      KeyPairProvider.SSH_DSS_CERT,
-                      KeyPairProvider.SSH_ED25519_CERT,
-                      KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT,
-                      KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT,
-                      KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT)));
+        super(Collections.unmodifiableList(Arrays.asList(
+                KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS,
+                KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS,
+                KeyPairProvider.SSH_RSA_CERT,
+                KeyPairProvider.SSH_DSS_CERT,
+                KeyPairProvider.SSH_ED25519_CERT,
+                KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT,
+                KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT,
+                KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT)));
     }
 
     @Override
@@ -76,7 +76,9 @@ public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<Ope
     }
 
     @Override
-    public String encodePublicKey(OutputStream s, OpenSshCertificate key) throws IOException {
+    public String encodePublicKey(OutputStream s, PublicKey k) throws IOException {
+        OpenSshCertificate key = ValidateUtils.checkInstanceOf(k, OpenSshCertificate.class,
+                "Key must be an OpenSshCertificate");
         Objects.requireNonNull(key, "No public key provided");
 
         ByteArrayBuffer buffer = new ByteArrayBuffer();
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
index 55ea87436..3a694ba46 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
@@ -26,38 +26,37 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
-import java.security.interfaces.RSAPrivateKey;
+import java.security.PublicKey;
 import java.security.interfaces.RSAPublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPublicKeySpec;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
-import java.util.Objects;
 
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
+public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder {
     public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder();
 
     public RSAPublicKeyDecoder() {
-        super(RSAPublicKey.class, RSAPrivateKey.class,
-              Collections.unmodifiableList(
-                      Arrays.asList(KeyPairProvider.SSH_RSA,
-                              // Not really required, but allow it
-                              KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS,
-                              KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS)));
+        super(Collections.unmodifiableList(
+                Arrays.asList(KeyPairProvider.SSH_RSA,
+                        // Not really required, but allow it
+                        KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS,
+                        KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS)));
     }
 
     @Override
-    public RSAPublicKey decodePublicKey(
+    public PublicKey decodePublicKey(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         // Not really required, but allow it
@@ -73,8 +72,8 @@ public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublic
     }
 
     @Override
-    public String encodePublicKey(OutputStream s, RSAPublicKey key) throws IOException {
-        Objects.requireNonNull(key, "No public key provided");
+    public String encodePublicKey(OutputStream s, PublicKey k) throws IOException {
+        RSAPublicKey key = ValidateUtils.checkInstanceOf(k, RSAPublicKey.class, "Key must be a RSAPublicKey");
         KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_RSA);
         KeyEntryResolver.encodeBigInt(s, key.getPublicExponent());
         KeyEntryResolver.encodeBigInt(s, key.getModulus());
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java
index 5e2dbda57..f43a7a9ca 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java
@@ -26,22 +26,22 @@ import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.interfaces.ECPublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Collections;
 import java.util.Map;
-import java.util.Objects;
 
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.u2f.SkEcdsaPublicKey;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class SkECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<SkEcdsaPublicKey, PrivateKey> {
+public class SkECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder {
     public static final String KEY_TYPE = "sk-ecdsa-sha2-nistp256@openssh.com";
     public static final int MAX_APP_NAME_LENGTH = 1024;
 
@@ -51,7 +51,7 @@ public class SkECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<
     private static final String VERIFY_REQUIRED_HEADER = "verify-required";
 
     public SkECDSAPublicKeyEntryDecoder() {
-        super(SkEcdsaPublicKey.class, PrivateKey.class, Collections.singleton(KEY_TYPE));
+        super(Collections.singleton(KEY_TYPE));
     }
 
     @Override
@@ -64,14 +64,15 @@ public class SkECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<
 
         boolean noTouchRequired = parseBooleanHeader(headers, NO_TOUCH_REQUIRED_HEADER, false);
         boolean verifyRequired = parseBooleanHeader(headers, VERIFY_REQUIRED_HEADER, false);
-        ECPublicKey ecPublicKey = ECDSAPublicKeyEntryDecoder.INSTANCE.decodePublicKey(ECCurves.nistp256, keyData);
+        PublicKey pk = ECDSAPublicKeyEntryDecoder.INSTANCE.decodePublicKey(ECCurves.nistp256, keyData);
+        ECPublicKey ecPublicKey = ValidateUtils.checkInstanceOf(pk, ECPublicKey.class, "Key must be an ECPublicKey");
         String appName = KeyEntryResolver.decodeString(keyData, MAX_APP_NAME_LENGTH);
         return new SkEcdsaPublicKey(appName, noTouchRequired, verifyRequired, ecPublicKey);
     }
 
     @Override
-    public String encodePublicKey(OutputStream s, SkEcdsaPublicKey key) throws IOException {
-        Objects.requireNonNull(key, "No public key provided");
+    public String encodePublicKey(OutputStream s, PublicKey k) throws IOException {
+        SkEcdsaPublicKey key = ValidateUtils.checkInstanceOf(k, SkEcdsaPublicKey.class, "Key must be an SkEcdsaPublicKey");
         ECDSAPublicKeyEntryDecoder.encodePublicKey(s, KEY_TYPE, ECCurves.nistp256, key.getDelegatePublicKey().getW());
         KeyEntryResolver.encodeString(s, key.getAppName());
         return KEY_TYPE;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
index 6fa4e22f0..b80d644d2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
@@ -27,24 +27,23 @@ import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Collections;
 import java.util.Map;
-import java.util.Objects;
 
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<SkED25519PublicKey, PrivateKey> {
+public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder {
     public static final String KEY_TYPE = "sk-ssh-ed25519@openssh.com";
     public static final int MAX_APP_NAME_LENGTH = 1024;
 
@@ -54,7 +53,7 @@ public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecode
     private static final String VERIFY_REQUIRED_HEADER = "verify-required";
 
     public SkED25519PublicKeyEntryDecoder() {
-        super(SkED25519PublicKey.class, PrivateKey.class, Collections.singleton(KEY_TYPE));
+        super(Collections.singleton(KEY_TYPE));
     }
 
     @Override
@@ -74,8 +73,9 @@ public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecode
     }
 
     @Override
-    public String encodePublicKey(OutputStream s, SkED25519PublicKey key) throws IOException {
-        Objects.requireNonNull(key, "No public key provided");
+    public String encodePublicKey(OutputStream s, PublicKey k) throws IOException {
+        SkED25519PublicKey key = ValidateUtils.checkInstanceOf(k, SkED25519PublicKey.class,
+                "Key must be an SkED25519PublicKey");
         KeyEntryResolver.encodeString(s, KEY_TYPE);
         try {
             byte[] keyData = EdDSAUtils.getBytes(key.getDelegatePublicKey());
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
index a821846cb..ad1d202ab 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
@@ -25,6 +25,8 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.interfaces.DSAParams;
 import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.DSAPublicKey;
@@ -39,22 +41,22 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class OpenSSHDSSPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<DSAPublicKey, DSAPrivateKey> {
+public class OpenSSHDSSPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder {
     public static final OpenSSHDSSPrivateKeyEntryDecoder INSTANCE = new OpenSSHDSSPrivateKeyEntryDecoder();
 
     public OpenSSHDSSPrivateKeyEntryDecoder() {
-        super(DSAPublicKey.class, DSAPrivateKey.class,
-              Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS)));
+        super(Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS)));
     }
 
     @Override
-    public DSAPrivateKey decodePrivateKey(
+    public PrivateKey decodePrivateKey(
             SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
             throws IOException, GeneralSecurityException {
         if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we were invoked directly
@@ -72,7 +74,11 @@ public class OpenSSHDSSPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDec
     }
 
     @Override
-    public String encodePrivateKey(SecureByteArrayOutputStream s, DSAPrivateKey key, DSAPublicKey pubKey) throws IOException {
+    public String encodePrivateKey(SecureByteArrayOutputStream s, PrivateKey k, PublicKey pk) throws IOException {
+        DSAPrivateKey key = ValidateUtils.checkInstanceOf(k, DSAPrivateKey.class, "Key must be a DSAPrivateKey");
+        DSAPublicKey pubKey = pk == null
+                ? null
+                : ValidateUtils.checkInstanceOf(pk, DSAPublicKey.class, "Key must be a DSAPublicKey");
         Objects.requireNonNull(key, "No private key provided");
 
         DSAParams keyParams = Objects.requireNonNull(key.getParams(), "No DSA params available");
@@ -96,8 +102,9 @@ public class OpenSSHDSSPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDec
     }
 
     @Override
-    public DSAPublicKey recoverPublicKey(DSAPrivateKey privateKey) throws GeneralSecurityException {
-        return KeyUtils.recoverDSAPublicKey(privateKey);
+    public PublicKey recoverPublicKey(PrivateKey k) throws GeneralSecurityException {
+        DSAPrivateKey key = ValidateUtils.checkInstanceOf(k, DSAPrivateKey.class, "Key must be a DSAPrivateKey");
+        return KeyUtils.recoverDSAPublicKey(key);
     }
 
     @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
index cb659cd32..82259e3a1 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
@@ -27,6 +27,8 @@ import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.interfaces.ECPrivateKey;
 import java.security.interfaces.ECPublicKey;
 import java.security.spec.ECParameterSpec;
@@ -41,21 +43,22 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<ECPublicKey, ECPrivateKey> {
+public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder {
     public static final OpenSSHECDSAPrivateKeyEntryDecoder INSTANCE = new OpenSSHECDSAPrivateKeyEntryDecoder();
 
     public OpenSSHECDSAPrivateKeyEntryDecoder() {
-        super(ECPublicKey.class, ECPrivateKey.class, ECCurves.KEY_TYPES);
+        super(ECCurves.KEY_TYPES);
     }
 
     @Override
-    public ECPrivateKey decodePrivateKey(
+    public PrivateKey decodePrivateKey(
             SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
             throws IOException, GeneralSecurityException {
         ECCurves curve = ECCurves.fromKeyType(keyType);
@@ -79,9 +82,9 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
     }
 
     @Override
-    public String encodePrivateKey(SecureByteArrayOutputStream s, ECPrivateKey key, ECPublicKey pubKey) throws IOException {
-        Objects.requireNonNull(key, "No private key provided");
-        Objects.requireNonNull(pubKey, "No public key provided");
+    public String encodePrivateKey(SecureByteArrayOutputStream s, PrivateKey k, PublicKey pk) throws IOException {
+        ECPrivateKey key = ValidateUtils.checkInstanceOf(k, ECPrivateKey.class, "Key must be an ECPrivateKey");
+        ECPublicKey pubKey = ValidateUtils.checkInstanceOf(pk, ECPublicKey.class, "Key must be an ECPublicKey");
         ECCurves curve = ECCurves.fromECKey(key);
         if (curve == null) {
             return null;
@@ -95,7 +98,8 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
     }
 
     @Override
-    public ECPublicKey recoverPublicKey(ECPrivateKey prvKey) throws GeneralSecurityException {
+    public PublicKey recoverPublicKey(PrivateKey key) throws GeneralSecurityException {
+        ECPrivateKey prvKey = ValidateUtils.checkInstanceOf(key, ECPrivateKey.class, "Key must be an ECPrivateKey");
         ECCurves curve = ECCurves.fromECKey(prvKey);
         if (curve == null) {
             throw new InvalidKeyException("Unknown curve");
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
index 107849cfd..5c2f77811 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
@@ -79,7 +79,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
     public static final OpenSSHKeyPairResourceParser INSTANCE = new OpenSSHKeyPairResourceParser();
 
     private static final byte[] AUTH_MAGIC_BYTES = AUTH_MAGIC.getBytes(StandardCharsets.UTF_8);
-    private static final Map<String, PrivateKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP
+    private static final Map<String, PrivateKeyEntryDecoder> BY_KEY_TYPE_DECODERS_MAP
             = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
 
     static {
@@ -212,7 +212,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
         byte[] keyData = KeyEntryResolver.readRLEBytes(stream, MAX_PUBLIC_KEY_DATA_SIZE);
         try (InputStream bais = new ByteArrayInputStream(keyData)) {
             String keyType = KeyEntryResolver.decodeString(bais, MAX_KEY_TYPE_NAME_LENGTH);
-            PublicKeyEntryDecoder<?, ?> decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
+            PublicKeyEntryDecoder decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
             if (decoder == null) {
                 throw new NoSuchAlgorithmException("Unsupported key type (" + keyType + ") in " + resourceKey);
             }
@@ -295,7 +295,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
                                                + " in " + resourceKey);
         }
 
-        PrivateKeyEntryDecoder<?, ?> decoder = getPrivateKeyEntryDecoder(prvType);
+        PrivateKeyEntryDecoder decoder = getPrivateKeyEntryDecoder(prvType);
         if (decoder == null) {
             throw new NoSuchAlgorithmException("Unsupported key type (" + prvType + ") in " + resourceKey);
         }
@@ -338,7 +338,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
      * @see                             PrivateKeyEntryDecoder#getPublicKeyType()
      * @see                             PrivateKeyEntryDecoder#getSupportedKeyTypes()
      */
-    public static void registerPrivateKeyEntryDecoder(PrivateKeyEntryDecoder<?, ?> decoder) {
+    public static void registerPrivateKeyEntryDecoder(PrivateKeyEntryDecoder decoder) {
         Objects.requireNonNull(decoder, "No decoder specified");
 
         Collection<String> names
@@ -355,7 +355,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
      *                 {@code null}/empty
      * @return         The registered {@link PrivateKeyEntryDecoder} or {code null} if not found
      */
-    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(String keyType) {
+    public static PrivateKeyEntryDecoder getPrivateKeyEntryDecoder(String keyType) {
         if (GenericUtils.isEmpty(keyType)) {
             return null;
         }
@@ -371,13 +371,13 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
      *            same decoder - {@code null} if no match found
      * @see       #getPrivateKeyEntryDecoder(Key)
      */
-    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(KeyPair kp) {
+    public static PrivateKeyEntryDecoder getPrivateKeyEntryDecoder(KeyPair kp) {
         if (kp == null) {
             return null;
         }
 
-        PrivateKeyEntryDecoder<?, ?> d1 = getPrivateKeyEntryDecoder(kp.getPublic());
-        PrivateKeyEntryDecoder<?, ?> d2 = getPrivateKeyEntryDecoder(kp.getPrivate());
+        PrivateKeyEntryDecoder d1 = getPrivateKeyEntryDecoder(kp.getPublic());
+        PrivateKeyEntryDecoder d2 = getPrivateKeyEntryDecoder(kp.getPrivate());
         if (d1 == d2) {
             return d1;
         } else {
@@ -389,7 +389,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
      * @param  key The {@link Key} (public or private) - ignored if {@code null}
      * @return     The registered {@link PrivateKeyEntryDecoder} for this key or {code null} if no match found
      */
-    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(Key key) {
+    public static PrivateKeyEntryDecoder getPrivateKeyEntryDecoder(Key key) {
         if (key == null) {
             return null;
         } else {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
index b149217df..f5b14fe89 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
@@ -25,9 +25,10 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.util.Collections;
@@ -39,23 +40,23 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
+public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder {
     public static final BigInteger DEFAULT_PUBLIC_EXPONENT = KeyUtils.DEFAULT_RSA_PUBLIC_EXPONENT;
     public static final OpenSSHRSAPrivateKeyDecoder INSTANCE = new OpenSSHRSAPrivateKeyDecoder();
 
     public OpenSSHRSAPrivateKeyDecoder() {
-        super(RSAPublicKey.class, RSAPrivateKey.class,
-              Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
+        super(Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
     }
 
     @Override
-    public RSAPrivateKey decodePrivateKey(
+    public PrivateKey decodePrivateKey(
             SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
             throws IOException, GeneralSecurityException {
         if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
@@ -82,7 +83,7 @@ public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder<
     }
 
     @Override
-    public String encodePrivateKey(SecureByteArrayOutputStream s, RSAPrivateKey key, RSAPublicKey pubKey) throws IOException {
+    public String encodePrivateKey(SecureByteArrayOutputStream s, PrivateKey key, PublicKey pubKey) throws IOException {
         Objects.requireNonNull(key, "No private key provided");
         if (key instanceof RSAPrivateCrtKey) {
             RSAPrivateCrtKey a = (RSAPrivateCrtKey) key;
@@ -104,8 +105,9 @@ public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder<
     }
 
     @Override
-    public RSAPublicKey recoverPublicKey(RSAPrivateKey privateKey) throws GeneralSecurityException {
-        return KeyUtils.recoverRSAPublicKey(privateKey);
+    public PublicKey recoverPublicKey(PrivateKey k) throws GeneralSecurityException {
+        RSAPrivateKey key = ValidateUtils.checkInstanceOf(k, RSAPrivateKey.class, "Key must be a RSAPrivateKey");
+        return KeyUtils.recoverRSAPublicKey(key);
     }
 
     @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java
index 082f16ddf..43ad6743c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java
@@ -56,7 +56,7 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class Ssh2PublicKeyEntryDecoder
-        implements PublicKeyRawDataDecoder<PublicKey>, PublicKeyEntryResolver,
+        implements PublicKeyRawDataDecoder, PublicKeyEntryResolver,
         PublicKeyRawDataReader<PublicKey>, KeyTypeNamesSupport {
     public static final NavigableSet<String> SUPPORTED_KEY_TYPES = Collections.unmodifiableNavigableSet(
             GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
@@ -119,7 +119,7 @@ public class Ssh2PublicKeyEntryDecoder
     public PublicKey decodePublicKeyByType(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
-        PublicKeyEntryDecoder<?, ?> decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
+        PublicKeyEntryDecoder decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
         if (decoder == null) {
             throw new InvalidKeySpecException("No decoder for key type=" + keyType);
         }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java
index 9df264183..fd267ca77 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java
@@ -28,7 +28,6 @@ import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.util.Arrays;
 import java.util.Base64;
@@ -152,10 +151,7 @@ public class OpenSSHKeyPairResourceWriter implements KeyPairResourceWriter<OpenS
             KeyEntryResolver.encodeInt(out, check);
             KeyEntryResolver.encodeInt(out, check);
             KeyEntryResolver.encodeString(out, keyType);
-            @SuppressWarnings("unchecked") // Problem with generics
-            PrivateKeyEntryDecoder<PublicKey, PrivateKey> encoder
-                    = (PrivateKeyEntryDecoder<PublicKey, PrivateKey>) OpenSSHKeyPairResourceParser
-                            .getPrivateKeyEntryDecoder(keyType);
+            PrivateKeyEntryDecoder encoder = OpenSSHKeyPairResourceParser.getPrivateKeyEntryDecoder(keyType);
             if (encoder.encodePrivateKey(out, key.getPrivate(), key.getPublic()) == null) {
                 throw new GeneralSecurityException("Cannot encode key of type " + keyType);
             }
@@ -176,9 +172,7 @@ public class OpenSSHKeyPairResourceWriter implements KeyPairResourceWriter<OpenS
 
     public static byte[] encodePublicKey(PublicKey key, String keyType)
             throws IOException, GeneralSecurityException {
-        @SuppressWarnings("unchecked") // Problem with generics.
-        PublicKeyEntryDecoder<PublicKey, ?> decoder
-                = (PublicKeyEntryDecoder<PublicKey, ?>) KeyUtils.getPublicKeyEntryDecoder(keyType);
+        PublicKeyEntryDecoder decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
         if (decoder == null) {
             throw new GeneralSecurityException("Unknown key type: " + keyType);
         }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java
index a9cf72ab2..ec39cdf89 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java
@@ -193,7 +193,7 @@ public interface SecurityProviderRegistrar extends SecurityProviderChoice, Optio
     /**
      * @return the EdDSA support implementation associated with the security provider (if applicable)
      */
-    default Optional<EdDSASupport<?, ?>> getEdDSASupport() {
+    default Optional<EdDSASupport> getEdDSASupport() {
         return Optional.empty();
     }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
index f88f56048..21fa16215 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
@@ -565,7 +565,7 @@ public final class SecurityUtils {
         return isProviderRegistered(EDDSA);
     }
 
-    public static Optional<EdDSASupport<?, ?>> getEdDSASupport() {
+    public static Optional<EdDSASupport> getEdDSASupport() {
         if (isFipsMode()) {
             return Optional.empty();
         }
@@ -575,14 +575,14 @@ public final class SecurityUtils {
             // Prefer the net.i2p.crypto provider if it's available for backwards compatibility
             SecurityProviderRegistrar netI2pCryptoProvider = REGISTERED_PROVIDERS.get(EDDSA);
             if (netI2pCryptoProvider != null) {
-                Optional<EdDSASupport<?, ?>> support = netI2pCryptoProvider.getEdDSASupport();
+                Optional<EdDSASupport> support = netI2pCryptoProvider.getEdDSASupport();
                 if (support.isPresent()) {
                     return support;
                 }
             }
 
             for (Map.Entry<String, SecurityProviderRegistrar> entry : REGISTERED_PROVIDERS.entrySet()) {
-                Optional<EdDSASupport<?, ?>> support = entry.getValue().getEdDSASupport();
+                Optional<EdDSASupport> support = entry.getValue().getEdDSASupport();
                 if (support.isPresent()) {
                     return support;
                 }
@@ -593,8 +593,8 @@ public final class SecurityUtils {
 
     /* -------------------------------------------------------------------- */
 
-    public static PublicKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+    public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (!support.isPresent()) {
             throw new UnsupportedOperationException(EDDSA + " provider N/A");
         }
@@ -602,8 +602,8 @@ public final class SecurityUtils {
         return support.get().getEDDSAPublicKeyEntryDecoder();
     }
 
-    public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getOpenSSHEDDSAPrivateKeyEntryDecoder() {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+    public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (!support.isPresent()) {
             throw new UnsupportedOperationException(EDDSA + " provider N/A");
         }
@@ -612,7 +612,7 @@ public final class SecurityUtils {
     }
 
     public static org.apache.sshd.common.signature.Signature getEDDSASigner() {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (support.isPresent()) {
             return support.get().getEDDSASigner();
         }
@@ -621,38 +621,22 @@ public final class SecurityUtils {
     }
 
     public static int getEDDSAKeySize(Key key) {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         return support.map(edDSASupport -> edDSASupport.getEDDSAKeySize(key)).orElse(-1);
     }
 
-    public static Class<? extends PublicKey> getEDDSAPublicKeyType() {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            return PublicKey.class;
-        }
-        return support.get().getEDDSAPublicKeyType();
-    }
-
-    public static Class<? extends PrivateKey> getEDDSAPrivateKeyType() {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            return PrivateKey.class;
-        }
-        return support.get().getEDDSAPrivateKeyType();
-    }
-
     public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         return support.map(edDSASupport -> edDSASupport.compareEDDSAPPublicKeys(k1, k2)).orElse(false);
     }
 
     public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         return support.map(edDSASupport -> edDSASupport.compareEDDSAPrivateKeys(k1, k2)).orElse(false);
     }
 
     public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (!support.isPresent()) {
             throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
         }
@@ -665,7 +649,7 @@ public final class SecurityUtils {
             throw new InvalidKeyException("Unsupported key type: " + keyType);
         }
 
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (!support.isPresent()) {
             throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
         }
@@ -678,7 +662,7 @@ public final class SecurityUtils {
             throw new InvalidKeyException("Unsupported key type: " + keyType);
         }
 
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (!support.isPresent()) {
             throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
         }
@@ -687,7 +671,7 @@ public final class SecurityUtils {
     }
 
     public static <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (!support.isPresent()) {
             throw new UnsupportedOperationException(EDDSA + " provider not supported");
         }
@@ -700,7 +684,7 @@ public final class SecurityUtils {
     }
 
     public static <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
-        Optional<EdDSASupport<?, ?>> support = getEdDSASupport();
+        Optional<EdDSASupport> support = getEdDSASupport();
         if (!support.isPresent()) {
             throw new UnsupportedOperationException(EDDSA + " provider not supported");
         }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
index f79991e02..a5f177066 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
@@ -173,7 +173,7 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi
     }
 
     @Override
-    public Optional<EdDSASupport<?, ?>> getEdDSASupport() {
+    public Optional<EdDSASupport> getEdDSASupport() {
         if (!isEdDSASupported()) {
             return Optional.empty();
         }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
index 7c5ac4d5a..eb1ffcd45 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
@@ -18,7 +18,6 @@
  */
 package org.apache.sshd.common.util.security.eddsa;
 
-import net.i2p.crypto.eddsa.EdDSAPrivateKey;
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder;
 
@@ -28,12 +27,12 @@ import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKe
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
+public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecoder {
 
     public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder();
 
     private Ed25519PublicKeyDecoder() {
-        super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new NetI2pCryptoEdDSASupport());
+        super(new NetI2pCryptoEdDSASupport());
     }
 
     public static byte[] getSeedValue(EdDSAPublicKey key) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
index 7233495b8..7fcffd240 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
@@ -99,7 +99,7 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
     }
 
     @Override
-    public Optional<EdDSASupport<?, ?>> getEdDSASupport() {
+    public Optional<EdDSASupport> getEdDSASupport() {
         if (!isSupported()) {
             return Optional.empty();
         }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
index 6d63916b2..0c6501f87 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
@@ -111,12 +111,12 @@ public final class EdDSASecurityProviderUtils {
         return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
     }
 
-    public static PublicKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() {
+    public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
         ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
         return Ed25519PublicKeyDecoder.INSTANCE;
     }
 
-    public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getOpenSSHEDDSAPrivateKeyEntryDecoder() {
+    public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
         ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
         return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
     }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
index 098442879..3e2eae767 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
@@ -19,17 +19,13 @@
 
 package org.apache.sshd.common.util.security.eddsa;
 
-import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.Key;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.spec.KeySpec;
 
 import net.i2p.crypto.eddsa.EdDSAPrivateKey;
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
-import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
-import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
 import org.apache.sshd.common.signature.Signature;
@@ -37,19 +33,19 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 
-public class NetI2pCryptoEdDSASupport implements EdDSASupport<EdDSAPublicKey, EdDSAPrivateKey> {
+public class NetI2pCryptoEdDSASupport implements EdDSASupport {
 
     public NetI2pCryptoEdDSASupport() {
         super();
     }
 
     @Override
-    public PublicKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> getEDDSAPublicKeyEntryDecoder() {
+    public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
         return Ed25519PublicKeyDecoder.INSTANCE;
     }
 
     @Override
-    public PrivateKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> getOpenSSHEDDSAPrivateKeyEntryDecoder() {
+    public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
         return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
     }
 
@@ -63,16 +59,6 @@ public class NetI2pCryptoEdDSASupport implements EdDSASupport<EdDSAPublicKey, Ed
         return EdDSASecurityProviderUtils.getEDDSAKeySize(key);
     }
 
-    @Override
-    public Class<EdDSAPublicKey> getEDDSAPublicKeyType() {
-        return EdDSAPublicKey.class;
-    }
-
-    @Override
-    public Class<EdDSAPrivateKey> getEDDSAPrivateKeyType() {
-        return EdDSAPrivateKey.class;
-    }
-
     @Override
     public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
         return EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2);
@@ -108,26 +94,6 @@ public class NetI2pCryptoEdDSASupport implements EdDSASupport<EdDSAPublicKey, Ed
         return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey);
     }
 
-    @Override
-    public KeySpec createPublicKeySpec(EdDSAPublicKey publicKey) {
-        return new EdDSAPublicKeySpec(publicKey.getA(), publicKey.getParams());
-    }
-
-    @Override
-    public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) {
-        return new EdDSAPrivateKeySpec(privateKey.getSeed(), privateKey.getParams());
-    }
-
-    @Override
-    public byte[] getPublicKeyData(EdDSAPublicKey publicKey) {
-        return publicKey == null ? null : publicKey.getAbyte();
-    }
-
-    @Override
-    public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException {
-        return privateKey.getSeed();
-    }
-
     @Override
     public String getKeyFactoryAlgorithm() {
         return SecurityUtils.EDDSA;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
index b4012a90f..7cfb3555b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
@@ -19,8 +19,6 @@
 
 package org.apache.sshd.common.util.security.eddsa;
 
-import net.i2p.crypto.eddsa.EdDSAPrivateKey;
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder;
 
 /**
@@ -30,11 +28,11 @@ import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519P
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class OpenSSHEd25519PrivateKeyEntryDecoder
-        extends GenericOpenSSHEd25519PrivateKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
+        extends GenericOpenSSHEd25519PrivateKeyEntryDecoder {
     public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder();
 
     public OpenSSHEd25519PrivateKeyEntryDecoder() {
-        super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new NetI2pCryptoEdDSASupport());
+        super(new NetI2pCryptoEdDSASupport());
     }
 
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
index cc2659bff..cd8baa249 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
@@ -26,7 +26,6 @@ import java.security.Key;
 import java.security.KeyFactory;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.spec.KeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
@@ -41,27 +40,26 @@ import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519P
 import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
-import org.bouncycastle.crypto.util.PrivateKeyFactory;
 import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
 import org.bouncycastle.jcajce.interfaces.EdDSAKey;
 import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
 import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
 import org.bouncycastle.jcajce.spec.RawEncodedKeySpec;
 
-public class BouncyCastleEdDSASupport implements EdDSASupport<EdDSAPublicKey, EdDSAPrivateKey> {
+public class BouncyCastleEdDSASupport implements EdDSASupport {
 
     public BouncyCastleEdDSASupport() {
         super();
     }
 
     @Override
-    public PublicKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> getEDDSAPublicKeyEntryDecoder() {
-        return new GenericEd25519PublicKeyDecoder<>(EdDSAPublicKey.class, EdDSAPrivateKey.class, this);
+    public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
+        return new GenericEd25519PublicKeyDecoder(this);
     }
 
     @Override
-    public PrivateKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> getOpenSSHEDDSAPrivateKeyEntryDecoder() {
-        return new GenericOpenSSHEd25519PrivateKeyEntryDecoder<>(EdDSAPublicKey.class, EdDSAPrivateKey.class, this);
+    public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
+        return new GenericOpenSSHEd25519PrivateKeyEntryDecoder(this);
     }
 
     @Override
@@ -74,16 +72,6 @@ public class BouncyCastleEdDSASupport implements EdDSASupport<EdDSAPublicKey, Ed
         return key instanceof EdDSAKey ? KEY_SIZE : -1;
     }
 
-    @Override
-    public Class<EdDSAPublicKey> getEDDSAPublicKeyType() {
-        return EdDSAPublicKey.class;
-    }
-
-    @Override
-    public Class<EdDSAPrivateKey> getEDDSAPrivateKeyType() {
-        return EdDSAPrivateKey.class;
-    }
-
     @Override
     public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
         if (!(k1 instanceof EdDSAPublicKey) || !(k2 instanceof EdDSAPublicKey)) {
@@ -140,28 +128,6 @@ public class BouncyCastleEdDSASupport implements EdDSASupport<EdDSAPublicKey, Ed
         throw new UnsupportedOperationException("Full SSHD-440 implementation N/A");
     }
 
-    @Override
-    public KeySpec createPublicKeySpec(EdDSAPublicKey publicKey) {
-        return new RawEncodedKeySpec(publicKey.getPointEncoding());
-    }
-
-    @Override
-    public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) {
-        return new PKCS8EncodedKeySpec(privateKey.getEncoded());
-    }
-
-    @Override
-    public byte[] getPublicKeyData(EdDSAPublicKey publicKey) {
-        return publicKey == null ? null : publicKey.getPointEncoding();
-    }
-
-    @Override
-    public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException {
-        Ed25519PrivateKeyParameters parameters
-                = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(privateKey.getEncoded());
-        return parameters.getEncoded();
-    }
-
     @Override
     public String getKeyFactoryAlgorithm() {
         return SecurityUtils.ED25519;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
index 162ffc09a..1ccbd6b41 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
@@ -22,11 +22,11 @@ package org.apache.sshd.common.util.security.eddsa.generic;
 import java.io.IOException;
 import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
 import java.security.Key;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.spec.KeySpec;
 
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
@@ -41,11 +41,9 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 /**
  * Provides generic operations required of a security provider to support EdDSA and Ed25519.
  *
- * @param  <PUB> type of the public key supported by the security provider
- * @param  <PRV> type of the private key supported by the security provider
- * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface EdDSASupport<PUB extends PublicKey, PRV extends PrivateKey> {
+public interface EdDSASupport {
 
     int KEY_SIZE = 256;
 
@@ -87,12 +85,12 @@ public interface EdDSASupport<PUB extends PublicKey, PRV extends PrivateKey> {
     /**
      * @return the public key entry decoder implementation associated with the security provider.
      */
-    PublicKeyEntryDecoder<PUB, PRV> getEDDSAPublicKeyEntryDecoder();
+    PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder();
 
     /**
      * @return the private key entry decoder implementation associated with the security provider.
      */
-    PrivateKeyEntryDecoder<PUB, PRV> getOpenSSHEDDSAPrivateKeyEntryDecoder();
+    PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder();
 
     /**
      * @return the signature implementation associated with the security provider.
@@ -105,16 +103,6 @@ public interface EdDSASupport<PUB extends PublicKey, PRV extends PrivateKey> {
      */
     int getEDDSAKeySize(Key key);
 
-    /**
-     * @return the public key class type associated with the security provider.
-     */
-    Class<PUB> getEDDSAPublicKeyType();
-
-    /**
-     * @return the private key class type associated with the security provider.
-     */
-    Class<PRV> getEDDSAPrivateKeyType();
-
     /**
      * @param  k1 the first key
      * @param  k2 the second key
@@ -135,19 +123,19 @@ public interface EdDSASupport<PUB extends PublicKey, PRV extends PrivateKey> {
      * @param  key the private key
      * @return     the public key associated with the private key.
      */
-    PUB recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException;
+    PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException;
 
     /**
      * @param  seed the raw public key bytes
      * @return      the associated public key
      */
-    PUB generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException;
+    PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException;
 
     /**
      * @param  seed the raw private key bytes
      * @return      the associated private key
      */
-    PRV generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException;
+    PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException;
 
     /**
      * @param  buffer the buffer to insert the public key into
@@ -166,29 +154,29 @@ public interface EdDSASupport<PUB extends PublicKey, PRV extends PrivateKey> {
      */
     <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey);
 
-    /**
-     * @param  publicKey the public key
-     * @return           a key spec from the public key
-     */
-    KeySpec createPublicKeySpec(PUB publicKey);
-
-    /**
-     * @param  privateKey the private key
-     * @return            a key spec from the private key
-     */
-    KeySpec createPrivateKeySpec(PRV privateKey);
-
     /**
      * @param  publicKey the public key
      * @return           the raw public key bytes associated with the key
      */
-    byte[] getPublicKeyData(PUB publicKey);
+    default byte[] getPublicKeyData(PublicKey publicKey) {
+        try {
+            return EdDSAUtils.getBytes(publicKey);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException(e);
+        }
+    };
 
     /**
      * @param  privateKey the private key
      * @return            the raw private key bytes associated with the key
      */
-    byte[] getPrivateKeyData(PRV privateKey) throws IOException;
+    default byte[] getPrivateKeyData(PrivateKey privateKey) throws IOException {
+        try {
+            return EdDSAUtils.getBytes(privateKey);
+        } catch (InvalidKeyException e) {
+            throw new IOException(e);
+        }
+    }
 
     /**
      * @return the algorithm name used by the provider's {@link java.security.KeyFactory}.
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
index 713b49e37..5aa82508c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
@@ -18,10 +18,17 @@
  */
 package org.apache.sshd.common.util.security.eddsa.generic;
 
+import java.io.IOException;
 import java.security.InvalidKeyException;
+import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
 import java.util.Arrays;
 
+import org.apache.sshd.common.util.io.der.DERParser;
+
 /**
  * Utilities to extract the raw key bytes from ed25519 or ed448 public keys, in a manner that is independent of the
  * actual concrete key implementation classes.
@@ -44,10 +51,43 @@ public final class EdDSAUtils {
     private static final byte[] ED448_X509_PREFIX = {
             0x30, 0x43, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x71, 0x03, 0x3a, 0x00 };
 
+    // For reconstructing private keys from raw bytes we construct a minimal PKCS#8 encoding, using RFC 5208 (version 0)
+    // without the public key and without attributes. This is allowed by RFC 5958 (Asymmetric key packages).
+
+    // Sequence, length 46, (3 bytes: Version 0), Sequence, length 5, OID, length 3, O, I, D, Octet String, length 34,
+    // Octet String, length 32
+    private static final byte[] ED25519_PKCS8_PREFIX = {
+            0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70,
+            0x04, 0x22, 0x04, 0x20 };
+    // Sequence, length 71, (3 bytes: Version 0), Sequence, length 5, OID, length 3, O, I, D, Octet String, length 59,
+    // Octet String, length 57
+    private static final byte[] ED448_PKCS8_PREFIX = {
+            0x30, 0x47, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x71,
+            0x04, 0x3b, 0x04, 0x39 };
+
+    // The first two numbers of the dotted notation are combined into one byte: (1 * 40 + 3) = 43 = 0x2b
+    private static final byte[] ED25519_OID = { 0x2b, 0x65, 0x70 }; // 1.3.101.112
+    private static final byte[] ED448_OID = { 0x2b, 0x65, 0x71 }; // 1.3.101.113
+
     private EdDSAUtils() {
         throw new IllegalStateException("No instantiation");
     }
 
+    private static boolean arrayEq(byte[] a, byte[] b) {
+        if (a == null && b == null) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        int unequal = a.length ^ b.length;
+        int length = Math.min(a.length, b.length);
+        for (int i = 0; i < length; i++) {
+            unequal |= a[i] ^ b[i];
+        }
+        return unequal == 0;
+    }
+
     private static boolean startsWith(byte[] data, byte[] prefix) {
         if (data == null || prefix == null || prefix.length == 0 || data.length < prefix.length) {
             return false;
@@ -86,4 +126,165 @@ public final class EdDSAUtils {
         }
         return Arrays.copyOfRange(encoded, encoded.length - n, encoded.length);
     }
+
+    /**
+     * Retrieves the raw key bytes from an ed25519 or ed448 {@link PrivateKey}.
+     *
+     * @param  key                 {@link PrivateKey} to get the bytes of
+     * @return                     the raw key bytes
+     * @throws InvalidKeyException if the key is not an ed25519 or ed448 key, or if it doesn't use PKCS#8 encoding
+     */
+    public static byte[] getBytes(PrivateKey key) throws InvalidKeyException {
+        // Extract the private key bytes from the PKCS#8 encoding.
+        if (!"PKCS#8".equalsIgnoreCase(key.getFormat())) {
+            throw new InvalidKeyException("Cannot extract private key bytes from a non-PKCS#8 encoding");
+        }
+        byte[] encoded = key.getEncoded();
+        if (encoded == null) {
+            throw new InvalidKeyException("Private key " + key.getClass().getCanonicalName() + " does not support encoding");
+        }
+        try {
+            return asn1Parse(encoded);
+        } finally {
+            Arrays.fill(encoded, (byte) 0);
+        }
+    }
+
+    /**
+     * Extracts the private key bytes from an encoded EdDSA private key by parsing the bytes as ASN.1 according to RFC
+     * 5958 (PKCS #8 encoding):
+     *
+     * <pre>
+     * OneAsymmetricKey ::= SEQUENCE {
+     *   version Version,
+     *   privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+     *   privateKey PrivateKey,
+     *   ...
+     * }
+     *
+     * Version ::= INTEGER
+     * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+     * PrivateKey ::= OCTET STRING
+     *
+     * AlgorithmIdentifier  ::=  SEQUENCE  {
+     *   algorithm   OBJECT IDENTIFIER,
+     *   parameters  ANY DEFINED BY algorithm OPTIONAL
+     * }
+     * </pre>
+     * <p>
+     * and RFC 8410: "... when encoding a OneAsymmetricKey object, the private key is wrapped in a CurvePrivateKey
+     * object and wrapped by the OCTET STRING of the 'privateKey' field."
+     * </p>
+     *
+     * <pre>
+     * CurvePrivateKey ::= OCTET STRING
+     * </pre>
+     *
+     * @param  encoded             encoded private key to extract the private key bytes from
+     * @return                     the extracted private key bytes
+     * @throws InvalidKeyException if the private key cannot be extracted
+     * @see                        <a href="https://tools.ietf.org/html/rfc5958">RFC 5958</a>
+     * @see                        <a href="https://tools.ietf.org/html/rfc8410">RFC 8410</a>
+     */
+    private static byte[] asn1Parse(byte[] encoded) throws InvalidKeyException {
+        byte[] privateKey = null;
+        try (DERParser byteParser = new DERParser(encoded);
+             DERParser oneAsymmetricKey = byteParser.readObject().createParser()) {
+            oneAsymmetricKey.readObject(); // Skip version
+            int n;
+            try (DERParser algorithmIdentifier = oneAsymmetricKey.readObject().createParser()) {
+                byte[] oid = algorithmIdentifier.readObject().getValue();
+                if (arrayEq(ED25519_OID, oid)) {
+                    n = ED25519_LENGTH;
+                } else if (arrayEq(ED448_OID, oid)) {
+                    n = ED448_LENGTH;
+                } else {
+                    throw new InvalidKeyException("Private key is neither ed25519 nor ed448");
+                }
+            }
+            privateKey = oneAsymmetricKey.readObject().getValue();
+            // The last n bytes of this must be the private key bytes.
+            return Arrays.copyOfRange(privateKey, privateKey.length - n, privateKey.length);
+            // Depending on the version there may be optional stuff following, but we don't care about that.
+        } catch (IOException e) {
+            throw new InvalidKeyException("Cannot parse EdDSA private key", e);
+        } finally {
+            if (privateKey != null) {
+                Arrays.fill(privateKey, (byte) 0);
+            }
+        }
+    }
+
+    /**
+     * Creates a {@link KeySpec} for re-creating the given ed25519 or ed448 public key.
+     *
+     * @param  key                 ed25519 or ed448 key to create a {@link KeySpec} for
+     * @return                     the {@link KeySpec}
+     * @throws InvalidKeyException if the key is neither an ed25519 nor an ed448 key
+     */
+    public static KeySpec createKeySpec(PublicKey key) throws InvalidKeyException {
+        return createPublicKeySpec(getBytes(key));
+    }
+
+    /**
+     * Creates a {@link KeySpec} for re-creating the given ed25519 or ed448 private key.
+     *
+     * @param  key                 ed25519 or ed448 key to create a {@link KeySpec} for
+     * @return                     the {@link KeySpec}
+     * @throws InvalidKeyException if the key is neither an ed25519 nor an ed448 key
+     */
+    public static KeySpec createKeySpec(PrivateKey key) throws InvalidKeyException {
+        return createPrivateKeySpec(getBytes(key));
+    }
+
+    /**
+     * Creates a {@link KeySpec} for re-creating an ed25519 or ed448 public key from the raw key bytes.
+     *
+     * @param  keyData             the raw key bytes
+     * @return                     the {@link KeySpec}
+     * @throws InvalidKeyException if the key bytes do not have the appropriate length for an ed25519 or ed448 key
+     */
+    public static KeySpec createPublicKeySpec(byte[] keyData) throws InvalidKeyException {
+        // Create an X.509 encoding for ed25519 or ed448, depending on the length of keyData.
+        if (keyData.length == ED25519_LENGTH) {
+            byte[] x509 = Arrays.copyOf(ED25519_X509_PREFIX, ED25519_X509_PREFIX.length + ED25519_LENGTH);
+            System.arraycopy(keyData, 0, x509, ED25519_X509_PREFIX.length, ED25519_LENGTH);
+            return new X509EncodedKeySpec(x509);
+        } else if (keyData.length == ED448_LENGTH) {
+            byte[] x509 = Arrays.copyOf(ED448_X509_PREFIX, ED448_X509_PREFIX.length + ED448_LENGTH);
+            System.arraycopy(keyData, 0, x509, ED448_X509_PREFIX.length, ED448_LENGTH);
+            return new X509EncodedKeySpec(x509);
+        }
+        throw new InvalidKeyException("Public key data is neither ed25519 nor ed448");
+    }
+
+    /**
+     * Creates a {@link KeySpec} for re-creating an ed25519 or ed448 public key from the raw key bytes.
+     *
+     * @param  keyData             the raw key bytes
+     * @return                     the {@link KeySpec}
+     * @throws InvalidKeyException if the key bytes do not have the appropriate length for an ed25519 or ed448 key
+     */
+    public static KeySpec createPrivateKeySpec(byte[] keyData) throws InvalidKeyException {
+        // Create a PKCS#8 encoding for ed25519 or ed448, depending on the length of keyData.
+        if (keyData.length == ED25519_LENGTH) {
+            byte[] pkcs8 = Arrays.copyOf(ED25519_PKCS8_PREFIX, ED25519_PKCS8_PREFIX.length + ED25519_LENGTH);
+            try {
+                System.arraycopy(keyData, 0, pkcs8, ED25519_PKCS8_PREFIX.length, ED25519_LENGTH);
+                return new PKCS8EncodedKeySpec(pkcs8);
+            } finally {
+                Arrays.fill(pkcs8, (byte) 0);
+            }
+        } else if (keyData.length == ED448_LENGTH) {
+            byte[] pkcs8 = Arrays.copyOf(ED448_PKCS8_PREFIX, ED448_PKCS8_PREFIX.length + ED448_LENGTH);
+            try {
+                System.arraycopy(keyData, 0, pkcs8, ED448_PKCS8_PREFIX.length, ED448_LENGTH);
+                return new PKCS8EncodedKeySpec(pkcs8);
+            } finally {
+                Arrays.fill(pkcs8, (byte) 0);
+            }
+        }
+        throw new InvalidKeyException("Private key data is neither ed25519 nor ed448");
+    }
+
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
index 98455f65c..f3294821a 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
@@ -24,7 +24,6 @@ import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.util.Collections;
 import java.util.Map;
@@ -39,14 +38,13 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class GenericEd25519PublicKeyDecoder<PUB extends PublicKey, PRV extends PrivateKey>
-        extends AbstractPublicKeyEntryDecoder<PUB, PRV> {
+public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder {
     public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this
 
-    protected final EdDSASupport<PUB, PRV> edDSASupport;
+    protected final EdDSASupport edDSASupport;
 
-    public GenericEd25519PublicKeyDecoder(Class<PUB> pubType, Class<PRV> prvType, EdDSASupport<PUB, PRV> edDSASupport) {
-        super(pubType, prvType, Collections.singletonList(KeyPairProvider.SSH_ED25519));
+    public GenericEd25519PublicKeyDecoder(EdDSASupport edDSASupport) {
+        super(Collections.singletonList(KeyPairProvider.SSH_ED25519));
         this.edDSASupport = edDSASupport;
     }
 
@@ -56,7 +54,7 @@ public class GenericEd25519PublicKeyDecoder<PUB extends PublicKey, PRV extends P
     }
 
     @Override
-    public String encodePublicKey(OutputStream s, PUB key) throws IOException {
+    public String encodePublicKey(OutputStream s, PublicKey key) throws IOException {
         Objects.requireNonNull(key, "No public key provided");
         KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519);
         byte[] seed = edDSASupport.getPublicKeyData(key);
@@ -70,7 +68,7 @@ public class GenericEd25519PublicKeyDecoder<PUB extends PublicKey, PRV extends P
     }
 
     @Override
-    public PUB decodePublicKey(
+    public PublicKey decodePublicKey(
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
index 6d48b67cb..62dec443b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
@@ -45,22 +45,20 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class GenericOpenSSHEd25519PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
-        extends AbstractPrivateKeyEntryDecoder<PUB, PRV> {
+public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder {
     private static final int PK_SIZE = 32;
     private static final int SK_SIZE = 32;
     private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE;
 
-    protected final EdDSASupport<PUB, PRV> edDSASupport;
+    protected final EdDSASupport edDSASupport;
 
-    public GenericOpenSSHEd25519PrivateKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType,
-                                                       EdDSASupport<PUB, PRV> edDSASupport) {
-        super(pubType, prvType, Collections.singletonList(KeyPairProvider.SSH_ED25519));
+    public GenericOpenSSHEd25519PrivateKeyEntryDecoder(EdDSASupport edDSASupport) {
+        super(Collections.singletonList(KeyPairProvider.SSH_ED25519));
         this.edDSASupport = edDSASupport;
     }
 
     @Override
-    public PRV decodePrivateKey(
+    public PrivateKey decodePrivateKey(
             SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
             throws IOException, GeneralSecurityException {
         if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
@@ -98,7 +96,7 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder<PUB extends PublicKey,
             }
 
             byte[] sk = Arrays.copyOf(keypair, SK_SIZE);
-            PRV privateKey = edDSASupport.generateEDDSAPrivateKey(sk);
+            PrivateKey privateKey = edDSASupport.generateEDDSAPrivateKey(sk);
 
             // we can now verify the generated pk matches the one we read
             if (!Arrays.equals(edDSASupport.getPublicKeyData(recoverPublicKey(privateKey)), pk)) {
@@ -114,7 +112,7 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder<PUB extends PublicKey,
     }
 
     @Override
-    public String encodePrivateKey(SecureByteArrayOutputStream s, PRV key, PUB pubKey)
+    public String encodePrivateKey(SecureByteArrayOutputStream s, PrivateKey key, PublicKey pubKey)
             throws IOException {
         Objects.requireNonNull(key, "No private key provided");
 
@@ -143,7 +141,7 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder<PUB extends PublicKey,
     }
 
     @Override
-    public PUB recoverPublicKey(PRV prvKey) throws GeneralSecurityException {
+    public PublicKey recoverPublicKey(PrivateKey prvKey) throws GeneralSecurityException {
         return edDSASupport.recoverEDDSAPublicKey(prvKey);
     }
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
index 529f7969b..de4c2dd71 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
@@ -100,6 +100,11 @@ class BuiltinIdentitiesTest extends JUnitTestSupport {
     void fromKey(BuiltinIdentities expected) throws GeneralSecurityException {
         Assumptions.assumeTrue(expected.isSupported(), "Unsupported built-in identity");
         KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(expected.getAlgorithm());
+        if ("EC".equals(expected.getAlgorithm())) {
+            // BC defaults to prime239v1, which is not one of the EC curves supported by SSH.
+            // So force 256 to get the nistp256 curve.
+            gen.initialize(256);
+        }
         KeyPair kp = gen.generateKeyPair();
         outputDebugMessage("Checking built-in identity: %s", expected);
         assertSame(expected, BuiltinIdentities.fromKeyPair(kp), expected + "[pair]");
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/EcdsaPublicKeyEntryDecoderTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/EcdsaPublicKeyEntryDecoderTest.java
index 4e564f2e2..1a6848ec3 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/EcdsaPublicKeyEntryDecoderTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/EcdsaPublicKeyEntryDecoderTest.java
@@ -59,9 +59,7 @@ class EcdsaPublicKeyEntryDecoderTest extends JUnitTestSupport {
 
             KeyPair keyPair = KeyUtils.generateKeyPair(keyType, keySize);
             PublicKey expected = keyPair.getPublic();
-            @SuppressWarnings("unchecked")
-            PublicKeyEntryDecoder<PublicKey, ?> decoder
-                    = (PublicKeyEntryDecoder<PublicKey, ?>) KeyUtils.getPublicKeyEntryDecoder(expected);
+            PublicKeyEntryDecoder decoder = KeyUtils.getPublicKeyEntryDecoder(expected);
             byte[] encodedPublicKey;
             try (ByteArrayOutputStream ostrm = new ByteArrayOutputStream()) {
                 decoder.encodePublicKey(ostrm, expected);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
index 360d8a303..d71f268c4 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
@@ -64,16 +64,12 @@ class OpenSSHKeyPairResourceParserDecodingTest extends OpenSSHKeyPairResourcePar
             throws Exception {
         assertEquals(1, GenericUtils.size(pairs), "Mismatched pairs count");
 
-        Class<? extends PublicKey> pubType = identity.getPublicKeyType();
-        Class<? extends PrivateKey> prvType = identity.getPrivateKeyType();
         Collection<String> supportedTypeNames = identity.getSupportedKeyTypes();
         for (KeyPair kp : pairs) {
             PublicKey pubKey = kp.getPublic();
-            assertObjectInstanceOf("Mismatched public key type", pubType, pubKey);
             assertKeyEquals("Mismatched identity public key", pubEntry, pubKey);
 
             PrivateKey prvKey = kp.getPrivate();
-            assertObjectInstanceOf("Mismatched private key type", prvType, prvKey);
 
             String pubName = KeyUtils.getKeyType(pubKey);
             String prvName = KeyUtils.getKeyType(prvKey);
@@ -85,12 +81,10 @@ class OpenSSHKeyPairResourceParserDecodingTest extends OpenSSHKeyPairResourcePar
 
             validateKeyPairSignable(pubName, kp);
 
-            @SuppressWarnings("rawtypes")
             PrivateKeyEntryDecoder decoder = OpenSSHKeyPairResourceParser.getPrivateKeyEntryDecoder(prvKey);
             assertNotNull(decoder, "No private key decoder");
 
             if (decoder.isPublicKeyRecoverySupported()) {
-                @SuppressWarnings("unchecked")
                 PublicKey recKey = decoder.recoverPublicKey(prvKey);
                 assertKeyEquals("Mismatched recovered public key", pubKey, recKey);
             }
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
index b0028f696..673644668 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
@@ -36,6 +36,7 @@ import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeAll;
@@ -55,14 +56,14 @@ import org.junit.jupiter.params.provider.MethodSource;
 class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends JUnitTestSupport {
 
     private byte[] prvBytes;
-    private PRV privateKey;
+    private PrivateKey privateKey;
     private byte[] pubBytes;
-    private PUB publicKey;
+    private PublicKey publicKey;
     private byte[] msgBytes;
     private byte[] expSignature;
 
     void initEd25519VectorsTest(
-            String name, EdDSASupport<PUB, PRV> support, String prvKey, String pubKey, String msg, String signature)
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg, String signature)
             throws GeneralSecurityException, IOException {
         prvBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, prvKey);
         privateKey = support.generateEDDSAPrivateKey(prvBytes.clone());
@@ -74,10 +75,10 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
 
     static List<Object[]> parameters() {
         List<Object[]> parameters = new ArrayList<>();
-        Map<String, EdDSASupport<?, ?>> supportedSecurityProviders = new LinkedHashMap<>();
+        Map<String, EdDSASupport> supportedSecurityProviders = new LinkedHashMap<>();
         supportedSecurityProviders.put(SecurityUtils.EDDSA, new NetI2pCryptoEdDSASupport());
         supportedSecurityProviders.put(SecurityUtils.BOUNCY_CASTLE, new BouncyCastleEdDSASupport());
-        for (Map.Entry<String, EdDSASupport<?, ?>> entry : supportedSecurityProviders.entrySet()) {
+        for (Map.Entry<String, EdDSASupport> entry : supportedSecurityProviders.entrySet()) {
             String supportClassName = entry.getValue().getClass().getSimpleName();
             parameters.add(new Object[] {
                     supportClassName + " TEST1 - empty message",
@@ -202,7 +203,7 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void publicKeyBytes(
-            String name, EdDSASupport<PUB, PRV> support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
         byte[] publicSeed = support.getPublicKeyData(publicKey);
@@ -212,7 +213,7 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void privateKeyBytes(
-            String name, EdDSASupport<PUB, PRV> support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
         byte[] privateSeed = support.getPrivateKeyData(privateKey);
@@ -222,7 +223,7 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void signature(
-            String name, EdDSASupport<PUB, PRV> support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
         Signature signer = support.getEDDSASigner();
@@ -241,7 +242,7 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void partialBufferSignature(
-            String name, EdDSASupport<PUB, PRV> support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
             String signature)
             throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
@@ -268,10 +269,10 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void recoverEDDSAPublicKey(
-            String name, EdDSASupport<PUB, PRV> support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
-        PUB recoveredKey = support.recoverEDDSAPublicKey(privateKey);
+        PublicKey recoveredKey = support.recoverEDDSAPublicKey(privateKey);
         assertTrue(support.compareEDDSAPPublicKeys(publicKey, recoveredKey), "Recovered key is not equal");
         byte[] recoveredBytes = support.getPublicKeyData(recoveredKey);
         assertArrayEquals(pubBytes, recoveredBytes, "Mismatched public seed value");
@@ -280,12 +281,12 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void createPublicKeySpec(
-            String name, EdDSASupport<PUB, PRV> support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
-        KeySpec keySpec = support.createPublicKeySpec(publicKey);
+        KeySpec keySpec = EdDSAUtils.createKeySpec(publicKey);
         KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider);
-        PUB generatedKey = (PUB) keyFactory.generatePublic(keySpec);
+        PublicKey generatedKey = keyFactory.generatePublic(keySpec);
         assertTrue(support.compareEDDSAPPublicKeys(publicKey, generatedKey), "Generated key is not equal");
         byte[] generatedBytes = support.getPublicKeyData(generatedKey);
         assertArrayEquals(pubBytes, generatedBytes, "Mismatched public seed value");
@@ -294,12 +295,12 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void createPrivateKeySpec(
-            String name, EdDSASupport<PUB, PRV> support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
-        KeySpec keySpec = support.createPrivateKeySpec(privateKey);
+        KeySpec keySpec = EdDSAUtils.createKeySpec(privateKey);
         KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider);
-        PRV generatedKey = (PRV) keyFactory.generatePrivate(keySpec);
+        PrivateKey generatedKey = keyFactory.generatePrivate(keySpec);
         assertTrue(support.compareEDDSAPrivateKeys(privateKey, generatedKey), "Generated key is not equal");
         byte[] generatedBytes = support.getPrivateKeyData(generatedKey);
         assertArrayEquals(prvBytes, generatedBytes, "Mismatched private seed value");
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
index 257563b8e..fc2072918 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
@@ -66,11 +66,11 @@ class SignatureFactoriesTest extends BaseTestSupport implements KeyTypeIndicator
     private String keyType;
     private int keySize;
     private boolean supported;
-    private PublicKeyEntryDecoder<?, ?> pubKeyDecoder;
+    private PublicKeyEntryDecoder pubKeyDecoder;
 
     private void initSignatureFactoriesTest(
             String keyType, NamedFactory<Signature> factory,
-            int keySize, boolean supported, PublicKeyEntryDecoder<?, ?> decoder) {
+            int keySize, boolean supported, PublicKeyEntryDecoder decoder) {
         this.keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
         if (supported) {
             Objects.requireNonNull(factory, "No signature factory provided");
@@ -100,7 +100,7 @@ class SignatureFactoriesTest extends BaseTestSupport implements KeyTypeIndicator
 
     private static void addTests(
             List<Object[]> list, String keyType, NamedFactory<Signature> factory, Collection<Integer> sizes,
-            PublicKeyEntryDecoder<?, ?> decoder) {
+            PublicKeyEntryDecoder decoder) {
         for (Integer keySize : sizes) {
             list.add(new Object[] { keyType, factory, keySize, decoder != null, decoder });
         }
@@ -154,14 +154,14 @@ class SignatureFactoriesTest extends BaseTestSupport implements KeyTypeIndicator
     @ParameterizedTest(name = "type={0}, size={2}")
     void publicKeyAuth(
             String keyType, NamedFactory<Signature> factory, int keySize, boolean supported,
-            PublicKeyEntryDecoder<?, ?> decoder) throws Exception {
+            PublicKeyEntryDecoder decoder) throws Exception {
         initSignatureFactoriesTest(keyType, factory, keySize, supported, decoder);
         Assumptions.assumeTrue(isSupported());
         testKeyPairProvider(getKeyType(), getKeySize(), pubKeyDecoder, Collections.singletonList(factory));
     }
 
     protected void testKeyPairProvider(
-            String keyName, int keySize, PublicKeyEntryDecoder<?, ?> decoder, List<NamedFactory<Signature>> signatures)
+            String keyName, int keySize, PublicKeyEntryDecoder decoder, List<NamedFactory<Signature>> signatures)
             throws Exception {
         testKeyPairProvider(keyName, () -> {
             try {
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java
index 9bc7106d6..e377aec7f 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java
@@ -25,8 +25,6 @@ import java.io.InputStream;
 import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
 import java.util.Base64;
@@ -50,13 +48,11 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @param  <PRV> Generic private key type
  * @author       <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends PrivateKey>
-        extends AbstractIdentityResourceLoader<PUB, PRV>
-        implements PuttyKeyPairResourceParser<PUB, PRV> {
+public abstract class AbstractPuttyKeyDecoder extends AbstractIdentityResourceLoader implements PuttyKeyPairResourceParser {
     public static final String ENCRYPTION_HEADER = "Encryption";
 
-    protected AbstractPuttyKeyDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
-        super(pubType, prvType, names);
+    protected AbstractPuttyKeyDecoder(Collection<String> names) {
+        super(names);
     }
 
     @Override
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/DSSPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/putty/DSSPuttyKeyDecoder.java
index 04426cd41..104bd7a28 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/DSSPuttyKeyDecoder.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/DSSPuttyKeyDecoder.java
@@ -26,8 +26,6 @@ import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
 import java.security.spec.DSAPrivateKeySpec;
 import java.security.spec.DSAPublicKeySpec;
 import java.util.Collection;
@@ -42,11 +40,11 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class DSSPuttyKeyDecoder extends AbstractPuttyKeyDecoder<DSAPublicKey, DSAPrivateKey> {
+public class DSSPuttyKeyDecoder extends AbstractPuttyKeyDecoder {
     public static final DSSPuttyKeyDecoder INSTANCE = new DSSPuttyKeyDecoder();
 
     public DSSPuttyKeyDecoder() {
-        super(DSAPublicKey.class, DSAPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_DSS));
+        super(Collections.singletonList(KeyPairProvider.SSH_DSS));
     }
 
     @Override
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/ECDSAPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/putty/ECDSAPuttyKeyDecoder.java
index ce1eeb2e2..e0027fb17 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/ECDSAPuttyKeyDecoder.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/ECDSAPuttyKeyDecoder.java
@@ -25,8 +25,6 @@ import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
 import java.security.spec.ECPrivateKeySpec;
@@ -47,11 +45,11 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class ECDSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<ECPublicKey, ECPrivateKey> {
+public class ECDSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder {
     public static final ECDSAPuttyKeyDecoder INSTANCE = new ECDSAPuttyKeyDecoder();
 
     public ECDSAPuttyKeyDecoder() {
-        super(ECPublicKey.class, ECPrivateKey.class, ECCurves.KEY_TYPES);
+        super(ECCurves.KEY_TYPES);
     }
 
     @Override
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java
index efc0c9997..b9c6db621 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java
@@ -38,13 +38,11 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class EdDSAPuttyKeyDecoder<PUB extends PublicKey, PRIV extends PrivateKey> extends AbstractPuttyKeyDecoder<PUB, PRIV> {
+public class EdDSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder {
     public static final EdDSAPuttyKeyDecoder INSTANCE = new EdDSAPuttyKeyDecoder();
 
     public EdDSAPuttyKeyDecoder() {
-        super((Class<PUB>) SecurityUtils.getEdDSASupport().get().getEDDSAPublicKeyType(),
-              (Class<PRIV>) SecurityUtils.getEdDSASupport().get().getEDDSAPrivateKeyType(),
-              Collections.singletonList(KeyPairProvider.SSH_ED25519));
+        super(Collections.singletonList(KeyPairProvider.SSH_ED25519));
     }
 
     @Override
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyPairResourceParser.java b/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyPairResourceParser.java
index 5398940f6..d8bea1d3c 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyPairResourceParser.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyPairResourceParser.java
@@ -25,8 +25,6 @@ import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
 import java.util.Collections;
@@ -39,7 +37,7 @@ import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.config.keys.IdentityResourceLoader;
+import org.apache.sshd.common.config.keys.KeyTypeNamesSupport;
 import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
 import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.util.GenericUtils;
@@ -98,8 +96,7 @@ import org.bouncycastle.crypto.params.Argon2Parameters;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 //CHECKSTYLE:ON
-public interface PuttyKeyPairResourceParser<PUB extends PublicKey, PRV extends PrivateKey>
-        extends IdentityResourceLoader<PUB, PRV>, KeyPairResourceParser {
+public interface PuttyKeyPairResourceParser extends KeyTypeNamesSupport, KeyPairResourceParser {
     String KEY_FILE_HEADER_PREFIX = "PuTTY-User-Key-File-";
     String PUBLIC_LINES_HEADER = "Public-Lines";
     String PRIVATE_LINES_HEADER = "Private-Lines";
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyUtils.java b/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyUtils.java
index 38cdeaa4d..50512bb5d 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyUtils.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/PuttyKeyUtils.java
@@ -33,22 +33,22 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public final class PuttyKeyUtils {
-    public static final List<PuttyKeyPairResourceParser<?, ?>> DEFAULT_PARSERS;
+    public static final List<PuttyKeyPairResourceParser> DEFAULT_PARSERS;
 
-    public static final NavigableMap<String, PuttyKeyPairResourceParser<?, ?>> BY_KEY_TYPE;
+    public static final NavigableMap<String, PuttyKeyPairResourceParser> BY_KEY_TYPE;
 
     public static final KeyPairResourceParser DEFAULT_INSTANCE;
 
     static {
-        List<PuttyKeyPairResourceParser<?, ?>> parsers = new ArrayList<>();
+        List<PuttyKeyPairResourceParser> parsers = new ArrayList<>();
         parsers.add(RSAPuttyKeyDecoder.INSTANCE);
         parsers.add(DSSPuttyKeyDecoder.INSTANCE);
         parsers.add(ECDSAPuttyKeyDecoder.INSTANCE);
         if (SecurityUtils.isEDDSACurveSupported()) {
             parsers.add(EdDSAPuttyKeyDecoder.INSTANCE);
         }
-        NavigableMap<String, PuttyKeyPairResourceParser<?, ?>> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        for (PuttyKeyPairResourceParser<?, ?> p : parsers) {
+        NavigableMap<String, PuttyKeyPairResourceParser> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        for (PuttyKeyPairResourceParser p : parsers) {
             Collection<String> supported = p.getSupportedKeyTypes();
             for (String k : supported) {
                 map.put(k, p);
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/RSAPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/putty/RSAPuttyKeyDecoder.java
index 6950ef2a3..bc687ce2f 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/RSAPuttyKeyDecoder.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/RSAPuttyKeyDecoder.java
@@ -26,8 +26,6 @@ import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
@@ -43,11 +41,11 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<RSAPublicKey, RSAPrivateKey> {
+public class RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder {
     public static final RSAPuttyKeyDecoder INSTANCE = new RSAPuttyKeyDecoder();
 
     public RSAPuttyKeyDecoder() {
-        super(RSAPublicKey.class, RSAPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_RSA));
+        super(Collections.singletonList(KeyPairProvider.SSH_RSA));
     }
 
     @Override
diff --git a/sshd-putty/src/test/java/org/apache/sshd/putty/AbstractPuttyTestSupport.java b/sshd-putty/src/test/java/org/apache/sshd/putty/AbstractPuttyTestSupport.java
index 56012a7be..ed7a839b5 100644
--- a/sshd-putty/src/test/java/org/apache/sshd/putty/AbstractPuttyTestSupport.java
+++ b/sshd-putty/src/test/java/org/apache/sshd/putty/AbstractPuttyTestSupport.java
@@ -49,13 +49,13 @@ abstract class AbstractPuttyTestSupport extends JUnitTestSupport {
     protected KeyPair testDecodeEncryptedPuttyKeyFile(
             String encryptedFile, boolean okIfMissing, String password, String keyType)
             throws IOException, GeneralSecurityException {
-        PuttyKeyPairResourceParser<?, ?> parser = PuttyKeyUtils.BY_KEY_TYPE.get(keyType);
+        PuttyKeyPairResourceParser parser = PuttyKeyUtils.BY_KEY_TYPE.get(keyType);
         assertNotNull(parser, "No parser found for key type=" + keyType);
         return testDecodeEncryptedPuttyKeyFile(encryptedFile, okIfMissing, password, parser, keyType);
     }
 
     protected KeyPair testDecodeEncryptedPuttyKeyFile(
-            String encryptedFile, boolean okIfMissing, String password, PuttyKeyPairResourceParser<?, ?> parser, String keyType)
+            String encryptedFile, boolean okIfMissing, String password, PuttyKeyPairResourceParser parser, String keyType)
             throws IOException, GeneralSecurityException {
         assumeTrue(BuiltinCiphers.aes256cbc.isSupported(), BuiltinCiphers.aes256cbc.getTransformation() + " N/A");
 
diff --git a/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java b/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java
index ca95735d3..8fae67ccc 100644
--- a/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java
+++ b/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java
@@ -54,7 +54,7 @@ class PuttyKeyUtilsTest extends AbstractPuttyTestSupport {
     private String keyType;
     private String regularFile;
     private String encryptedFile;
-    private PuttyKeyPairResourceParser<?, ?> parser;
+    private PuttyKeyPairResourceParser parser;
 
     void initPuttyKeyUtilsTest(String keyType) {
         this.keyType = keyType;
@@ -90,7 +90,7 @@ class PuttyKeyUtilsTest extends AbstractPuttyTestSupport {
             NamedResource resourceKey = NamedResource.ofName(resource);
             assertTrue(parser.canExtractKeyPairs(resourceKey, lines), resource + " - can extract key pair");
 
-            for (PuttyKeyPairResourceParser<?, ?> other : PuttyKeyUtils.BY_KEY_TYPE.values()) {
+            for (PuttyKeyPairResourceParser other : PuttyKeyUtils.BY_KEY_TYPE.values()) {
                 if (parser == other) {
                     continue;
                 }


(mina-sshd) 05/05: Update CHANGES.md

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit fdac96c91e229bb17e13390a017bce24d79b72c8
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Sat Sep 6 21:46:35 2025 +0200

    Update CHANGES.md
    
    Move the changes done up to 3.0.0-M1 to a separate file, and start new
    content for the next milestone.
---
 CHANGES.md                             | 33 ++++++++++++---------------------
 CHANGES.md => docs/changes/3.0.0-M1.md | 14 +++++---------
 2 files changed, 17 insertions(+), 30 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 2fcd49d04..39440ae8b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,28 +8,19 @@
 
 Version 3 includes all the features and bug fixes of version 2, including the [latest ones](https://github.com/apache/mina-sshd/blob/master/CHANGES.md#planned-for-next-version).
 
-## Bug fixes
+## Milestone 1: Pre-Release 3.0.0-M1
 
-* [GH-622](https://github.com/apache/mina-sshd/issues/622) Handle quoted values in `HostConfigEntry`.
+Complete refactoring of the SSH transport protocol. New feature: support for client-side proxies.
+
+* [Change notes for 3.0.0-M1](./doc/changes/3.0.0-M1.md)
+
+# Planned for the Next Milestone Release
+
+## Bug Fixes
 
 ## Major Code Re-factoring
 
-* The `AbstractSession` has been completely refactored. Most of its code has been moved out of this class into separate filters in a filter chain. For details, see the [technical documentation](./docs/technical/filters.md).
-* Handling of global requests has been moved from `AbstractSession` to the `ConnectionService`.
-* KEX temporarily closes `RemoteWindow`s, preventing data to be written in that way until KEX is over. Version 2 blocked threads in a different, more convoluted, and fragile way.
-* Deprecated API has been removed.
-    * System property "org.apache.sshd.registerBouncyCastle" is gone; use "org.apache.sshd.security.provider.BC.enabled" instead.
-    * System property "org.apache.sshd.eddsaSupport" is gone; use "org.apache.sshd.security.provider.EdDSA.enabled" instead. (This property applies only to the `net.i2p` ed25519 provider.)
-* Method `KeyUtils.cloneKeyPair()` has been removed. It was never used inside Apache MINA sshd. If you need to duplicate an existing `KeyPair`, use `Key.getEncoded()` on the keys and then re-create a duplicate key using an `X509EncodedKeySpec` for the public key or a `PKCS8EncodedKeySpec` for the private key.
-* `HostConfigEntry` has been changed to be more compliant with OpenSSH, and handles quoted values now. It also has a new method `getValues(key)` to get all the values of a key that can have multiple values, either because it may have multiple space-separated values (such as `UserKnownHostsFile`) or because it appears several times and does not follow the "first match wins" rule (such as `IdentityFile` or `CertificateFile`). Note that some keys have values that are comma-separated lists o [...]
-* Integration tests using docker containers have been moved out of bundle `sshd-core` into a new bundle `sshd-test`, and are run now also with the MINA and the netty transports.
-* All docker tests have been changed to be skipped if no docker engine is running. If a docker engine _is_ running, they will newly also be run on Windows. (Previously, they were disabled unconditionally on Windows because the Windows runners in CI don't have docker support.)
-    * This was back-ported to version 2.17.0-SNAPSHOT on the master branch.
-
-## New Features
-
-* Random padding on SSH packets as suggested by [RFC 4253, section 6](https://datatracker.ietf.org/doc/html/rfc4253#section-6).
-* New event callback `SessionListener.sessionStarting()`. See the [filter documentation](./docs/technical/filters.md). `SessionListener.sessionEstablished()` was removed; it was called from the constructor of `AbstractSession` at a time when the object was not yet fully initialized.
-* [GH-728](https://github.com/apache/mina-sshd/issues/728) New method `ClientSession.getHostConfigEntry()` to get the `HostConfigEntry` for the session.
-* [GH-729](https://github.com/apache/mina-sshd/issues/729) Support for client-side SOCKS5 or HTTP CONNECT proxies. See the [documentation](./docs/client-setup.md#proxies).
-* The [OpenSSH "hostkeys-00@openssh.com" host key rotation extension](https://github.com/openssh/openssh-portable/blob/b5b405fee/PROTOCOL#L367) is now implemented client-side. New host keys so received are registered on the session but we don't update the `known_hosts` file. If you want that, implement your own `NewHostKeysHandler` and set it on the `SshClient`.
+* The classes dealing with serializing or de-serializing public and private keys have been de-generified, which simplifies them a lot. Previous code from version 2 tried to tie particular public key types and private key types together via generics, such that it could be statically checked that only matching key types were used. But that never worked well and in a few crucial places unchecked conversions or raw types were used anyway, which makes the point moot. Code now just uses `Publi [...]
+* The way ed25519 keys are handled has been refactored. Duplicate code has been removed, and the handling has been simplified to make it easier in the near future to include support for other eddsa implementations. This brings additional API breaks, but in code areas that are unlikely to be used in customer code.
+
+## New Features
\ No newline at end of file
diff --git a/CHANGES.md b/docs/changes/3.0.0-M1.md
similarity index 86%
copy from CHANGES.md
copy to docs/changes/3.0.0-M1.md
index 2fcd49d04..5bc40b060 100644
--- a/CHANGES.md
+++ b/docs/changes/3.0.0-M1.md
@@ -1,14 +1,8 @@
-# Previous Versions
+# Milestone Pre-Release 3.0.0-M1
 
-* [Change Notes for Version 2](./docs/changes/version2.md)
+Includes all the features and bug fixes of [version 2.16.0](./2.16.0.md) and up to [commit 084e69b818](https://github.com/apache/mina-sshd/blob/084e69b818/CHANGES.md#planned-for-next-version).
 
-# Version 3
-
-**This is work in progress.**  Version 3 contains many API breaks from version 2. API comparison reports generated by [japicmp](https://siom79.github.io/japicmp) are published at [https://apache.github.io/mina-sshd](https://apache.github.io/mina-sshd).
-
-Version 3 includes all the features and bug fixes of version 2, including the [latest ones](https://github.com/apache/mina-sshd/blob/master/CHANGES.md#planned-for-next-version).
-
-## Bug fixes
+## Bug Fixes
 
 * [GH-622](https://github.com/apache/mina-sshd/issues/622) Handle quoted values in `HostConfigEntry`.
 
@@ -33,3 +27,5 @@ Version 3 includes all the features and bug fixes of version 2, including the [l
 * [GH-728](https://github.com/apache/mina-sshd/issues/728) New method `ClientSession.getHostConfigEntry()` to get the `HostConfigEntry` for the session.
 * [GH-729](https://github.com/apache/mina-sshd/issues/729) Support for client-side SOCKS5 or HTTP CONNECT proxies. See the [documentation](./docs/client-setup.md#proxies).
 * The [OpenSSH "hostkeys-00@openssh.com" host key rotation extension](https://github.com/openssh/openssh-portable/blob/b5b405fee/PROTOCOL#L367) is now implemented client-side. New host keys so received are registered on the session but we don't update the `known_hosts` file. If you want that, implement your own `NewHostKeysHandler` and set it on the `SshClient`.
+
+


(mina-sshd) 01/05: Limit maximum padding for small channel data messages

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 01ad91edf16a27a4218e7fbba18a2c24e38608a5
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Sep 4 21:32:37 2025 +0200

    Limit maximum padding for small channel data messages
    
    Do not blow up small SSH messages too much when adding extra random
    padding.
---
 .../apache/sshd/common/session/filters/CryptFilter.java  | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java
index 776591616..713466912 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java
@@ -67,8 +67,8 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider {
     // - 1 byte padding count
     // - 1 byte payload
     // - 4 bytes padding
-    // Since all ciphers, including the none cipher, have a block size of 8, we need to have
-    // at least 8 bytes if the length itself is not encrypted, or 12 if it is. (Even if a
+    // Since all ciphers, including the none cipher, have a block size of at least 8, we need to
+    // have at least 8 bytes if the length itself is not encrypted, or 12 if it is. (Even if a
     // zero-length payload was allowed, it would be 8 or 12.) So in practice the minimum length
     // is 8 bytes.
     private static final int MIN_PACKET_LENGTH = 8;
@@ -445,17 +445,25 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider {
             }
             // RFC 4253: at least 4, at most 255 bytes.
             int minPadding = 4;
+            int maxPadding = MAX_PADDING;
             // Minor layering break here: always pad messages that might carry user passwords with at least 64 bytes
             // to prevent that traffic analysis might make guesses about password lengths.
             if (cmd >= SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST && cmd <= SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC) {
                 minPadding = 64; // Must be smaller than MAX_PADDING, of course
+            } else if (payloadLength < 16) {
+                // For very small data packets (such as single keystrokes) ensure that we don't grow too much.
+                // 16 is a bit arbitrary; a single keystroke in an interactive session is 10 bytes (SSH_MSG_CHANNEL_DATA
+                // + channel id + data length + 1 byte data), and so are the messages from the "ping@openssh.com"
+                // extension used for keystroke obfuscation (SSH_MSG_PING + data length + "PING!").
+                maxPadding = 64;
             }
             int pad = minPadding;
             // For low-level messages, do not add extra padding.
             if (cmd >= SshConstants.SSH_MSG_KEXINIT) {
                 // RFC 4253: variable amounts of random padding may help thwart traffic analysis. We don't need a secure
-                // random for this.
-                pad = minPadding + random.random(MAX_PADDING + 1 - minPadding);
+                // random for this. Note that the padding can only randomize message size on the wire in quanta of
+                // blockSize. It's a bit unclear how effective this may be against traffic analysis.
+                pad = minPadding + random.random(maxPadding + 1 - minPadding);
             }
             // Now pad is in the range [4..MAX_PADDING]
             int totalLength = toEncrypt + pad;


(mina-sshd) 03/05: Simplify ed25519 handling

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 1b2021fe893f62625376fb9170e80a05cc5fef44
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Fri Sep 5 01:05:42 2025 +0200

    Simplify ed25519 handling
    
    A lot of ed25519 operations can actually be done in a way that does not
    depend on the concrete implementation (Bouncy Castle or net.i2p or even
    Java built-in). Public keys are encoded as X.509 and private keys as
    PKCS#8, and both encodings do contain the raw key bytes. Parsing these
    formats and extracting the key bytes is simple, so we can write keys to
    files or buffers easily. We can also reconstruct keys from the raw bytes
    by creating valid X.509 or PKCS#8 KeySpecs.
    
    The only minor hiccup is that the net.i2p provider uses non-standard
    algorithm names for keys and signatures. So refactor this to generally
    use the standard names, and let the SecurityProviderRegistrar create the
    SecurityEntityFactory. The EdDSASecurityProviderRegistrar can then
    create factories for net.i2p that replace the standard names by the
    net.i2p-specific algorithm names.
---
 .../org/apache/sshd/cli/client/SshKeyScanMain.java |   2 +-
 .../sshd/cli/server/SshServerCliSupport.java       |   2 +-
 .../sshd/common/config/keys/BuiltinIdentities.java |   2 +-
 .../apache/sshd/common/config/keys/KeyUtils.java   |  11 +-
 .../keys/impl/SkED25519PublicKeyEntryDecoder.java  |   9 +-
 .../sshd/common/signature/BuiltinSignatures.java   |   5 +-
 .../sshd/common/signature/SignatureFactory.java    |   3 +-
 .../sshd/common/signature/SignatureSkED25519.java  |   4 +-
 .../org/apache/sshd/common/util/buffer/Buffer.java |  22 +-
 .../buffer/keys/ED25519BufferPublicKeyParser.java  |   4 +-
 .../util/security/SecurityEntityFactory.java       |  10 +-
 .../util/security/SecurityProviderChoice.java      |   7 +
 .../sshd/common/util/security/SecurityUtils.java   |  89 ++-----
 .../BouncyCastleSecurityProviderRegistrar.java     |   5 +-
 .../eddsa/Ed25519PEMResourceKeyParser.java         |   4 +-
 .../eddsa/EdDSASecurityProviderRegistrar.java      |  65 ++++-
 .../security/eddsa/EdDSASecurityProviderUtils.java |  41 +---
 .../security/eddsa/NetI2pCryptoEdDSASupport.java   |  50 ----
 .../util/security/eddsa/SignatureEd25519.java      |  29 ++-
 .../bouncycastle/BouncyCastleEdDSASupport.java     |  75 ------
 .../util/security/eddsa/generic/EdDSASupport.java  |  92 +------
 .../util/security/eddsa/generic/EdDSAUtils.java    | 111 +++++++--
 .../GenericEd25519PEMResourceKeyParser.java        |   2 +-
 .../generic/GenericEd25519PublicKeyDecoder.java    |   9 +-
 ...enericOpenSSHEd25519PrivateKeyEntryDecoder.java |  32 ++-
 .../eddsa/generic/GenericSignatureEd25519.java     |  51 ----
 .../sshd/common/config/keys/KeyRandomArtTest.java  |   9 +-
 .../pem/PKCS8PEMResourceKeyPairParserTest.java     |   4 +-
 .../BouncyCastleGeneratorHostKeyProviderTest.java  |   4 +-
 .../util/security/eddsa/EDDSAProviderTest.java     |  13 +-
 .../util/security/eddsa/Ed25519VectorsTest.java    | 264 ++++++++++-----------
 .../eddsa/EdDSASecurityProviderRegistrarTest.java  |   4 +-
 .../SimpleGeneratorHostKeyProviderTest.java        |   2 +-
 .../pem/{pkcs8-eddsa.enc => pkcs8-ed25519.enc}     |   0
 .../pem/{pkcs8-eddsa.enc2 => pkcs8-ed25519.enc2}   |   0
 .../pem/{pkcs8-eddsa.pem => pkcs8-ed25519.pem}     |   0
 .../apache/sshd/putty/EdDSAPuttyKeyDecoder.java    |   5 +-
 37 files changed, 415 insertions(+), 626 deletions(-)

diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
index 0eacd4b6d..71302e2a9 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
@@ -574,7 +574,7 @@ public class SshKeyScanMain implements Channel, Callable<Void>, ServerKeyVerifie
                 throw new NoSuchAlgorithmException("EDDSA curves not supported: " + keyType);
             }
 
-            KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+            KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.ED25519);
             return Collections.singletonList(g.generateKeyPair());
         } else {
             throw new InvalidKeySpecException("Unknown key type: " + keyType);
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
index cbd01e013..e40f44e29 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
@@ -110,7 +110,7 @@ public abstract class SshServerCliSupport extends CliSupport {
             if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) {
                 keyAlgorithm = KeyUtils.EC_ALGORITHM;
             } else if (BuiltinIdentities.Constants.ED25519.equals(keyAlgorithm)) {
-                keyAlgorithm = SecurityUtils.EDDSA;
+                keyAlgorithm = SecurityUtils.ED25519;
             }
 
             // force re-generation of host key if not same algorithm
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
index 8c9afcf45..f4afc957f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
@@ -44,7 +44,7 @@ public enum BuiltinIdentities implements Identity {
     DSA(Constants.DSA, KeyPairProvider.SSH_DSS),
     ECDSA(Constants.ECDSA, KeyUtils.EC_ALGORITHM,
           ECCurves.VALUES.stream().map(KeyTypeIndicator::getKeyType).collect(Collectors.toList())),
-    ED25119(Constants.ED25519, SecurityUtils.EDDSA, KeyPairProvider.SSH_ED25519) {
+    ED25119(Constants.ED25519, SecurityUtils.ED25519, KeyPairProvider.SSH_ED25519) {
         @Override
         public boolean isSupported() {
             return SecurityUtils.isEDDSACurveSupported();
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index 4ff385c73..059b3a4c3 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -807,8 +807,9 @@ public final class KeyUtils {
             if (curve != null) {
                 return curve.getKeySize();
             }
-        } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
-            return SecurityUtils.getEDDSAKeySize(key);
+        } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())
+                || SecurityUtils.ED25519.equalsIgnoreCase(key.getAlgorithm())) {
+            return 256;
         }
 
         return -1;
@@ -886,7 +887,8 @@ public final class KeyUtils {
             return recoverRSAPublicKey((RSAPrivateKey) key);
         } else if (key instanceof DSAPrivateKey) {
             return recoverDSAPublicKey((DSAPrivateKey) key);
-        } else if ((key != null) && SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
+        } else if ((key != null) && (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())
+                || SecurityUtils.ED25519.equalsIgnoreCase(key.getAlgorithm()))) {
             return SecurityUtils.recoverEDDSAPublicKey(key);
         }
         return null;
@@ -902,6 +904,9 @@ public final class KeyUtils {
         } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
                 && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
             return SecurityUtils.compareEDDSAPrivateKeys(k1, k2);
+        } else if ((k1 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k1.getAlgorithm()) && (k2 != null)
+                && SecurityUtils.ED25519.equalsIgnoreCase(k2.getAlgorithm())) {
+            return SecurityUtils.compareEDDSAPrivateKeys(k1, k2);
         }
         return false; // either key is null or not of same class
     }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
index b80d644d2..2a2e90b15 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
@@ -23,7 +23,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -77,12 +76,8 @@ public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecode
         SkED25519PublicKey key = ValidateUtils.checkInstanceOf(k, SkED25519PublicKey.class,
                 "Key must be an SkED25519PublicKey");
         KeyEntryResolver.encodeString(s, KEY_TYPE);
-        try {
-            byte[] keyData = EdDSAUtils.getBytes(key.getDelegatePublicKey());
-            KeyEntryResolver.writeRLEBytes(s, keyData);
-        } catch (InvalidKeyException e) {
-            throw new IOException(e.getMessage(), e);
-        }
+        byte[] keyData = EdDSAUtils.getBytes(key.getDelegatePublicKey());
+        KeyEntryResolver.writeRLEBytes(s, keyData);
         KeyEntryResolver.encodeString(s, key.getAppName());
         return KEY_TYPE;
     }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
index 54aea9533..bb3444351 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
@@ -44,6 +44,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.SignatureEd25519;
 
 /**
  * Provides easy access to the currently implemented signatures
@@ -197,7 +198,7 @@ public enum BuiltinSignatures implements SignatureFactory {
     ed25519(KeyPairProvider.SSH_ED25519) {
         @Override
         public Signature create() {
-            return SecurityUtils.getEDDSASigner();
+            return new SignatureEd25519();
         }
 
         @Override
@@ -208,7 +209,7 @@ public enum BuiltinSignatures implements SignatureFactory {
     ed25519_cert(KeyPairProvider.SSH_ED25519_CERT) {
         @Override
         public Signature create() {
-            return SecurityUtils.getEDDSASigner();
+            return new SignatureEd25519();
         }
 
         @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
index 6876856e5..e2a1bf681 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
@@ -236,7 +236,8 @@ public interface SignatureFactory extends BuiltinFactory<Signature> {
             } else if (algo.contains("rsa")) {
                 factory = BuiltinSignatures.fromFactoryName(algo);
             }
-        } else if (SecurityUtils.EDDSA.equalsIgnoreCase(pubKey.getAlgorithm())) {
+        } else if (SecurityUtils.EDDSA.equalsIgnoreCase(pubKey.getAlgorithm())
+                || SecurityUtils.ED25519.equalsIgnoreCase(pubKey.getAlgorithm())) {
             factory = BuiltinSignatures.ed25519;
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java
index 8fc0eec79..9c43ab64d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java
@@ -20,7 +20,7 @@ package org.apache.sshd.common.signature;
 
 import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.SignatureEd25519;
 
 public class SignatureSkED25519 extends AbstractSecurityKeySignature {
 
@@ -42,6 +42,6 @@ public class SignatureSkED25519 extends AbstractSecurityKeySignature {
 
     @Override
     protected Signature getDelegateSignature() {
-        return SecurityUtils.getEDDSASigner();
+        return new SignatureEd25519();
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
index ee857d6b4..de59280d7 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
@@ -72,6 +72,7 @@ import org.apache.sshd.common.util.Readable;
 import org.apache.sshd.common.util.buffer.keys.BufferPublicKeyParser;
 import org.apache.sshd.common.util.logging.SimplifiedLog;
 import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
  * Provides an abstract message buffer for encoding SSH messages
@@ -1042,8 +1043,8 @@ public abstract class Buffer implements Readable {
             byte[] ecPoint = ECCurves.encodeECPoint(ecKey.getW(), ecParams);
             putString(curve.getName());
             putBytes(ecPoint);
-        } else if (SecurityUtils.EDDSA.equals(key.getAlgorithm()) || SecurityUtils.ED25519.equals(key.getAlgorithm())) {
-            SecurityUtils.putRawEDDSAPublicKey(this, key);
+        } else if (KeyPairProvider.SSH_ED25519.equals(KeyUtils.getKeyType(key))) {
+            putBytes(EdDSAUtils.getBytes(key));
         } else if (key instanceof SecurityKeyPublicKey) {
             putRawPublicKeyBytes(((SecurityKeyPublicKey<?>) key).getDelegatePublicKey());
             putString(((SecurityKeyPublicKey<?>) key).getAppName());
@@ -1122,8 +1123,21 @@ public abstract class Buffer implements Readable {
             putString(curve.getName());
             putBytes(ecPoint);
             putMPInt(ecPriv.getS());
-        } else if (SecurityUtils.EDDSA.equals(pubKey.getAlgorithm())) {
-            SecurityUtils.putEDDSAKeyPair(this, pubKey, prvKey);
+        } else if (KeyPairProvider.SSH_ED25519.equals(KeyUtils.getKeyType(pubKey))) {
+            putString(KeyPairProvider.SSH_ED25519);
+            byte[] skData = null;
+            try {
+                byte[] pkData = EdDSAUtils.getBytes(pubKey);
+                putBytes(pkData);
+                skData = EdDSAUtils.getBytes(prvKey);
+                putUInt(pkData.length + skData.length);
+                putRawBytes(skData);
+                putRawBytes(pkData);
+            } finally {
+                if (skData != null) {
+                    Arrays.fill(skData, (byte) 0);
+                }
+            }
         } else {
             throw new BufferException("Unsupported key pair algorithm: " + pubKey.getAlgorithm());
         }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java
index 5ca57cd5c..393731c30 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java
@@ -25,7 +25,7 @@ import java.security.PublicKey;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
  * TODO complete this when SSHD-440 is done
@@ -43,6 +43,6 @@ public class ED25519BufferPublicKeyParser extends AbstractBufferPublicKeyParser<
     public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
         ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
         byte[] seed = buffer.getBytes();
-        return SecurityUtils.generateEDDSAPublicKey(keyType, seed);
+        return EdDSAUtils.getPublicKey(seed);
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
index badd54166..d8648cb83 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
@@ -59,16 +59,10 @@ public interface SecurityEntityFactory<T> {
         if (registrar == null) {
             if ((defaultProvider == null) || (defaultProvider == SecurityProviderChoice.EMPTY)) {
                 return toDefaultFactory(entityType);
-            } else if (defaultProvider.isNamedProviderUsed()) {
-                return toNamedProviderFactory(entityType, defaultProvider.getProviderName());
-            } else {
-                return toProviderInstanceFactory(entityType, defaultProvider.getSecurityProvider());
             }
-        } else if (registrar.isNamedProviderUsed()) {
-            return toNamedProviderFactory(entityType, registrar.getProviderName());
-        } else {
-            return toProviderInstanceFactory(entityType, registrar.getSecurityProvider());
+            return defaultProvider.getFactory(entityType);
         }
+        return registrar.getFactory(entityType);
     }
 
     static <F> SecurityEntityFactory<F> toDefaultFactory(Class<F> entityType)
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java
index 326873b1d..1da7e6017 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java
@@ -71,6 +71,13 @@ public interface SecurityProviderChoice extends NamedResource {
         return getName();
     }
 
+    default <F> SecurityEntityFactory<F> getFactory(Class<F> entityType) throws ReflectiveOperationException {
+        if (isNamedProviderUsed()) {
+            return SecurityEntityFactory.toNamedProviderFactory(entityType, getProviderName());
+        }
+        return SecurityEntityFactory.toProviderInstanceFactory(entityType, getSecurityProvider());
+    }
+
     /**
      * @return The security {@link Provider} to use in case {@link #isNamedProviderUsed()} is {@code false}. Can be
      *         {@code null} if {@link #isNamedProviderUsed()} is {@code true}, but not recommended.
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
index 21fa16215..dcfb98101 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
@@ -24,7 +24,6 @@ import java.math.BigInteger;
 import java.nio.file.Path;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
-import java.security.Key;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -78,6 +77,7 @@ import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleGeneratorHo
 import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleKeyPairResourceParser;
 import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleRandomFactory;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
 import org.slf4j.Logger;
@@ -95,14 +95,13 @@ public final class SecurityUtils {
     public static final String BOUNCY_CASTLE = "BC";
 
     /**
-     * EDDSA support - should match {@code EdDSAKey.KEY_ALGORITHM}
+     * EDDSA (net.i2p) {@link SecurityProviderRegistrar} name.
      */
     public static final String EDDSA = "EdDSA";
 
-    // A copy-paste from the original, but we don't want to drag the classes into the classpath
-    // See EdDSAEngine.SIGNATURE_ALGORITHM
-    public static final String CURVE_ED25519_SHA512 = "NONEwithEdDSA";
-
+    /**
+     * Key and signature algorithm name for ed25519.
+     */
     public static final String ED25519 = "Ed25519";
 
     /**
@@ -611,28 +610,18 @@ public final class SecurityUtils {
         return support.get().getOpenSSHEDDSAPrivateKeyEntryDecoder();
     }
 
-    public static org.apache.sshd.common.signature.Signature getEDDSASigner() {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (support.isPresent()) {
-            return support.get().getEDDSASigner();
-        }
-
-        throw new UnsupportedOperationException(EDDSA + " Signer not available");
-    }
-
-    public static int getEDDSAKeySize(Key key) {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        return support.map(edDSASupport -> edDSASupport.getEDDSAKeySize(key)).orElse(-1);
-    }
-
     public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        return support.map(edDSASupport -> edDSASupport.compareEDDSAPPublicKeys(k1, k2)).orElse(false);
+        if (k1 == null && k2 == null) {
+            return true;
+        }
+        return k1 != null && k2 != null && EdDSAUtils.equals(k1, k2);
     }
 
     public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        return support.map(edDSASupport -> edDSASupport.compareEDDSAPrivateKeys(k1, k2)).orElse(false);
+        if (k1 == null && k2 == null) {
+            return true;
+        }
+        return k1 != null && k2 != null && EdDSAUtils.equals(k1, k2);
     }
 
     public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
@@ -644,54 +633,6 @@ public final class SecurityUtils {
         return support.get().recoverEDDSAPublicKey(key);
     }
 
-    public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException {
-        if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
-            throw new InvalidKeyException("Unsupported key type: " + keyType);
-        }
-
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
-        }
-
-        return support.get().generateEDDSAPublicKey(seed);
-    }
-
-    public static PrivateKey generateEDDSAPrivateKey(String keyType, byte[] seed) throws GeneralSecurityException, IOException {
-        if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
-            throw new InvalidKeyException("Unsupported key type: " + keyType);
-        }
-
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
-        }
-
-        return support.get().generateEDDSAPrivateKey(seed);
-    }
-
-    public static <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            throw new UnsupportedOperationException(EDDSA + " provider not supported");
-        }
-
-        return support.get().putRawEDDSAPublicKey(buffer, key);
-    }
-
-    public static <B extends Buffer> B putEDDSAKeyPair(B buffer, KeyPair kp) {
-        return putEDDSAKeyPair(buffer, Objects.requireNonNull(kp, "No key pair").getPublic(), kp.getPrivate());
-    }
-
-    public static <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            throw new UnsupportedOperationException(EDDSA + " provider not supported");
-        }
-
-        return support.get().putEDDSAKeyPair(buffer, pubKey, prvKey);
-    }
-
     public static KeyPair extractEDDSAKeyPair(Buffer buffer, String keyType) throws GeneralSecurityException {
         if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
             throw new InvalidKeyException("Unsupported key type: " + keyType);
@@ -779,8 +720,8 @@ public final class SecurityUtils {
     }
 
     public static KeyFactory getKeyFactory(String algorithm) throws GeneralSecurityException {
-        SecurityEntityFactory<KeyFactory> factory
-                = resolveSecurityEntityFactory(KeyFactory.class, algorithm, r -> r.isKeyFactorySupported(algorithm));
+        SecurityEntityFactory<KeyFactory> factory = resolveSecurityEntityFactory(KeyFactory.class, algorithm,
+                r -> r.isKeyFactorySupported(algorithm));
         return factory.getInstance(algorithm);
     }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
index a5f177066..206a1e95a 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
@@ -24,7 +24,6 @@ import java.security.KeyPairGenerator;
 import java.security.Provider;
 import java.security.Security;
 import java.security.Signature;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -105,12 +104,12 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi
 
         if (KeyPairGenerator.class.isAssignableFrom(entityType)
                 || KeyFactory.class.isAssignableFrom(entityType)) {
-            if (Objects.compare(name, SecurityUtils.EDDSA, String.CASE_INSENSITIVE_ORDER) == 0
+            if (SecurityUtils.ED25519.equalsIgnoreCase(name)
                     && SecurityUtils.isNetI2pCryptoEdDSARegistered()) {
                 return false;
             }
         } else if (Signature.class.isAssignableFrom(entityType)) {
-            if (Objects.compare(name, SecurityUtils.CURVE_ED25519_SHA512, String.CASE_INSENSITIVE_ORDER) == 0
+            if (SecurityUtils.ED25519.equalsIgnoreCase(name)
                     && SecurityUtils.isNetI2pCryptoEdDSARegistered()) {
                 return false;
             }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
index ab64ea720..63ddddac3 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
@@ -55,9 +55,9 @@ public class Ed25519PEMResourceKeyParser extends GenericEd25519PEMResourceKeyPar
             throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported");
         }
 
-        EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512);
+        EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(SecurityUtils.ED25519);
         EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
-        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
         return EdDSAPrivateKey.class.cast(factory.generatePrivate(keySpec));
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
index 7fcffd240..3697bdf24 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
@@ -18,16 +18,17 @@
  */
 package org.apache.sshd.common.util.security.eddsa;
 
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
 import java.security.Provider;
 import java.security.Signature;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
+import org.apache.sshd.common.util.security.SecurityEntityFactory;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 import org.apache.sshd.common.util.threads.ThreadUtils;
@@ -36,6 +37,7 @@ import org.apache.sshd.common.util.threads.ThreadUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
+
     public static final String PROVIDER_CLASS = "net.i2p.crypto.eddsa.EdDSASecurityProvider";
     // Do not define a static registrar instance to minimize class loading issues
     private final AtomicReference<Boolean> supportHolder = new AtomicReference<>(null);
@@ -73,9 +75,9 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
 
         if (KeyPairGenerator.class.isAssignableFrom(entityType)
                 || KeyFactory.class.isAssignableFrom(entityType)) {
-            return Objects.compare(name, getName(), String.CASE_INSENSITIVE_ORDER) == 0;
+            return SecurityUtils.ED25519.equalsIgnoreCase(name);
         } else if (Signature.class.isAssignableFrom(entityType)) {
-            return Objects.compare(SecurityUtils.CURVE_ED25519_SHA512, name, String.CASE_INSENSITIVE_ORDER) == 0;
+            return SecurityUtils.ED25519.equalsIgnoreCase(name);
         } else {
             return false;
         }
@@ -98,6 +100,36 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
         return supported.booleanValue();
     }
 
+    @Override
+    public <F> SecurityEntityFactory<F> getFactory(Class<F> entityType) throws ReflectiveOperationException {
+        // Return factories that map the algorithm names to the non-standard ones used by net.i2p.
+        // That way the rest of our code can work with the standard names.
+        if (KeyPairGenerator.class.isAssignableFrom(entityType) || KeyFactory.class.isAssignableFrom(entityType)) {
+            return new DelegatingSecurityEntityFactory<F>(super.getFactory(entityType)) {
+
+                @Override
+                protected String effectiveAlgorithm(String originalAlgorithm) {
+                    if (SecurityUtils.ED25519.equalsIgnoreCase(originalAlgorithm)) {
+                        return "EdDSA";
+                    }
+                    return originalAlgorithm;
+                }
+            };
+        } else if (Signature.class.isAssignableFrom(entityType)) {
+            return new DelegatingSecurityEntityFactory<F>(super.getFactory(entityType)) {
+
+                @Override
+                protected String effectiveAlgorithm(String originalAlgorithm) {
+                    if (SecurityUtils.ED25519.equalsIgnoreCase(originalAlgorithm)) {
+                        return "NONEwithEdDSA";
+                    }
+                    return originalAlgorithm;
+                }
+            };
+        }
+        return super.getFactory(entityType);
+    }
+
     @Override
     public Optional<EdDSASupport> getEdDSASupport() {
         if (!isSupported()) {
@@ -105,4 +137,31 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
         }
         return Optional.of(new NetI2pCryptoEdDSASupport());
     }
+
+    private static abstract class DelegatingSecurityEntityFactory<F> implements SecurityEntityFactory<F> {
+
+        private SecurityEntityFactory<F> delegate;
+
+        DelegatingSecurityEntityFactory(SecurityEntityFactory<F> delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public Class<F> getEntityType() {
+            return delegate.getEntityType();
+        }
+
+        @Override
+        public F getInstance(String algorithm) throws GeneralSecurityException {
+            return delegate.getInstance(effectiveAlgorithm(algorithm));
+        }
+
+        protected abstract String effectiveAlgorithm(String originalAlgorithm);
+
+        @Override
+        public String toString() {
+            return delegate.toString();
+        }
+    }
+
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
index 0c6501f87..2fe0705c6 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
@@ -32,23 +32,20 @@ import net.i2p.crypto.eddsa.EdDSAEngine;
 import net.i2p.crypto.eddsa.EdDSAKey;
 import net.i2p.crypto.eddsa.EdDSAPrivateKey;
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
-import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
 import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
-import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
 import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
 import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public final class EdDSASecurityProviderUtils {
     // See EdDSANamedCurveTable
-    public static final String CURVE_ED25519_SHA512 = "Ed25519";
     public static final int KEY_SIZE = EdDSASupport.KEY_SIZE;
 
     private EdDSASecurityProviderUtils() {
@@ -94,7 +91,7 @@ public final class EdDSASecurityProviderUtils {
 
         EdDSAPrivateKey prvKey = (EdDSAPrivateKey) key;
         EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(prvKey.getAbyte(), prvKey.getParams());
-        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
         return EdDSAPublicKey.class.cast(factory.generatePublic(keySpec));
     }
 
@@ -103,14 +100,6 @@ public final class EdDSASecurityProviderUtils {
         return new SignatureEd25519();
     }
 
-    public static boolean isEDDSAKeyFactoryAlgorithm(String algorithm) {
-        return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
-    }
-
-    public static boolean isEDDSAKeyPairGeneratorAlgorithm(String algorithm) {
-        return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
-    }
-
     public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
         ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
         return Ed25519PublicKeyDecoder.INSTANCE;
@@ -157,10 +146,7 @@ public final class EdDSASecurityProviderUtils {
             throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported");
         }
 
-        EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
-        EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(seed, params);
-        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
-        return factory.generatePublic(keySpec);
+        return EdDSAUtils.getPublicKey(seed);
     }
 
     public static PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException {
@@ -168,25 +154,6 @@ public final class EdDSASecurityProviderUtils {
             throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported");
         }
 
-        EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
-        EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
-        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
-        return factory.generatePrivate(keySpec);
-    }
-
-    public static <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
-        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
-        EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, EdDSAPublicKey.class, "Not an EDDSA public key: %s", key);
-        byte[] seed = Ed25519PublicKeyDecoder.getSeedValue(edKey);
-        ValidateUtils.checkNotNull(seed, "No seed extracted from key: %s", edKey.getA());
-        buffer.putBytes(seed);
-        return buffer;
-    }
-
-    public static <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
-        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
-        ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an EDDSA public key: %s", pubKey);
-        ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey);
-        throw new UnsupportedOperationException("Full SSHD-440 implementation N/A");
+        return EdDSAUtils.getPrivateKey(seed);
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
index 3e2eae767..da8a64eee 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
@@ -20,17 +20,11 @@
 package org.apache.sshd.common.util.security.eddsa;
 
 import java.security.GeneralSecurityException;
-import java.security.Key;
 import java.security.PrivateKey;
-import java.security.PublicKey;
 
-import net.i2p.crypto.eddsa.EdDSAPrivateKey;
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
-import org.apache.sshd.common.signature.Signature;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 
 public class NetI2pCryptoEdDSASupport implements EdDSASupport {
@@ -49,53 +43,9 @@ public class NetI2pCryptoEdDSASupport implements EdDSASupport {
         return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
     }
 
-    @Override
-    public Signature getEDDSASigner() {
-        return EdDSASecurityProviderUtils.getEDDSASignature();
-    }
-
-    @Override
-    public int getEDDSAKeySize(Key key) {
-        return EdDSASecurityProviderUtils.getEDDSAKeySize(key);
-    }
-
-    @Override
-    public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
-        return EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2);
-    }
-
-    @Override
-    public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
-        return EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2);
-    }
-
     @Override
     public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
         return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key);
     }
 
-    @Override
-    public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException {
-        return (EdDSAPublicKey) EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed);
-    }
-
-    @Override
-    public EdDSAPrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException {
-        return Ed25519PEMResourceKeyParser.generateEdDSAPrivateKey(seed);
-    }
-
-    @Override
-    public <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
-        return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key);
-    }
-
-    @Override
-    public <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
-        return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey);
-    }
-
-    @Override
-    public String getKeyFactoryAlgorithm() {
-        return SecurityUtils.EDDSA;
-    }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
index 1c49809ea..7823acc31 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
@@ -18,16 +18,35 @@
  */
 package org.apache.sshd.common.util.security.eddsa;
 
-import net.i2p.crypto.eddsa.EdDSAEngine;
-import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519;
+import java.util.Map;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.signature.AbstractSignature;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
- * An implementation of {@link GenericSignatureEd25519} tied to the {@code net.i2p.crypto} EdDSA security provider.
+ * An ed25519 signature.
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class SignatureEd25519 extends GenericSignatureEd25519 {
+public class SignatureEd25519 extends AbstractSignature {
+
     public SignatureEd25519() {
-        super(EdDSAEngine.SIGNATURE_ALGORITHM);
+        super(SecurityUtils.ED25519, KeyPairProvider.SSH_ED25519);
+    }
+
+    @Override
+    public boolean verify(SessionContext session, byte[] sig) throws Exception {
+        byte[] data = sig;
+        Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase);
+        if (encoding != null) {
+            String keyType = encoding.getKey();
+            ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType);
+            data = encoding.getValue();
+        }
+
+        return doVerify(data);
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
index cd8baa249..ea679e906 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
@@ -19,32 +19,18 @@
 
 package org.apache.sshd.common.util.security.eddsa.bouncycastle;
 
-import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyFactory;
 import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.spec.PKCS8EncodedKeySpec;
 
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
-import org.apache.sshd.common.signature.Signature;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder;
 import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder;
-import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
-import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
-import org.bouncycastle.jcajce.interfaces.EdDSAKey;
 import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
 import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
-import org.bouncycastle.jcajce.spec.RawEncodedKeySpec;
 
 public class BouncyCastleEdDSASupport implements EdDSASupport {
 
@@ -62,34 +48,6 @@ public class BouncyCastleEdDSASupport implements EdDSASupport {
         return new GenericOpenSSHEd25519PrivateKeyEntryDecoder(this);
     }
 
-    @Override
-    public Signature getEDDSASigner() {
-        return new GenericSignatureEd25519(SecurityUtils.EDDSA);
-    }
-
-    @Override
-    public int getEDDSAKeySize(Key key) {
-        return key instanceof EdDSAKey ? KEY_SIZE : -1;
-    }
-
-    @Override
-    public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
-        if (!(k1 instanceof EdDSAPublicKey) || !(k2 instanceof EdDSAPublicKey)) {
-            return false;
-        }
-
-        return k1.equals(k2);
-    }
-
-    @Override
-    public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
-        if (!(k1 instanceof EdDSAPrivateKey) || !(k2 instanceof EdDSAPrivateKey)) {
-            return false;
-        }
-
-        return k1.equals(k2);
-    }
-
     @Override
     public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
         if (!(key instanceof EdDSAPrivateKey)) {
@@ -99,37 +57,4 @@ public class BouncyCastleEdDSASupport implements EdDSASupport {
         return edDSAKey.getPublicKey();
     }
 
-    @Override
-    public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException {
-        RawEncodedKeySpec keySpec = new RawEncodedKeySpec(seed);
-        KeyFactory factory = SecurityUtils.getKeyFactory(getKeyFactoryAlgorithm());
-        return (EdDSAPublicKey) factory.generatePublic(keySpec);
-    }
-
-    @Override
-    public EdDSAPrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException {
-        Ed25519PrivateKeyParameters parameters = new Ed25519PrivateKeyParameters(seed);
-        PrivateKeyInfo info = PrivateKeyInfoFactory.createPrivateKeyInfo(parameters);
-        KeyFactory factory = SecurityUtils.getKeyFactory(getKeyFactoryAlgorithm());
-        return (EdDSAPrivateKey) factory.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded()));
-    }
-
-    @Override
-    public <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
-        EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, EdDSAPublicKey.class, "Not an EDDSA public key: %s", key);
-        buffer.putBytes(edKey.getPointEncoding());
-        return buffer;
-    }
-
-    @Override
-    public <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
-        ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an EDDSA public key: %s", pubKey);
-        ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey);
-        throw new UnsupportedOperationException("Full SSHD-440 implementation N/A");
-    }
-
-    @Override
-    public String getKeyFactoryAlgorithm() {
-        return SecurityUtils.ED25519;
-    }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
index 1ccbd6b41..02ff2e18c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
@@ -22,17 +22,12 @@ package org.apache.sshd.common.util.security.eddsa.generic;
 import java.io.IOException;
 import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.Key;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.signature.Signature;
-import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.io.der.ASN1Object;
 import org.apache.sshd.common.util.io.der.ASN1Type;
 import org.apache.sshd.common.util.io.der.DERParser;
@@ -78,7 +73,7 @@ public interface EdDSASupport {
                 throw new StreamCorruptedException("Mismatched key data container type: " + objType);
             }
 
-            return SecurityUtils.generateEDDSAPrivateKey(KeyPairProvider.SSH_ED25519, obj.getValue());
+            return EdDSAUtils.getPrivateKey(obj.getValue());
         }
     }
 
@@ -92,95 +87,10 @@ public interface EdDSASupport {
      */
     PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder();
 
-    /**
-     * @return the signature implementation associated with the security provider.
-     */
-    Signature getEDDSASigner();
-
-    /**
-     * @param  key the key to get the size of.
-     * @return     the size of the key if it is an EdDSA key, -1 otherwise.
-     */
-    int getEDDSAKeySize(Key key);
-
-    /**
-     * @param  k1 the first key
-     * @param  k2 the second key
-     * @return    {@code true} if both keys are instances of the public key type associated with the security provider
-     *            and they are equal.
-     */
-    boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2);
-
-    /**
-     * @param  k1 the first key
-     * @param  k2 the second key
-     * @return    {@code true} if both keys are instances of the private key type associated with the security provider
-     *            and they are equal.
-     */
-    boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2);
-
     /**
      * @param  key the private key
      * @return     the public key associated with the private key.
      */
     PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException;
 
-    /**
-     * @param  seed the raw public key bytes
-     * @return      the associated public key
-     */
-    PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException;
-
-    /**
-     * @param  seed the raw private key bytes
-     * @return      the associated private key
-     */
-    PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException;
-
-    /**
-     * @param  buffer the buffer to insert the public key into
-     * @param  key    the public key to be inserted into the buffer
-     * @return        the buffer that was passed in
-     * @param  <B>    type of the buffer
-     */
-    <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key);
-
-    /**
-     * @param  buffer the buffer to insert the keys into
-     * @param  pubKey the public key to be inserted into the buffer
-     * @param  prvKey the private key to be inserted into the buffer
-     * @return        the buffer that was passed in
-     * @param  <B>    type of the buffer
-     */
-    <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey);
-
-    /**
-     * @param  publicKey the public key
-     * @return           the raw public key bytes associated with the key
-     */
-    default byte[] getPublicKeyData(PublicKey publicKey) {
-        try {
-            return EdDSAUtils.getBytes(publicKey);
-        } catch (InvalidKeyException e) {
-            throw new IllegalArgumentException(e);
-        }
-    };
-
-    /**
-     * @param  privateKey the private key
-     * @return            the raw private key bytes associated with the key
-     */
-    default byte[] getPrivateKeyData(PrivateKey privateKey) throws IOException {
-        try {
-            return EdDSAUtils.getBytes(privateKey);
-        } catch (InvalidKeyException e) {
-            throw new IOException(e);
-        }
-    }
-
-    /**
-     * @return the algorithm name used by the provider's {@link java.security.KeyFactory}.
-     */
-    String getKeyFactoryAlgorithm();
-
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
index 5aa82508c..61460c46c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
@@ -19,7 +19,9 @@
 package org.apache.sshd.common.util.security.eddsa.generic;
 
 import java.io.IOException;
+import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
+import java.security.KeyFactory;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.spec.KeySpec;
@@ -28,10 +30,11 @@ import java.security.spec.X509EncodedKeySpec;
 import java.util.Arrays;
 
 import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
- * Utilities to extract the raw key bytes from ed25519 or ed448 public keys, in a manner that is independent of the
- * actual concrete key implementation classes.
+ * Utilities to extract the raw key bytes from ed25519 or ed448 keys or to construct such keys from the raw key bytes,
+ * in a manner that is independent of the actual concrete key implementation classes.
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
@@ -103,18 +106,19 @@ public final class EdDSAUtils {
     /**
      * Retrieves the raw key bytes from an ed25519 or ed448 {@link PublicKey}.
      *
-     * @param  key                 {@link PublicKey} to get the bytes of
-     * @return                     the raw key bytes
-     * @throws InvalidKeyException if the key is not an ed25519 or ed448 key, or if it doesn't use X.509 encoding
+     * @param  key                      {@link PublicKey} to get the bytes of
+     * @return                          the raw key bytes
+     * @throws IllegalArgumentException if the key is not an ed25519 or ed448 key, or if it doesn't use X.509 encoding
      */
-    public static byte[] getBytes(PublicKey key) throws InvalidKeyException {
+    public static byte[] getBytes(PublicKey key) throws IllegalArgumentException {
         // Extract the public key bytes from the X.509 encoding (last n bytes, depending on the OID).
         if (!"X.509".equalsIgnoreCase(key.getFormat())) {
-            throw new InvalidKeyException("Cannot extract public key bytes from a non-X.509 encoding");
+            throw new IllegalArgumentException("Cannot extract public key bytes from a non-X.509 encoding");
         }
         byte[] encoded = key.getEncoded();
         if (encoded == null) {
-            throw new InvalidKeyException("Public key " + key.getClass().getCanonicalName() + " does not support encoding");
+            throw new IllegalArgumentException(
+                    "Public key " + key.getClass().getCanonicalName() + " does not support encoding");
         }
         int n;
         if (encoded.length == ED25519_LENGTH + ED25519_X509_PREFIX.length && startsWith(encoded, ED25519_X509_PREFIX)) {
@@ -122,7 +126,7 @@ public final class EdDSAUtils {
         } else if (encoded.length == ED448_LENGTH + ED448_X509_PREFIX.length && startsWith(encoded, ED448_X509_PREFIX)) {
             n = ED448_LENGTH;
         } else {
-            throw new InvalidKeyException("Public key is neither ed25519 nor ed448");
+            throw new IllegalArgumentException("Public key is neither ed25519 nor ed448");
         }
         return Arrays.copyOfRange(encoded, encoded.length - n, encoded.length);
     }
@@ -130,18 +134,19 @@ public final class EdDSAUtils {
     /**
      * Retrieves the raw key bytes from an ed25519 or ed448 {@link PrivateKey}.
      *
-     * @param  key                 {@link PrivateKey} to get the bytes of
-     * @return                     the raw key bytes
-     * @throws InvalidKeyException if the key is not an ed25519 or ed448 key, or if it doesn't use PKCS#8 encoding
+     * @param  key                      {@link PrivateKey} to get the bytes of
+     * @return                          the raw key bytes
+     * @throws IllegalArgumentException if the key is not an ed25519 or ed448 key, or if it doesn't use PKCS#8 encoding
      */
-    public static byte[] getBytes(PrivateKey key) throws InvalidKeyException {
+    public static byte[] getBytes(PrivateKey key) throws IllegalArgumentException {
         // Extract the private key bytes from the PKCS#8 encoding.
         if (!"PKCS#8".equalsIgnoreCase(key.getFormat())) {
-            throw new InvalidKeyException("Cannot extract private key bytes from a non-PKCS#8 encoding");
+            throw new IllegalArgumentException("Cannot extract private key bytes from a non-PKCS#8 encoding");
         }
         byte[] encoded = key.getEncoded();
         if (encoded == null) {
-            throw new InvalidKeyException("Private key " + key.getClass().getCanonicalName() + " does not support encoding");
+            throw new IllegalArgumentException(
+                    "Private key " + key.getClass().getCanonicalName() + " does not support encoding");
         }
         try {
             return asn1Parse(encoded);
@@ -180,13 +185,13 @@ public final class EdDSAUtils {
      * CurvePrivateKey ::= OCTET STRING
      * </pre>
      *
-     * @param  encoded             encoded private key to extract the private key bytes from
-     * @return                     the extracted private key bytes
-     * @throws InvalidKeyException if the private key cannot be extracted
-     * @see                        <a href="https://tools.ietf.org/html/rfc5958">RFC 5958</a>
-     * @see                        <a href="https://tools.ietf.org/html/rfc8410">RFC 8410</a>
+     * @param  encoded                  encoded private key to extract the private key bytes from
+     * @return                          the extracted private key bytes
+     * @throws IllegalArgumentException if the private key cannot be extracted
+     * @see                             <a href="https://tools.ietf.org/html/rfc5958">RFC 5958</a>
+     * @see                             <a href="https://tools.ietf.org/html/rfc8410">RFC 8410</a>
      */
-    private static byte[] asn1Parse(byte[] encoded) throws InvalidKeyException {
+    private static byte[] asn1Parse(byte[] encoded) throws IllegalArgumentException {
         byte[] privateKey = null;
         try (DERParser byteParser = new DERParser(encoded);
              DERParser oneAsymmetricKey = byteParser.readObject().createParser()) {
@@ -199,7 +204,7 @@ public final class EdDSAUtils {
                 } else if (arrayEq(ED448_OID, oid)) {
                     n = ED448_LENGTH;
                 } else {
-                    throw new InvalidKeyException("Private key is neither ed25519 nor ed448");
+                    throw new IllegalArgumentException("Private key is neither ed25519 nor ed448");
                 }
             }
             privateKey = oneAsymmetricKey.readObject().getValue();
@@ -207,7 +212,7 @@ public final class EdDSAUtils {
             return Arrays.copyOfRange(privateKey, privateKey.length - n, privateKey.length);
             // Depending on the version there may be optional stuff following, but we don't care about that.
         } catch (IOException e) {
-            throw new InvalidKeyException("Cannot parse EdDSA private key", e);
+            throw new IllegalArgumentException("Cannot parse EdDSA private key", e);
         } finally {
             if (privateKey != null) {
                 Arrays.fill(privateKey, (byte) 0);
@@ -287,4 +292,64 @@ public final class EdDSAUtils {
         throw new InvalidKeyException("Private key data is neither ed25519 nor ed448");
     }
 
+    /**
+     * Creates a {@link PublicKey} from the raw key bytes of an ed25519 or ed448 key.
+     *
+     * @param  keyData                  the raw key bytes
+     * @return                          the {@link PublicKey}
+     * @throws GeneralSecurityException if the key cannot be created
+     */
+    public static PublicKey getPublicKey(byte[] keyData) throws GeneralSecurityException {
+        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
+        return factory.generatePublic(createPublicKeySpec(keyData));
+    }
+
+    /**
+     * Creates a {@link PrivateKey} from the raw key bytes of an ed25519 or ed448 key.
+     *
+     * @param  keyData                  the raw key bytes
+     * @return                          the {@link PrivateKey}
+     * @throws GeneralSecurityException if the key cannot be created
+     */
+    public static PrivateKey getPrivateKey(byte[] keyData) throws GeneralSecurityException {
+        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
+        return factory.generatePrivate(createPrivateKeySpec(keyData));
+    }
+
+    /**
+     * Compares two ed25519 or two ed448 {@link PublicKey}s.
+     *
+     * @param  k1                       first {@link PublicKey}
+     * @param  k2                       second {@link PublicKey}
+     * @return                          if the two keys are equal
+     * @throws IllegalArgumentException if one of the keys is neither an ed25519 nor an ed448 key
+     */
+    public static boolean equals(PublicKey k1, PublicKey k2) throws IllegalArgumentException {
+        return arrayEq(getBytes(k1), getBytes(k2));
+    }
+
+    /**
+     * Compares two ed25519 or two ed448 {@link PrivateKey}s.
+     *
+     * @param  k1                       first {@link PrivateKey}
+     * @param  k2                       second {@link PrivateKey}
+     * @return                          if the two keys are equal
+     * @throws IllegalArgumentException if one of the keys is neither an ed25519 nor an ed448 key
+     */
+    public static boolean equals(PrivateKey k1, PrivateKey k2) throws IllegalArgumentException {
+        byte[] k1Data = null;
+        byte[] k2Data = null;
+        try {
+            k1Data = getBytes(k1);
+            k2Data = getBytes(k2);
+            return arrayEq(k1Data, k2Data);
+        } finally {
+            if (k1Data != null) {
+                Arrays.fill(k1Data, (byte) 0);
+            }
+            if (k2Data != null) {
+                Arrays.fill(k2Data, (byte) 0);
+            }
+        }
+    }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java
index 9e4c64511..7ee7c47f2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java
@@ -64,7 +64,7 @@ public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPa
     public static final GenericEd25519PEMResourceKeyParser INSTANCE = new GenericEd25519PEMResourceKeyParser();
 
     public GenericEd25519PEMResourceKeyParser() {
-        super(SecurityUtils.EDDSA, ED25519_OID, BEGINNERS, ENDERS);
+        super(SecurityUtils.ED25519, ED25519_OID, BEGINNERS, ENDERS);
     }
 
     @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
index f3294821a..084bfe0c9 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
@@ -50,21 +50,20 @@ public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecode
 
     @Override
     public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
-        return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+        return SecurityUtils.getKeyPairGenerator(SecurityUtils.ED25519);
     }
 
     @Override
     public String encodePublicKey(OutputStream s, PublicKey key) throws IOException {
         Objects.requireNonNull(key, "No public key provided");
         KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519);
-        byte[] seed = edDSASupport.getPublicKeyData(key);
-        KeyEntryResolver.writeRLEBytes(s, seed);
+        KeyEntryResolver.writeRLEBytes(s, EdDSAUtils.getBytes(key));
         return KeyPairProvider.SSH_ED25519;
     }
 
     @Override
     public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
-        return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+        return SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
     }
 
     @Override
@@ -72,7 +71,7 @@ public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecode
             SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN);
-        return edDSASupport.generateEDDSAPublicKey(seed);
+        return EdDSAUtils.getPublicKey(seed);
     }
 
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
index 62dec443b..e1285fb55 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
@@ -96,10 +96,10 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivate
             }
 
             byte[] sk = Arrays.copyOf(keypair, SK_SIZE);
-            PrivateKey privateKey = edDSASupport.generateEDDSAPrivateKey(sk);
+            PrivateKey privateKey = EdDSAUtils.getPrivateKey(sk);
 
             // we can now verify the generated pk matches the one we read
-            if (!Arrays.equals(edDSASupport.getPublicKeyData(recoverPublicKey(privateKey)), pk)) {
+            if (!Arrays.equals(EdDSAUtils.getBytes(recoverPublicKey(privateKey)), pk)) {
                 throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk.");
             }
 
@@ -120,18 +120,24 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivate
         // we are expected to write the following arrays (type:size):
         // [pk:32], [sk:32,pk:32]
 
-        byte[] sk = edDSASupport.getPrivateKeyData(key);
-        byte[] pk = edDSASupport.getPublicKeyData(pubKey);
-
-        Objects.requireNonNull(sk, "No seed");
+        byte[] sk = null;
+        try {
+            byte[] pk = EdDSAUtils.getBytes(pubKey);
+            sk = EdDSAUtils.getBytes(key);
 
-        byte[] keypair = new byte[KEYPAIR_SIZE];
-        System.arraycopy(sk, 0, keypair, 0, SK_SIZE);
-        System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE);
+            Objects.requireNonNull(sk, "No seed");
 
-        KeyEntryResolver.writeRLEBytes(s, pk);
-        KeyEntryResolver.writeRLEBytes(s, keypair);
+            byte[] keypair = new byte[KEYPAIR_SIZE];
+            System.arraycopy(sk, 0, keypair, 0, SK_SIZE);
+            System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE);
 
+            KeyEntryResolver.writeRLEBytes(s, pk);
+            KeyEntryResolver.writeRLEBytes(s, keypair);
+        } finally {
+            if (sk != null) {
+                Arrays.fill(sk, (byte) 0);
+            }
+        }
         return KeyPairProvider.SSH_ED25519;
     }
 
@@ -147,11 +153,11 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivate
 
     @Override
     public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
-        return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+        return SecurityUtils.getKeyPairGenerator(SecurityUtils.ED25519);
     }
 
     @Override
     public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
-        return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+        return SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java
deleted file mode 100644
index 943c473d5..000000000
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.common.util.security.eddsa.generic;
-
-import java.util.Map;
-
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.signature.AbstractSignature;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class GenericSignatureEd25519 extends AbstractSignature {
-
-    public GenericSignatureEd25519(String algorithm) {
-        super(algorithm, KeyPairProvider.SSH_ED25519);
-    }
-
-    @Override
-    public boolean verify(SessionContext session, byte[] sig) throws Exception {
-        byte[] data = sig;
-        Map.Entry<String, byte[]> encoding
-                = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase);
-        if (encoding != null) {
-            String keyType = encoding.getKey();
-            ValidateUtils.checkTrue(
-                    KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType);
-            data = encoding.getValue();
-        }
-
-        return doVerify(data);
-    }
-}
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
index febcf9e33..761c0a4bc 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
@@ -61,7 +61,7 @@ class KeyRandomArtTest extends JUnitTestSupport {
 
         if (SecurityUtils.isEDDSACurveSupported()) {
             for (int keySize : ED25519_SIZES) {
-                params.add(new Object[] { SecurityUtils.EDDSA, keySize });
+                params.add(new Object[] { SecurityUtils.ED25519, keySize });
             }
         }
         return params;
@@ -78,7 +78,12 @@ class KeyRandomArtTest extends JUnitTestSupport {
         KeyPair keyPair = CommonTestSupportUtils.generateKeyPair(algorithm, keySize);
         KEYS.add(keyPair);
         KeyRandomArt art = new KeyRandomArt(keyPair.getPublic());
-        assertEquals(algorithm, art.getAlgorithm(), "Mismatched algorithm");
+        if (SecurityUtils.ED25519.equals(algorithm)) {
+            assertTrue(SecurityUtils.ED25519.equals(art.getAlgorithm()) || SecurityUtils.EDDSA.equals(art.getAlgorithm()),
+                    "Mismatched algorithm");
+        } else {
+            assertEquals(algorithm, art.getAlgorithm(), "Mismatched algorithm");
+        }
         assertEquals(keySize, art.getKeySize(), "Mismatched key size");
 
         String s = art.toString();
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
index d81e4347b..b162a425a 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
@@ -72,7 +72,7 @@ class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
             params.add(new Object[] { KeyUtils.EC_ALGORITHM, curve.getKeySize() });
         }
         if (SecurityUtils.isEDDSACurveSupported()) {
-            params.add(new Object[] { SecurityUtils.EDDSA, 0 });
+            params.add(new Object[] { SecurityUtils.ED25519, 0 });
         }
         return params;
     }
@@ -149,7 +149,7 @@ class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
         // https://github.com/bcgit/bc-java/issues/1238#issuecomment-1263162809 and has a
         // different password. Unknown how it was generated; openssl always writes the
         // RFC 5958 EncryptedPrivateKeyInfo.
-        String password = "eddsa".equalsIgnoreCase(algorithm) ? "Vjvyhfngz0MCUs$kwOF0" : "test";
+        String password = "ed25519".equalsIgnoreCase(algorithm) ? "Vjvyhfngz0MCUs$kwOF0" : "test";
         Collection<KeyPair> pairs = PEMResourceParserUtils.PROXY.loadKeyPairs(null, url, (s, r, i) -> password);
         assertEquals(1, GenericUtils.size(pairs), "Mismatched extract keys count");
         validateKeyPairSignable(algorithm + "/" + keySize, GenericUtils.head(pairs));
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProviderTest.java
index dd9cb02a0..58f9aeede 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProviderTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProviderTest.java
@@ -85,7 +85,7 @@ class BouncyCastleGeneratorHostKeyProviderTest extends JUnitTestSupport {
             assertNotNull(curve, "No curve for key size=" + keySize);
             expected = KeyUtils.generateKeyPair(curve.getKeyType(), curve.getKeySize());
         } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyType)) {
-            KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+            KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.ED25519);
             expected = g.generateKeyPair();
         } else {
             throw new InvalidKeyException("Unsupported key type: " + keyType);
@@ -96,7 +96,7 @@ class BouncyCastleGeneratorHostKeyProviderTest extends JUnitTestSupport {
         if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) {
             keyAlgorithm = KeyUtils.EC_ALGORITHM;
         } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyAlgorithm)) {
-            keyAlgorithm = SecurityUtils.EDDSA;
+            keyAlgorithm = SecurityUtils.ED25519;
         }
 
         Path dir = getTempTargetFolder();
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java
index 32d51fc6c..572c7ea32 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java
@@ -27,7 +27,6 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.Signature;
 
-import net.i2p.crypto.eddsa.EdDSAEngine;
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
@@ -57,7 +56,7 @@ class EDDSAProviderTest extends JUnitTestSupport {
     @BeforeAll
     static void checkProviderSupported() throws GeneralSecurityException {
         Assumptions.assumeTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
-        KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+        KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.ED25519);
         assertNotNull(g, "No generator instance");
 
         keyPair = g.generateKeyPair();
@@ -65,7 +64,8 @@ class EDDSAProviderTest extends JUnitTestSupport {
 
         PublicKey pubKey = keyPair.getPublic();
         assertNotNull(pubKey, "No public key");
-        assertEquals(SecurityUtils.EDDSA, pubKey.getAlgorithm(), "Mismatched public key algorithm");
+        String algo = pubKey.getAlgorithm();
+        assertTrue(SecurityUtils.EDDSA.equals(algo) || SecurityUtils.ED25519.equals(algo), "Mismatched public key algorithm");
         assertEquals(KeyPairProvider.SSH_ED25519, KeyUtils.getKeyType(pubKey), "Mismatched public key type");
 
         PrivateKey prvKey = keyPair.getPrivate();
@@ -76,7 +76,7 @@ class EDDSAProviderTest extends JUnitTestSupport {
 
     @Test
     void signature() throws GeneralSecurityException {
-        Signature s = SecurityUtils.getSignature(EdDSAEngine.SIGNATURE_ALGORITHM);
+        Signature s = SecurityUtils.getSignature(SecurityUtils.ED25519);
         assertNotNull(s, "No signature instance");
         s.initSign(keyPair.getPrivate());
 
@@ -84,7 +84,7 @@ class EDDSAProviderTest extends JUnitTestSupport {
         s.update(data);
         byte[] signed = s.sign();
 
-        s = SecurityUtils.getSignature(EdDSAEngine.SIGNATURE_ALGORITHM);
+        s = SecurityUtils.getSignature(SecurityUtils.ED25519);
         s.initVerify(keyPair.getPublic());
         s.update(data);
         assertTrue(s.verify(signed), "Failed to verify");
@@ -114,7 +114,8 @@ class EDDSAProviderTest extends JUnitTestSupport {
 
     private void testPublicKeyRecovery(PublicKey pubKey) throws IOException, GeneralSecurityException {
         assertNotNull(pubKey, "No public key generated");
-        assertEquals(SecurityUtils.EDDSA, pubKey.getAlgorithm(), "Mismatched public key algorithm");
+        String algo = pubKey.getAlgorithm();
+        assertTrue(SecurityUtils.EDDSA.equals(algo) || SecurityUtils.ED25519.equals(algo), "Mismatched public key algorithm");
 
         ByteArrayBuffer buf = new ByteArrayBuffer();
         buf.putRawPublicKey(pubKey);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
index 673644668..a7deabcd0 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
@@ -27,14 +27,12 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.spec.KeySpec;
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.security.SecurityProviderRegistrar;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
@@ -53,7 +51,7 @@ import org.junit.jupiter.params.provider.MethodSource;
  */
 @TestMethodOrder(MethodName.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests
 @Tag("NoIoTestCase")
-class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends JUnitTestSupport {
+class Ed25519VectorsTest extends JUnitTestSupport {
 
     private byte[] prvBytes;
     private PrivateKey privateKey;
@@ -66,132 +64,111 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
             String name, EdDSASupport support, String prvKey, String pubKey, String msg, String signature)
             throws GeneralSecurityException, IOException {
         prvBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, prvKey);
-        privateKey = support.generateEDDSAPrivateKey(prvBytes.clone());
+        privateKey = EdDSAUtils.getPrivateKey(prvBytes.clone());
         pubBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, pubKey);
-        publicKey = support.generateEDDSAPublicKey(pubBytes.clone());
+        publicKey = EdDSAUtils.getPublicKey(pubBytes.clone());
         msgBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, msg);
         expSignature = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, signature);
     }
 
     static List<Object[]> parameters() {
         List<Object[]> parameters = new ArrayList<>();
-        Map<String, EdDSASupport> supportedSecurityProviders = new LinkedHashMap<>();
-        supportedSecurityProviders.put(SecurityUtils.EDDSA, new NetI2pCryptoEdDSASupport());
-        supportedSecurityProviders.put(SecurityUtils.BOUNCY_CASTLE, new BouncyCastleEdDSASupport());
-        for (Map.Entry<String, EdDSASupport> entry : supportedSecurityProviders.entrySet()) {
-            String supportClassName = entry.getValue().getClass().getSimpleName();
-            parameters.add(new Object[] {
-                    supportClassName + " TEST1 - empty message",
-                    entry.getValue(),
-                    entry.getKey(),
-                    "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
-                    "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
-                    "",
-                    "e5564300c360ac729086e2cc806e828a"
-                        + "84877f1eb8e5d974d873e06522490155"
-                        + "5fb8821590a33bacc61e39701cf9b46b"
-                        + "d25bf5f0595bbe24655141438e7a100b"
-            });
-            parameters.add(new Object[] {
-                    supportClassName + " TEST2 - one byte",
-                    entry.getValue(),
-                    entry.getKey(),
-                    "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
-                    "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
-                    "72",
-                    "92a009a9f0d4cab8720e820b5f642540"
-                          + "a2b27b5416503f8fb3762223ebdb69da"
-                          + "085ac1e43e15996e458f3613d0f11d8c"
-                          + "387b2eaeb4302aeeb00d291612bb0c00"
-            });
-            parameters.add(new Object[] {
-                    supportClassName + " TEST3 - 2 bytes",
-                    entry.getValue(),
-                    entry.getKey(),
-                    "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
-                    "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
-                    "af82",
-                    "6291d657deec24024827e69c3abe01a3"
-                            + "0ce548a284743a445e3680d7db5ac3ac"
-                            + "18ff9b538d16f290ae67f760984dc659"
-                            + "4a7c15e9716ed28dc027beceea1ec40a"
-            });
-            parameters.add(new Object[] {
-                    supportClassName + " TEST1024 - large message",
-                    entry.getValue(),
-                    entry.getKey(),
-                    "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5",
-                    "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e",
-                    "08b8b2b733424243760fe426a4b54908"
-                                                                                        + "632110a66c2f6591eabd3345e3e4eb98"
-                                                                                        + "fa6e264bf09efe12ee50f8f54e9f77b1"
-                                                                                        + "e355f6c50544e23fb1433ddf73be84d8"
-                                                                                        + "79de7c0046dc4996d9e773f4bc9efe57"
-                                                                                        + "38829adb26c81b37c93a1b270b20329d"
-                                                                                        + "658675fc6ea534e0810a4432826bf58c"
-                                                                                        + "941efb65d57a338bbd2e26640f89ffbc"
-                                                                                        + "1a858efcb8550ee3a5e1998bd177e93a"
-                                                                                        + "7363c344fe6b199ee5d02e82d522c4fe"
-                                                                                        + "ba15452f80288a821a579116ec6dad2b"
-                                                                                        + "3b310da903401aa62100ab5d1a36553e"
-                                                                                        + "06203b33890cc9b832f79ef80560ccb9"
-                                                                                        + "a39ce767967ed628c6ad573cb116dbef"
-                                                                                        + "efd75499da96bd68a8a97b928a8bbc10"
-                                                                                        + "3b6621fcde2beca1231d206be6cd9ec7"
-                                                                                        + "aff6f6c94fcd7204ed3455c68c83f4a4"
-                                                                                        + "1da4af2b74ef5c53f1d8ac70bdcb7ed1"
-                                                                                        + "85ce81bd84359d44254d95629e9855a9"
-                                                                                        + "4a7c1958d1f8ada5d0532ed8a5aa3fb2"
-                                                                                        + "d17ba70eb6248e594e1a2297acbbb39d"
-                                                                                        + "502f1a8c6eb6f1ce22b3de1a1f40cc24"
-                                                                                        + "554119a831a9aad6079cad88425de6bd"
-                                                                                        + "e1a9187ebb6092cf67bf2b13fd65f270"
-                                                                                        + "88d78b7e883c8759d2c4f5c65adb7553"
-                                                                                        + "878ad575f9fad878e80a0c9ba63bcbcc"
-                                                                                        + "2732e69485bbc9c90bfbd62481d9089b"
-                                                                                        + "eccf80cfe2df16a2cf65bd92dd597b07"
-                                                                                        + "07e0917af48bbb75fed413d238f5555a"
-                                                                                        + "7a569d80c3414a8d0859dc65a46128ba"
-                                                                                        + "b27af87a71314f318c782b23ebfe808b"
-                                                                                        + "82b0ce26401d2e22f04d83d1255dc51a"
-                                                                                        + "ddd3b75a2b1ae0784504df543af8969b"
-                                                                                        + "e3ea7082ff7fc9888c144da2af58429e"
-                                                                                        + "c96031dbcad3dad9af0dcbaaaf268cb8"
-                                                                                        + "fcffead94f3c7ca495e056a9b47acdb7"
-                                                                                        + "51fb73e666c6c655ade8297297d07ad1"
-                                                                                        + "ba5e43f1bca32301651339e22904cc8c"
-                                                                                        + "42f58c30c04aafdb038dda0847dd988d"
-                                                                                        + "cda6f3bfd15c4b4c4525004aa06eeff8"
-                                                                                        + "ca61783aacec57fb3d1f92b0fe2fd1a8"
-                                                                                        + "5f6724517b65e614ad6808d6f6ee34df"
-                                                                                        + "f7310fdc82aebfd904b01e1dc54b2927"
-                                                                                        + "094b2db68d6f903b68401adebf5a7e08"
-                                                                                        + "d78ff4ef5d63653a65040cf9bfd4aca7"
-                                                                                        + "984a74d37145986780fc0b16ac451649"
-                                                                                        + "de6188a7dbdf191f64b5fc5e2ab47b57"
-                                                                                        + "f7f7276cd419c17a3ca8e1b939ae49e4"
-                                                                                        + "88acba6b965610b5480109c8b17b80e1"
-                                                                                        + "b7b750dfc7598d5d5011fd2dcc5600a3"
-                                                                                        + "2ef5b52a1ecc820e308aa342721aac09"
-                                                                                        + "43bf6686b64b2579376504ccc493d97e"
-                                                                                        + "6aed3fb0f9cd71a43dd497f01f17c0e2"
-                                                                                        + "cb3797aa2a2f256656168e6c496afc5f"
-                                                                                        + "b93246f6b1116398a346f1a641f3b041"
-                                                                                        + "e989f7914f90cc2c7fff357876e506b5"
-                                                                                        + "0d334ba77c225bc307ba537152f3f161"
-                                                                                        + "0e4eafe595f6d9d90d11faa933a15ef1"
-                                                                                        + "369546868a7f3a45a96768d40fd9d034"
-                                                                                        + "12c091c6315cf4fde7cb68606937380d"
-                                                                                        + "b2eaaa707b4c4185c32eddcdd306705e"
-                                                                                        + "4dc1ffc872eeee475a64dfac86aba41c"
-                                                                                        + "0618983f8741c5ef68d3a101e8a3b8ca"
-                                                                                        + "c60c905c15fc910840b94c00a0b9d0",
-                    "0aab4c900501b3e24d7cdf4663326a3a"
-                                                                                                                            + "87df5e4843b2cbdb67cbf6e460fec350"
-                                                                                                                            + "aa5371b1508f9f4528ecea23c436d94b"
-                                                                                                                            + "5e8fcd4f681e30a6ac00a9704a188a03"
-            });
+        SecurityProviderRegistrar registrar = SecurityUtils.getRegisteredProvider(SecurityUtils.EDDSA);
+        if (registrar == null) {
+            registrar = SecurityUtils.getRegisteredProvider(SecurityUtils.BOUNCY_CASTLE);
         }
+        if (registrar == null) {
+            throw new IllegalStateException("Neither net.i2p nor BC registered");
+        }
+        EdDSASupport support = registrar.getEdDSASupport().orElseThrow(() -> new IllegalStateException("No EdDSA support"));
+        String supportClassName = support.getClass().getSimpleName();
+        parameters.add(new Object[] {
+                supportClassName + " TEST1 - empty message", support,
+                "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
+                "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", "",
+                "e5564300c360ac729086e2cc806e828a" + "84877f1eb8e5d974d873e06522490155" + "5fb8821590a33bacc61e39701cf9b46b"
+                                                                                        + "d25bf5f0595bbe24655141438e7a100b" });
+        parameters.add(new Object[] {
+                supportClassName + " TEST2 - one byte", support,
+                "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
+                "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", "72",
+                "92a009a9f0d4cab8720e820b5f642540" + "a2b27b5416503f8fb3762223ebdb69da" + "085ac1e43e15996e458f3613d0f11d8c"
+                                                                                          + "387b2eaeb4302aeeb00d291612bb0c00" });
+        parameters.add(new Object[] {
+                supportClassName + " TEST3 - 2 bytes", support,
+                "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
+                "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", "af82",
+                "6291d657deec24024827e69c3abe01a3" + "0ce548a284743a445e3680d7db5ac3ac" + "18ff9b538d16f290ae67f760984dc659"
+                                                                                            + "4a7c15e9716ed28dc027beceea1ec40a" });
+        parameters.add(new Object[] {
+                supportClassName + " TEST1024 - large message", support,
+                "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5",
+                "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e",
+                "08b8b2b733424243760fe426a4b54908" + "632110a66c2f6591eabd3345e3e4eb98" + "fa6e264bf09efe12ee50f8f54e9f77b1"
+                                                                                    + "e355f6c50544e23fb1433ddf73be84d8"
+                                                                                    + "79de7c0046dc4996d9e773f4bc9efe57"
+                                                                                    + "38829adb26c81b37c93a1b270b20329d"
+                                                                                    + "658675fc6ea534e0810a4432826bf58c"
+                                                                                    + "941efb65d57a338bbd2e26640f89ffbc"
+                                                                                    + "1a858efcb8550ee3a5e1998bd177e93a"
+                                                                                    + "7363c344fe6b199ee5d02e82d522c4fe"
+                                                                                    + "ba15452f80288a821a579116ec6dad2b"
+                                                                                    + "3b310da903401aa62100ab5d1a36553e"
+                                                                                    + "06203b33890cc9b832f79ef80560ccb9"
+                                                                                    + "a39ce767967ed628c6ad573cb116dbef"
+                                                                                    + "efd75499da96bd68a8a97b928a8bbc10"
+                                                                                    + "3b6621fcde2beca1231d206be6cd9ec7"
+                                                                                    + "aff6f6c94fcd7204ed3455c68c83f4a4"
+                                                                                    + "1da4af2b74ef5c53f1d8ac70bdcb7ed1"
+                                                                                    + "85ce81bd84359d44254d95629e9855a9"
+                                                                                    + "4a7c1958d1f8ada5d0532ed8a5aa3fb2"
+                                                                                    + "d17ba70eb6248e594e1a2297acbbb39d"
+                                                                                    + "502f1a8c6eb6f1ce22b3de1a1f40cc24"
+                                                                                    + "554119a831a9aad6079cad88425de6bd"
+                                                                                    + "e1a9187ebb6092cf67bf2b13fd65f270"
+                                                                                    + "88d78b7e883c8759d2c4f5c65adb7553"
+                                                                                    + "878ad575f9fad878e80a0c9ba63bcbcc"
+                                                                                    + "2732e69485bbc9c90bfbd62481d9089b"
+                                                                                    + "eccf80cfe2df16a2cf65bd92dd597b07"
+                                                                                    + "07e0917af48bbb75fed413d238f5555a"
+                                                                                    + "7a569d80c3414a8d0859dc65a46128ba"
+                                                                                    + "b27af87a71314f318c782b23ebfe808b"
+                                                                                    + "82b0ce26401d2e22f04d83d1255dc51a"
+                                                                                    + "ddd3b75a2b1ae0784504df543af8969b"
+                                                                                    + "e3ea7082ff7fc9888c144da2af58429e"
+                                                                                    + "c96031dbcad3dad9af0dcbaaaf268cb8"
+                                                                                    + "fcffead94f3c7ca495e056a9b47acdb7"
+                                                                                    + "51fb73e666c6c655ade8297297d07ad1"
+                                                                                    + "ba5e43f1bca32301651339e22904cc8c"
+                                                                                    + "42f58c30c04aafdb038dda0847dd988d"
+                                                                                    + "cda6f3bfd15c4b4c4525004aa06eeff8"
+                                                                                    + "ca61783aacec57fb3d1f92b0fe2fd1a8"
+                                                                                    + "5f6724517b65e614ad6808d6f6ee34df"
+                                                                                    + "f7310fdc82aebfd904b01e1dc54b2927"
+                                                                                    + "094b2db68d6f903b68401adebf5a7e08"
+                                                                                    + "d78ff4ef5d63653a65040cf9bfd4aca7"
+                                                                                    + "984a74d37145986780fc0b16ac451649"
+                                                                                    + "de6188a7dbdf191f64b5fc5e2ab47b57"
+                                                                                    + "f7f7276cd419c17a3ca8e1b939ae49e4"
+                                                                                    + "88acba6b965610b5480109c8b17b80e1"
+                                                                                    + "b7b750dfc7598d5d5011fd2dcc5600a3"
+                                                                                    + "2ef5b52a1ecc820e308aa342721aac09"
+                                                                                    + "43bf6686b64b2579376504ccc493d97e"
+                                                                                    + "6aed3fb0f9cd71a43dd497f01f17c0e2"
+                                                                                    + "cb3797aa2a2f256656168e6c496afc5f"
+                                                                                    + "b93246f6b1116398a346f1a641f3b041"
+                                                                                    + "e989f7914f90cc2c7fff357876e506b5"
+                                                                                    + "0d334ba77c225bc307ba537152f3f161"
+                                                                                    + "0e4eafe595f6d9d90d11faa933a15ef1"
+                                                                                    + "369546868a7f3a45a96768d40fd9d034"
+                                                                                    + "12c091c6315cf4fde7cb68606937380d"
+                                                                                    + "b2eaaa707b4c4185c32eddcdd306705e"
+                                                                                    + "4dc1ffc872eeee475a64dfac86aba41c"
+                                                                                    + "0618983f8741c5ef68d3a101e8a3b8ca"
+                                                                                    + "c60c905c15fc910840b94c00a0b9d0",
+                "0aab4c900501b3e24d7cdf4663326a3a" + "87df5e4843b2cbdb67cbf6e460fec350" + "aa5371b1508f9f4528ecea23c436d94b"
+                                                                                                                        + "5e8fcd4f681e30a6ac00a9704a188a03"
+        });
         return parameters;
     }
 
@@ -203,37 +180,37 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void publicKeyBytes(
-            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
-        byte[] publicSeed = support.getPublicKeyData(publicKey);
+        byte[] publicSeed = EdDSAUtils.getBytes(publicKey);
         assertArrayEquals(pubBytes, publicSeed, "Mismatched public seed value");
     }
 
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void privateKeyBytes(
-            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
-        byte[] privateSeed = support.getPrivateKeyData(privateKey);
+        byte[] privateSeed = EdDSAUtils.getBytes(privateKey);
         assertArrayEquals(prvBytes, privateSeed, "Mismatched private seed value");
     }
 
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void signature(
-            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
-        Signature signer = support.getEDDSASigner();
+        Signature signer = new SignatureEd25519();
         signer.initSigner(null, privateKey);
         signer.update(null, msgBytes.clone());
 
         byte[] actSignature = signer.sign(null);
         assertArrayEquals(expSignature, actSignature, "Mismatched signature");
 
-        Signature verifier = support.getEDDSASigner();
+        Signature verifier = new SignatureEd25519();
         verifier.initVerifier(null, publicKey);
         verifier.update(null, msgBytes.clone());
         assertTrue(verifier.verify(null, expSignature), "Verification failed");
@@ -242,7 +219,7 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void partialBufferSignature(
-            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
             String signature)
             throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
@@ -253,14 +230,14 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
         System.arraycopy(msgBytes, 0, dataBuf, offset, msgBytes.length);
         System.arraycopy(extraData, offset, dataBuf, offset + msgBytes.length, extraData.length - offset);
 
-        Signature signer = support.getEDDSASigner();
+        Signature signer = new SignatureEd25519();
         signer.initSigner(null, privateKey);
         signer.update(null, dataBuf.clone(), offset, msgBytes.length);
 
         byte[] actSignature = signer.sign(null);
         assertArrayEquals(expSignature, actSignature, "Mismatched signature");
 
-        Signature verifier = support.getEDDSASigner();
+        Signature verifier = new SignatureEd25519();
         verifier.initVerifier(null, publicKey);
         verifier.update(null, dataBuf.clone(), offset, msgBytes.length);
         assertTrue(verifier.verify(null, expSignature), "Verification failed");
@@ -269,41 +246,40 @@ class Ed25519VectorsTest<PUB extends PublicKey, PRV extends PrivateKey> extends
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void recoverEDDSAPublicKey(
-            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
         PublicKey recoveredKey = support.recoverEDDSAPublicKey(privateKey);
-        assertTrue(support.compareEDDSAPPublicKeys(publicKey, recoveredKey), "Recovered key is not equal");
-        byte[] recoveredBytes = support.getPublicKeyData(recoveredKey);
+        assertTrue(SecurityUtils.compareEDDSAPPublicKeys(publicKey, recoveredKey), "Recovered key is not equal");
+        byte[] recoveredBytes = EdDSAUtils.getBytes(recoveredKey);
         assertArrayEquals(pubBytes, recoveredBytes, "Mismatched public seed value");
     }
 
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void createPublicKeySpec(
-            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
         KeySpec keySpec = EdDSAUtils.createKeySpec(publicKey);
-        KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider);
+        KeyFactory keyFactory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
         PublicKey generatedKey = keyFactory.generatePublic(keySpec);
-        assertTrue(support.compareEDDSAPPublicKeys(publicKey, generatedKey), "Generated key is not equal");
-        byte[] generatedBytes = support.getPublicKeyData(generatedKey);
+        assertTrue(SecurityUtils.compareEDDSAPPublicKeys(publicKey, generatedKey), "Generated key is not equal");
+        byte[] generatedBytes = EdDSAUtils.getBytes(generatedKey);
         assertArrayEquals(pubBytes, generatedBytes, "Mismatched public seed value");
     }
 
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void createPrivateKeySpec(
-            String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg,
+            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
             String signature) throws Exception {
         initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
         KeySpec keySpec = EdDSAUtils.createKeySpec(privateKey);
-        KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider);
+        KeyFactory keyFactory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
         PrivateKey generatedKey = keyFactory.generatePrivate(keySpec);
-        assertTrue(support.compareEDDSAPrivateKeys(privateKey, generatedKey), "Generated key is not equal");
-        byte[] generatedBytes = support.getPrivateKeyData(generatedKey);
+        assertTrue(SecurityUtils.compareEDDSAPrivateKeys(privateKey, generatedKey), "Generated key is not equal");
+        byte[] generatedBytes = EdDSAUtils.getBytes(generatedKey);
         assertArrayEquals(prvBytes, generatedBytes, "Mismatched private seed value");
     }
-
 }
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java
index 9e0f5b3e0..6e187708f 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java
@@ -58,10 +58,10 @@ class EdDSASecurityProviderRegistrarTest extends SecurityProviderRegistrarTestSu
 
     @Test
     void supportedSecurityEntities() {
-        assertSecurityEntitySupportState(getCurrentTestName(), registrarInstance, true, registrarInstance.getName(),
+        assertSecurityEntitySupportState(getCurrentTestName(), registrarInstance, true, SecurityUtils.ED25519,
                 KeyPairGenerator.class, KeyFactory.class);
         assertSecurityEntitySupportState(getCurrentTestName(), registrarInstance, true,
-                SecurityUtils.CURVE_ED25519_SHA512, Signature.class);
+                SecurityUtils.ED25519, Signature.class);
 
         Collection<Class<?>> supported
                 = new HashSet<>(Arrays.asList(KeyPairGenerator.class, KeyFactory.class, Signature.class));
diff --git a/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java
index 8712fa4b7..2c356117d 100644
--- a/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java
@@ -85,7 +85,7 @@ class SimpleGeneratorHostKeyProviderTest extends JUnitTestSupport {
     @Test
     void edDSA() throws IOException, GeneralSecurityException {
         Assumptions.assumeTrue(SecurityUtils.isEDDSACurveSupported(), "EdDSA not supported");
-        testSimpleGeneratorHostKeyProvider(SecurityUtils.EDDSA, KeyPairProvider.SSH_ED25519, -1, null);
+        testSimpleGeneratorHostKeyProvider(SecurityUtils.ED25519, KeyPairProvider.SSH_ED25519, -1, null);
     }
 
     private void testSimpleGeneratorHostKeyProvider(
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.enc b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ed25519.enc
similarity index 100%
rename from sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.enc
rename to sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ed25519.enc
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.enc2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ed25519.enc2
similarity index 100%
rename from sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.enc2
rename to sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ed25519.enc2
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.pem b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ed25519.pem
similarity index 100%
rename from sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-eddsa.pem
rename to sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/pem/pkcs8-ed25519.pem
diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java
index b9c6db621..de68ca37c 100644
--- a/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java
+++ b/sshd-putty/src/main/java/org/apache/sshd/putty/EdDSAPuttyKeyDecoder.java
@@ -32,6 +32,7 @@ import java.util.Map;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
  * TODO Add javadoc
@@ -60,9 +61,9 @@ public class EdDSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder {
         }
 
         byte[] seed = pubReader.read(Short.MAX_VALUE); // reasonable max. allowed size
-        PublicKey pubKey = SecurityUtils.getEdDSASupport().get().generateEDDSAPublicKey(seed);
+        PublicKey pubKey = EdDSAUtils.getPublicKey(seed);
         seed = prvReader.read(Short.MAX_VALUE); // reasonable max. allowed size
-        PrivateKey prvKey = SecurityUtils.getEdDSASupport().get().generateEDDSAPrivateKey(seed);
+        PrivateKey prvKey = EdDSAUtils.getPrivateKey(seed);
         return Collections.singletonList(new KeyPair(pubKey, prvKey));
     }
 }


(mina-sshd) 04/05: More ed25519 simplifications

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 834a078ad4583ef8937f54eec02fdea9b961a7f0
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Sat Sep 6 00:31:53 2025 +0200

    More ed25519 simplifications
    
    Introduce a PublicKeyFactory interface for something that can recover a
    public key from a private key. On the ScurityProviderRegistrars
    implement this interface, and use that in SecurityUtils to recover
    ed25519 public keys.
    
    With that, the EdDSASupport interface and its per-registrar instances
    become obsolete. Remove them altogether.
    
    Move all EdDSA things that are not specific to a provider (which is
    actually everything but public key recovery) into the "eddsa.generic"
    package.
---
 .../apache/sshd/common/config/keys/KeyUtils.java   |  3 +-
 .../keys/impl/SkED25519PublicKeyEntryDecoder.java  |  4 +-
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  |  7 +-
 .../sshd/common/signature/BuiltinSignatures.java   |  2 +-
 .../sshd/common/signature/SignatureSkED25519.java  |  2 +-
 ...eKeyEntryDecoder.java => PublicKeyFactory.java} | 25 +++---
 .../util/security/SecurityProviderRegistrar.java   | 14 ++--
 .../sshd/common/util/security/SecurityUtils.java   | 92 ++++++++-------------
 .../bouncycastle/BouncyCastlePublicKeyFactory.java | 49 +++++++++++
 .../BouncyCastleSecurityProviderRegistrar.java     | 22 +++--
 .../eddsa/Ed25519PEMResourceKeyParser.java         | 63 --------------
 .../security/eddsa/Ed25519PublicKeyDecoder.java    | 42 ----------
 .../eddsa/EdDSASecurityProviderRegistrar.java      | 19 +++--
 .../security/eddsa/EdDSASecurityProviderUtils.java | 11 +--
 .../security/eddsa/NetI2pCryptoEdDSASupport.java   | 51 ------------
 .../bouncycastle/BouncyCastleEdDSASupport.java     | 60 --------------
 ...arser.java => Ed25519PEMResourceKeyParser.java} | 34 ++++++--
 ...eyDecoder.java => Ed25519PublicKeyDecoder.java} | 14 ++--
 .../util/security/eddsa/generic/EdDSASupport.java  | 96 ----------------------
 .../util/security/eddsa/generic/EdDSAUtils.java    | 34 ++++++--
 ...a => OpenSSHEd25519PrivateKeyEntryDecoder.java} | 15 ++--
 .../eddsa/{ => generic}/SignatureEd25519.java      |  2 +-
 .../util/security/eddsa/Ed25519VectorsTest.java    | 53 +++++-------
 .../common/signature/SignatureFactoriesTest.java   |  3 +-
 24 files changed, 233 insertions(+), 484 deletions(-)

diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index 059b3a4c3..ab6f70273 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -84,6 +84,7 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder;
 
 /**
  * Utility class for keys
@@ -172,7 +173,7 @@ public final class KeyUtils {
         registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
 
         if (SecurityUtils.isEDDSACurveSupported()) {
-            registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder());
+            registerPublicKeyEntryDecoder(Ed25519PublicKeyDecoder.INSTANCE);
         }
 
         registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
index 2a2e90b15..de2656de6 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
@@ -36,7 +36,7 @@ import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
@@ -65,7 +65,7 @@ public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecode
 
         boolean noTouchRequired = parseBooleanHeader(headers, NO_TOUCH_REQUIRED_HEADER, false);
         boolean verifyRequired = parseBooleanHeader(headers, VERIFY_REQUIRED_HEADER, false);
-        PublicKey pk = SecurityUtils.getEDDSAPublicKeyEntryDecoder().decodePublicKey(session, KeyPairProvider.SSH_ED25519,
+        PublicKey pk = Ed25519PublicKeyDecoder.INSTANCE.decodePublicKey(session, KeyPairProvider.SSH_ED25519,
                 keyData, headers);
         String appName = KeyEntryResolver.decodeString(keyData, MAX_APP_NAME_LENGTH);
         return new SkED25519PublicKey(appName, noTouchRequired, verifyRequired, pk);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
index 38134fc83..74a5a221b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java
@@ -49,7 +49,8 @@ import org.apache.sshd.common.util.io.der.ASN1Type;
 import org.apache.sshd.common.util.io.der.DERParser;
 import org.apache.sshd.common.util.security.Decryptor;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
+import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PEMResourceKeyParser;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -146,9 +147,9 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
                 kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
             }
         } else if (SecurityUtils.isEDDSACurveSupported()
-                && EdDSASupport.ED25519_OID.endsWith(oid)) {
+                && EdDSAUtils.ED25519_OID.equals(oid)) {
             ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
-            kp = EdDSASupport.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes());
+            kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes());
         } else {
             PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
             PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
index bb3444351..b943e9eea 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
@@ -44,7 +44,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.SignatureEd25519;
+import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519;
 
 /**
  * Provides easy access to the currently implemented signatures
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java
index 9c43ab64d..12510ce70 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java
@@ -20,7 +20,7 @@ package org.apache.sshd.common.signature;
 
 import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.util.security.eddsa.SignatureEd25519;
+import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519;
 
 public class SignatureSkED25519 extends AbstractSecurityKeySignature {
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/PublicKeyFactory.java
similarity index 58%
rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/PublicKeyFactory.java
index 7cfb3555b..a2903d1b1 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/PublicKeyFactory.java
@@ -16,23 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.sshd.common.util.security;
 
-package org.apache.sshd.common.util.security.eddsa;
-
-import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 
 /**
- * An implementation of {@link GenericOpenSSHEd25519PrivateKeyEntryDecoder} tied to the {@code net.i2p.crypto} EdDSA
- * security provider
+ * Something that can compute a {@link PublicKey} from a given {@link PrivateKey}.
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class OpenSSHEd25519PrivateKeyEntryDecoder
-        extends GenericOpenSSHEd25519PrivateKeyEntryDecoder {
-    public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder();
-
-    public OpenSSHEd25519PrivateKeyEntryDecoder() {
-        super(new NetI2pCryptoEdDSASupport());
-    }
+@FunctionalInterface
+public interface PublicKeyFactory {
 
+    /**
+     * Given a {@link PrivateKey} computes the corresponding {@link PublicKey}.
+     *
+     * @param  key {@link PrivateKey} to get the {@link PublicKey} for
+     * @return     the {@link PublicKey}, or {@code null} if no public key could be computed
+     */
+    PublicKey getPublicKey(PrivateKey key);
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java
index ec39cdf89..68c9756a4 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java
@@ -22,7 +22,9 @@ package org.apache.sshd.common.util.security;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
 import java.security.MessageDigest;
+import java.security.PrivateKey;
 import java.security.Provider;
+import java.security.PublicKey;
 import java.security.Security;
 import java.security.Signature;
 import java.security.cert.CertificateFactory;
@@ -32,7 +34,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.function.Predicate;
 
 import javax.crypto.Cipher;
@@ -46,12 +47,11 @@ import org.apache.sshd.common.SyspropsMapWrapper;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.IgnoringEmptyMap;
 import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface SecurityProviderRegistrar extends SecurityProviderChoice, OptionalFeature, PropertyResolver {
+public interface SecurityProviderRegistrar extends SecurityProviderChoice, OptionalFeature, PublicKeyFactory, PropertyResolver {
     /**
      * Base name for configuration properties related to security providers
      */
@@ -190,11 +190,9 @@ public interface SecurityProviderRegistrar extends SecurityProviderChoice, Optio
         return isSecurityEntitySupported(CertificateFactory.class, type);
     }
 
-    /**
-     * @return the EdDSA support implementation associated with the security provider (if applicable)
-     */
-    default Optional<EdDSASupport> getEdDSASupport() {
-        return Optional.empty();
+    @Override
+    default PublicKey getPublicKey(PrivateKey key) {
+        return null;
     }
 
     /**
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
index dcfb98101..609cd0b0d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
@@ -42,7 +42,6 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
@@ -60,7 +59,6 @@ import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
-import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
 import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser;
 import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils;
@@ -76,8 +74,8 @@ import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleEncryptedPr
 import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleGeneratorHostKeyProvider;
 import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleKeyPairResourceParser;
 import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleRandomFactory;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.OpenSSHEd25519PrivateKeyEntryDecoder;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
 import org.slf4j.Logger;
@@ -168,6 +166,8 @@ public final class SecurityUtils {
 
     private static final AtomicReference<Boolean> FIPS_MODE = new AtomicReference<>();
 
+    private static final AtomicReference<Boolean> EDDSA_SUPPORT_PRESENT = new AtomicReference<>();
+
     private SecurityUtils() {
         throw new UnsupportedOperationException("No instance");
     }
@@ -556,7 +556,26 @@ public final class SecurityUtils {
      * @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported
      */
     public static boolean isEDDSACurveSupported() {
-        return getEdDSASupport().isPresent();
+        Boolean isSupported = EDDSA_SUPPORT_PRESENT.get();
+        if (isSupported == null) {
+            register();
+            // Currently we can only support ed25519 through libraries and thus we require a registrar.
+            // TODO: GH-585 (needs a multi-release JAR)
+            boolean supported = false;
+            synchronized (REGISTERED_PROVIDERS) {
+                for (SecurityProviderRegistrar r : REGISTERED_PROVIDERS.values()) {
+                    supported = r.isEnabled() && r.isSupported() && r.isKeyFactorySupported(ED25519);
+                    if (supported) {
+                        break;
+                    }
+                }
+            }
+            isSupported = Boolean.valueOf(supported);
+            if (!EDDSA_SUPPORT_PRESENT.compareAndSet(null, Boolean.valueOf(supported))) {
+                isSupported = EDDSA_SUPPORT_PRESENT.get();
+            }
+        }
+        return isSupported != null && isSupported.booleanValue();
     }
 
     public static boolean isNetI2pCryptoEdDSARegistered() {
@@ -564,73 +583,30 @@ public final class SecurityUtils {
         return isProviderRegistered(EDDSA);
     }
 
-    public static Optional<EdDSASupport> getEdDSASupport() {
-        if (isFipsMode()) {
-            return Optional.empty();
-        }
-        register();
-
-        synchronized (REGISTERED_PROVIDERS) {
-            // Prefer the net.i2p.crypto provider if it's available for backwards compatibility
-            SecurityProviderRegistrar netI2pCryptoProvider = REGISTERED_PROVIDERS.get(EDDSA);
-            if (netI2pCryptoProvider != null) {
-                Optional<EdDSASupport> support = netI2pCryptoProvider.getEdDSASupport();
-                if (support.isPresent()) {
-                    return support;
-                }
-            }
-
-            for (Map.Entry<String, SecurityProviderRegistrar> entry : REGISTERED_PROVIDERS.entrySet()) {
-                Optional<EdDSASupport> support = entry.getValue().getEdDSASupport();
-                if (support.isPresent()) {
-                    return support;
-                }
-            }
-        }
-        return Optional.empty();
-    }
-
     /* -------------------------------------------------------------------- */
 
-    public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            throw new UnsupportedOperationException(EDDSA + " provider N/A");
-        }
-
-        return support.get().getEDDSAPublicKeyEntryDecoder();
-    }
-
     public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            throw new UnsupportedOperationException(EDDSA + " provider N/A");
-        }
-
-        return support.get().getOpenSSHEDDSAPrivateKeyEntryDecoder();
+        return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
     }
 
     public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
-        if (k1 == null && k2 == null) {
-            return true;
-        }
-        return k1 != null && k2 != null && EdDSAUtils.equals(k1, k2);
+        return EdDSAUtils.equals(k1, k2);
     }
 
     public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
-        if (k1 == null && k2 == null) {
-            return true;
-        }
-        return k1 != null && k2 != null && EdDSAUtils.equals(k1, k2);
+        return EdDSAUtils.equals(k1, k2);
     }
 
     public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
-        Optional<EdDSASupport> support = getEdDSASupport();
-        if (!support.isPresent()) {
-            throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
+        synchronized (REGISTERED_PROVIDERS) {
+            for (SecurityProviderRegistrar registrar : REGISTERED_PROVIDERS.values()) {
+                PublicKey pk = registrar.getPublicKey(key);
+                if (pk != null) {
+                    return pk;
+                }
+            }
         }
-
-        return support.get().recoverEDDSAPublicKey(key);
+        return null;
     }
 
     public static KeyPair extractEDDSAKeyPair(Buffer buffer, String keyType) throws GeneralSecurityException {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastlePublicKeyFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastlePublicKeyFactory.java
new file mode 100644
index 000000000..70a1a0aaa
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastlePublicKeyFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.security.bouncycastle;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.apache.sshd.common.util.security.PublicKeyFactory;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
+
+public class BouncyCastlePublicKeyFactory implements PublicKeyFactory {
+
+    public BouncyCastlePublicKeyFactory() {
+        super();
+    }
+
+    @Override
+    public PublicKey getPublicKey(PrivateKey key) {
+        if (SecurityUtils.ED25519.equals(key.getAlgorithm())) {
+            return getPublicEdDSAKey(key);
+        }
+        return null;
+    }
+
+    public PublicKey getPublicEdDSAKey(PrivateKey key) {
+        if (key instanceof EdDSAPrivateKey) {
+            EdDSAPrivateKey edDSAKey = (EdDSAPrivateKey) key;
+            return edDSAKey.getPublicKey();
+        }
+        return null;
+    }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
index 206a1e95a..fbd1de662 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
@@ -21,17 +21,17 @@ package org.apache.sshd.common.util.security.bouncycastle;
 import java.lang.reflect.Field;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
 import java.security.Provider;
+import java.security.PublicKey;
 import java.security.Security;
 import java.security.Signature;
-import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 
 /**
@@ -104,14 +104,12 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi
 
         if (KeyPairGenerator.class.isAssignableFrom(entityType)
                 || KeyFactory.class.isAssignableFrom(entityType)) {
-            if (SecurityUtils.ED25519.equalsIgnoreCase(name)
-                    && SecurityUtils.isNetI2pCryptoEdDSARegistered()) {
-                return false;
+            if (SecurityUtils.ED25519.equalsIgnoreCase(name)) {
+                return !SecurityUtils.isNetI2pCryptoEdDSARegistered() && isEdDSASupported();
             }
         } else if (Signature.class.isAssignableFrom(entityType)) {
-            if (SecurityUtils.ED25519.equalsIgnoreCase(name)
-                    && SecurityUtils.isNetI2pCryptoEdDSARegistered()) {
-                return false;
+            if (SecurityUtils.ED25519.equalsIgnoreCase(name)) {
+                return !SecurityUtils.isNetI2pCryptoEdDSARegistered() && isEdDSASupported();
             }
         }
 
@@ -172,11 +170,11 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi
     }
 
     @Override
-    public Optional<EdDSASupport> getEdDSASupport() {
-        if (!isEdDSASupported()) {
-            return Optional.empty();
+    public PublicKey getPublicKey(PrivateKey key) {
+        if (isEnabled() && isEdDSASupported() && key.getClass().getPackage().getName().startsWith("org.bouncycastle.")) {
+            return new BouncyCastlePublicKeyFactory().getPublicKey(key);
         }
-        return Optional.of(new org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport());
+        return super.getPublicKey(key);
     }
 
     private boolean isEdDSASupported() {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
deleted file mode 100644
index 63ddddac3..000000000
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.common.util.security.eddsa;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-
-import net.i2p.crypto.eddsa.EdDSAPrivateKey;
-import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
-import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
-import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
-import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
-import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PEMResourceKeyParser;
-
-/**
- * An implementation of {@link GenericEd25519PEMResourceKeyParser} tied to the {@code net.i2p.crypto} EdDSA security
- * provider.
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class Ed25519PEMResourceKeyParser extends GenericEd25519PEMResourceKeyParser {
-
-    public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser();
-
-    public Ed25519PEMResourceKeyParser() {
-        super();
-    }
-
-    public static EdDSAPrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException {
-        return EdDSAPrivateKey.class.cast(EdDSASupport.decodeEdDSAPrivateKey(keyData));
-    }
-
-    public static EdDSAPrivateKey generateEdDSAPrivateKey(byte[] seed) throws GeneralSecurityException {
-        if (!SecurityUtils.isEDDSACurveSupported()) {
-            throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported");
-        }
-
-        EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(SecurityUtils.ED25519);
-        EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
-        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
-        return EdDSAPrivateKey.class.cast(factory.generatePrivate(keySpec));
-    }
-}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
deleted file mode 100644
index eb1ffcd45..000000000
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.common.util.security.eddsa;
-
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
-import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder;
-
-/**
- * An implementation of {@link GenericEd25519PublicKeyDecoder} tied to the {@code net.i2p.crypto} EdDSA security
- * provider.
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecoder {
-
-    public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder();
-
-    private Ed25519PublicKeyDecoder() {
-        super(new NetI2pCryptoEdDSASupport());
-    }
-
-    public static byte[] getSeedValue(EdDSAPublicKey key) {
-        // a bit of reverse-engineering on the EdDSAPublicKeySpec
-        return (key == null) ? null : key.getAbyte();
-    }
-}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
index 3697bdf24..b351153ca 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
@@ -21,16 +21,16 @@ package org.apache.sshd.common.util.security.eddsa;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
 import java.security.Provider;
+import java.security.PublicKey;
 import java.security.Signature;
-import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
 import org.apache.sshd.common.util.security.SecurityEntityFactory;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 
 /**
@@ -131,14 +131,19 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
     }
 
     @Override
-    public Optional<EdDSASupport> getEdDSASupport() {
-        if (!isSupported()) {
-            return Optional.empty();
+    public PublicKey getPublicKey(PrivateKey key) {
+        if (isEnabled() && isSupported() && "EdDSA".equals(key.getAlgorithm())
+                && key.getClass().getPackage().getName().startsWith("net.i2p.")) {
+            try {
+                return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key);
+            } catch (GeneralSecurityException e) {
+                return null;
+            }
         }
-        return Optional.of(new NetI2pCryptoEdDSASupport());
+        return super.getPublicKey(key);
     }
 
-    private static abstract class DelegatingSecurityEntityFactory<F> implements SecurityEntityFactory<F> {
+    private abstract static class DelegatingSecurityEntityFactory<F> implements SecurityEntityFactory<F> {
 
         private SecurityEntityFactory<F> delegate;
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
index 2fe0705c6..e6d0c9f9f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
@@ -34,19 +34,19 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey;
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
 import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
-import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
+import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public final class EdDSASecurityProviderUtils {
     // See EdDSANamedCurveTable
-    public static final int KEY_SIZE = EdDSASupport.KEY_SIZE;
+    public static final int KEY_SIZE = EdDSAUtils.ED25519_LENGTH * Byte.SIZE;
 
     private EdDSASecurityProviderUtils() {
         throw new UnsupportedOperationException("No instance");
@@ -105,11 +105,6 @@ public final class EdDSASecurityProviderUtils {
         return Ed25519PublicKeyDecoder.INSTANCE;
     }
 
-    public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
-        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
-        return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
-    }
-
     public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
         if (!SecurityUtils.isEDDSACurveSupported()) {
             return false;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
deleted file mode 100644
index da8a64eee..000000000
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.common.util.security.eddsa;
-
-import java.security.GeneralSecurityException;
-import java.security.PrivateKey;
-
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
-import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
-import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
-
-public class NetI2pCryptoEdDSASupport implements EdDSASupport {
-
-    public NetI2pCryptoEdDSASupport() {
-        super();
-    }
-
-    @Override
-    public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
-        return Ed25519PublicKeyDecoder.INSTANCE;
-    }
-
-    @Override
-    public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
-        return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
-    }
-
-    @Override
-    public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
-        return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key);
-    }
-
-}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
deleted file mode 100644
index ea679e906..000000000
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.common.util.security.eddsa.bouncycastle;
-
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-
-import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
-import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
-import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
-import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder;
-import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder;
-import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
-import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
-
-public class BouncyCastleEdDSASupport implements EdDSASupport {
-
-    public BouncyCastleEdDSASupport() {
-        super();
-    }
-
-    @Override
-    public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() {
-        return new GenericEd25519PublicKeyDecoder(this);
-    }
-
-    @Override
-    public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() {
-        return new GenericOpenSSHEd25519PrivateKeyEntryDecoder(this);
-    }
-
-    @Override
-    public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
-        if (!(key instanceof EdDSAPrivateKey)) {
-            throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA);
-        }
-        EdDSAPrivateKey edDSAKey = (EdDSAPrivateKey) key;
-        return edDSAKey.getPublicKey();
-    }
-
-}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PEMResourceKeyParser.java
similarity index 80%
rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java
rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PEMResourceKeyParser.java
index 7ee7c47f2..f32985d77 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PEMResourceKeyParser.java
@@ -25,6 +25,8 @@ import java.io.StreamCorruptedException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -44,7 +46,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
+public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
 
     // From an early draft of RFC 8410 (https://datatracker.ietf.org/doc/html/draft-ietf-curdle-pkix-eddsa-00).
     // The final RFC 8410 dropped the "EDDSA" and moved to PKCS#8 using just "BEGIN PRIVATE KEY".
@@ -59,11 +61,11 @@ public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPa
     public static final String END_ED25519_MARKER = "END ED25519 PRIVATE KEY";
     public static final List<String> ENDERS = GenericUtils.unmodifiableList(END_MARKER, END_ED25519_MARKER);
 
-    public static final String ED25519_OID = EdDSASupport.ED25519_OID;
+    public static final String ED25519_OID = EdDSAUtils.ED25519_OID;
 
-    public static final GenericEd25519PEMResourceKeyParser INSTANCE = new GenericEd25519PEMResourceKeyParser();
+    public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser();
 
-    public GenericEd25519PEMResourceKeyParser() {
+    public Ed25519PEMResourceKeyParser() {
         super(SecurityUtils.ED25519, ED25519_OID, BEGINNERS, ENDERS);
     }
 
@@ -145,7 +147,29 @@ public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPa
     }
 
     public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException {
-        return EdDSASupport.decodeEd25519KeyPair(keyData);
+        PrivateKey privateKey = decodeEdDSAPrivateKey(keyData);
+        PublicKey publicKey = SecurityUtils.recoverEDDSAPublicKey(privateKey);
+        return new KeyPair(publicKey, privateKey);
+    }
+
+    /**
+     * @param  keyData the raw private key bytes.
+     * @return         the associated private key.
+     */
+    private static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException {
+        try (DERParser parser = new DERParser(keyData)) {
+            ASN1Object obj = parser.readObject();
+            if (obj == null) {
+                throw new StreamCorruptedException("Missing key data container");
+            }
+
+            ASN1Type objType = obj.getObjType();
+            if (objType != ASN1Type.OCTET_STRING) {
+                throw new StreamCorruptedException("Mismatched key data container type: " + objType);
+            }
+
+            return EdDSAUtils.getPrivateKey(obj.getValue());
+        }
     }
 
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java
similarity index 86%
rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java
index 084bfe0c9..712defeac 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java
@@ -36,16 +36,18 @@ import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
+ * An {@link AbstractPublicKeyEntryDecoder} for ed25519 keys.
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder {
+public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder {
+
     public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this
 
-    protected final EdDSASupport edDSASupport;
+    public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder();
 
-    public GenericEd25519PublicKeyDecoder(EdDSASupport edDSASupport) {
+    private Ed25519PublicKeyDecoder() {
         super(Collections.singletonList(KeyPairProvider.SSH_ED25519));
-        this.edDSASupport = edDSASupport;
     }
 
     @Override
@@ -67,11 +69,9 @@ public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecode
     }
 
     @Override
-    public PublicKey decodePublicKey(
-            SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
+    public PublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN);
         return EdDSAUtils.getPublicKey(seed);
     }
-
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
deleted file mode 100644
index 02ff2e18c..000000000
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.common.util.security.eddsa.generic;
-
-import java.io.IOException;
-import java.io.StreamCorruptedException;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
-import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
-import org.apache.sshd.common.util.io.der.ASN1Object;
-import org.apache.sshd.common.util.io.der.ASN1Type;
-import org.apache.sshd.common.util.io.der.DERParser;
-import org.apache.sshd.common.util.security.SecurityUtils;
-
-/**
- * Provides generic operations required of a security provider to support EdDSA and Ed25519.
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public interface EdDSASupport {
-
-    int KEY_SIZE = 256;
-
-    /**
-     * @see <A HREF="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</A>
-     */
-    String ED25519_OID = "1.3.101.112";
-
-    /**
-     * @param  keyData the raw private key bytes.
-     * @return         a {@link KeyPair} from the given raw private key data.
-     */
-    static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException {
-        PrivateKey privateKey = decodeEdDSAPrivateKey(keyData);
-        PublicKey publicKey = SecurityUtils.recoverEDDSAPublicKey(privateKey);
-        return new KeyPair(publicKey, privateKey);
-    }
-
-    /**
-     * @param  keyData the raw private key bytes.
-     * @return         the associated private key.
-     */
-    static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException {
-        try (DERParser parser = new DERParser(keyData)) {
-            ASN1Object obj = parser.readObject();
-            if (obj == null) {
-                throw new StreamCorruptedException("Missing key data container");
-            }
-
-            ASN1Type objType = obj.getObjType();
-            if (objType != ASN1Type.OCTET_STRING) {
-                throw new StreamCorruptedException("Mismatched key data container type: " + objType);
-            }
-
-            return EdDSAUtils.getPrivateKey(obj.getValue());
-        }
-    }
-
-    /**
-     * @return the public key entry decoder implementation associated with the security provider.
-     */
-    PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder();
-
-    /**
-     * @return the private key entry decoder implementation associated with the security provider.
-     */
-    PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder();
-
-    /**
-     * @param  key the private key
-     * @return     the public key associated with the private key.
-     */
-    PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException;
-
-}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
index 61460c46c..838c50e3d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
@@ -28,6 +28,7 @@ import java.security.spec.KeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 import java.util.Arrays;
+import java.util.Objects;
 
 import org.apache.sshd.common.util.io.der.DERParser;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -40,9 +41,19 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  */
 public final class EdDSAUtils {
 
-    private static final int ED25519_LENGTH = 32; // bytes
+    /**
+     * @see <a href="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</a>
+     */
+    public static final String ED25519_OID = "1.3.101.112";
+
+    /**
+     * @see <a href="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</a>
+     */
+    public static final String ED448_OID = "1.3.101.113";
+
+    public static final int ED25519_LENGTH = 32; // bytes
 
-    private static final int ED448_LENGTH = 57; // bytes
+    public static final int ED448_LENGTH = 57; // bytes
 
     // These are the constant prefixes of X.509 encodings of ed25519 and ed448 keys. Appending the actual 32
     // or 57 key bytes yields valid encodings.
@@ -69,8 +80,8 @@ public final class EdDSAUtils {
             0x04, 0x3b, 0x04, 0x39 };
 
     // The first two numbers of the dotted notation are combined into one byte: (1 * 40 + 3) = 43 = 0x2b
-    private static final byte[] ED25519_OID = { 0x2b, 0x65, 0x70 }; // 1.3.101.112
-    private static final byte[] ED448_OID = { 0x2b, 0x65, 0x71 }; // 1.3.101.113
+    private static final byte[] ED25519_OID_BYTES = { 0x2b, 0x65, 0x70 }; // 1.3.101.112
+    private static final byte[] ED448_OID_BYTES = { 0x2b, 0x65, 0x71 }; // 1.3.101.113
 
     private EdDSAUtils() {
         throw new IllegalStateException("No instantiation");
@@ -199,9 +210,9 @@ public final class EdDSAUtils {
             int n;
             try (DERParser algorithmIdentifier = oneAsymmetricKey.readObject().createParser()) {
                 byte[] oid = algorithmIdentifier.readObject().getValue();
-                if (arrayEq(ED25519_OID, oid)) {
+                if (arrayEq(ED25519_OID_BYTES, oid)) {
                     n = ED25519_LENGTH;
-                } else if (arrayEq(ED448_OID, oid)) {
+                } else if (arrayEq(ED448_OID_BYTES, oid)) {
                     n = ED448_LENGTH;
                 } else {
                     throw new IllegalArgumentException("Private key is neither ed25519 nor ed448");
@@ -325,7 +336,10 @@ public final class EdDSAUtils {
      * @throws IllegalArgumentException if one of the keys is neither an ed25519 nor an ed448 key
      */
     public static boolean equals(PublicKey k1, PublicKey k2) throws IllegalArgumentException {
-        return arrayEq(getBytes(k1), getBytes(k2));
+        if (Objects.equals(k1, k2)) {
+            return true;
+        }
+        return k1 != null && k2 != null && arrayEq(getBytes(k1), getBytes(k2));
     }
 
     /**
@@ -337,6 +351,12 @@ public final class EdDSAUtils {
      * @throws IllegalArgumentException if one of the keys is neither an ed25519 nor an ed448 key
      */
     public static boolean equals(PrivateKey k1, PrivateKey k2) throws IllegalArgumentException {
+        if (Objects.equals(k1, k2)) {
+            return true;
+        }
+        if (k1 == null || k2 == null) {
+            return false;
+        }
         byte[] k1Data = null;
         byte[] k2Data = null;
         try {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java
similarity index 92%
rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java
index e1285fb55..aafd8a717 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java
@@ -45,16 +45,16 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder {
+public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder {
+
+    public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder();
+
     private static final int PK_SIZE = 32;
     private static final int SK_SIZE = 32;
     private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE;
 
-    protected final EdDSASupport edDSASupport;
-
-    public GenericOpenSSHEd25519PrivateKeyEntryDecoder(EdDSASupport edDSASupport) {
+    public OpenSSHEd25519PrivateKeyEntryDecoder() {
         super(Collections.singletonList(KeyPairProvider.SSH_ED25519));
-        this.edDSASupport = edDSASupport;
     }
 
     @Override
@@ -99,7 +99,8 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivate
             PrivateKey privateKey = EdDSAUtils.getPrivateKey(sk);
 
             // we can now verify the generated pk matches the one we read
-            if (!Arrays.equals(EdDSAUtils.getBytes(recoverPublicKey(privateKey)), pk)) {
+            PublicKey recoveredPk = recoverPublicKey(privateKey);
+            if (recoveredPk != null && !Arrays.equals(EdDSAUtils.getBytes(recoveredPk), pk)) {
                 throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk.");
             }
 
@@ -148,7 +149,7 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivate
 
     @Override
     public PublicKey recoverPublicKey(PrivateKey prvKey) throws GeneralSecurityException {
-        return edDSASupport.recoverEDDSAPublicKey(prvKey);
+        return SecurityUtils.recoverEDDSAPublicKey(prvKey);
     }
 
     @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/SignatureEd25519.java
similarity index 96%
rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/SignatureEd25519.java
index 7823acc31..3e7e1eb1d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/SignatureEd25519.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.util.security.eddsa;
+package org.apache.sshd.common.util.security.eddsa.generic;
 
 import java.util.Map;
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
index a7deabcd0..29ad12af8 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java
@@ -33,8 +33,8 @@ import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.security.SecurityProviderRegistrar;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport;
 import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeAll;
@@ -61,7 +61,7 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     private byte[] expSignature;
 
     void initEd25519VectorsTest(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg, String signature)
+            String name, String prvKey, String pubKey, String msg, String signature)
             throws GeneralSecurityException, IOException {
         prvBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, prvKey);
         privateKey = EdDSAUtils.getPrivateKey(prvBytes.clone());
@@ -80,28 +80,27 @@ class Ed25519VectorsTest extends JUnitTestSupport {
         if (registrar == null) {
             throw new IllegalStateException("Neither net.i2p nor BC registered");
         }
-        EdDSASupport support = registrar.getEdDSASupport().orElseThrow(() -> new IllegalStateException("No EdDSA support"));
-        String supportClassName = support.getClass().getSimpleName();
+        String supportClassName = registrar.getClass().getSimpleName();
         parameters.add(new Object[] {
-                supportClassName + " TEST1 - empty message", support,
+                supportClassName + " TEST1 - empty message",
                 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
                 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", "",
                 "e5564300c360ac729086e2cc806e828a" + "84877f1eb8e5d974d873e06522490155" + "5fb8821590a33bacc61e39701cf9b46b"
                                                                                         + "d25bf5f0595bbe24655141438e7a100b" });
         parameters.add(new Object[] {
-                supportClassName + " TEST2 - one byte", support,
+                supportClassName + " TEST2 - one byte",
                 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
                 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", "72",
                 "92a009a9f0d4cab8720e820b5f642540" + "a2b27b5416503f8fb3762223ebdb69da" + "085ac1e43e15996e458f3613d0f11d8c"
                                                                                           + "387b2eaeb4302aeeb00d291612bb0c00" });
         parameters.add(new Object[] {
-                supportClassName + " TEST3 - 2 bytes", support,
+                supportClassName + " TEST3 - 2 bytes",
                 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
                 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", "af82",
                 "6291d657deec24024827e69c3abe01a3" + "0ce548a284743a445e3680d7db5ac3ac" + "18ff9b538d16f290ae67f760984dc659"
                                                                                             + "4a7c15e9716ed28dc027beceea1ec40a" });
         parameters.add(new Object[] {
-                supportClassName + " TEST1024 - large message", support,
+                supportClassName + " TEST1024 - large message",
                 "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5",
                 "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e",
                 "08b8b2b733424243760fe426a4b54908" + "632110a66c2f6591eabd3345e3e4eb98" + "fa6e264bf09efe12ee50f8f54e9f77b1"
@@ -180,9 +179,8 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void publicKeyBytes(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
-            String signature) throws Exception {
-        initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
+            String name, String prvKey, String pubKey, String msg, String signature) throws Exception {
+        initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
         byte[] publicSeed = EdDSAUtils.getBytes(publicKey);
         assertArrayEquals(pubBytes, publicSeed, "Mismatched public seed value");
     }
@@ -190,9 +188,8 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void privateKeyBytes(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
-            String signature) throws Exception {
-        initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
+            String name, String prvKey, String pubKey, String msg, String signature) throws Exception {
+        initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
         byte[] privateSeed = EdDSAUtils.getBytes(privateKey);
         assertArrayEquals(prvBytes, privateSeed, "Mismatched private seed value");
     }
@@ -200,9 +197,8 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void signature(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
-            String signature) throws Exception {
-        initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
+            String name, String prvKey, String pubKey, String msg, String signature) throws Exception {
+        initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
         Signature signer = new SignatureEd25519();
         signer.initSigner(null, privateKey);
         signer.update(null, msgBytes.clone());
@@ -219,10 +215,8 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void partialBufferSignature(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
-            String signature)
-            throws Exception {
-        initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
+            String name, String prvKey, String pubKey, String msg, String signature) throws Exception {
+        initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
         byte[] extraData = getCurrentTestName().getBytes(StandardCharsets.UTF_8);
         byte[] dataBuf = new byte[msgBytes.length + extraData.length];
         int offset = extraData.length / 2;
@@ -246,10 +240,9 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void recoverEDDSAPublicKey(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
-            String signature) throws Exception {
-        initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
-        PublicKey recoveredKey = support.recoverEDDSAPublicKey(privateKey);
+            String name, String prvKey, String pubKey, String msg, String signature) throws Exception {
+        initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
+        PublicKey recoveredKey = SecurityUtils.recoverEDDSAPublicKey(privateKey);
         assertTrue(SecurityUtils.compareEDDSAPPublicKeys(publicKey, recoveredKey), "Recovered key is not equal");
         byte[] recoveredBytes = EdDSAUtils.getBytes(recoveredKey);
         assertArrayEquals(pubBytes, recoveredBytes, "Mismatched public seed value");
@@ -258,9 +251,8 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void createPublicKeySpec(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
-            String signature) throws Exception {
-        initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
+            String name, String prvKey, String pubKey, String msg, String signature) throws Exception {
+        initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
         KeySpec keySpec = EdDSAUtils.createKeySpec(publicKey);
         KeyFactory keyFactory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
         PublicKey generatedKey = keyFactory.generatePublic(keySpec);
@@ -272,9 +264,8 @@ class Ed25519VectorsTest extends JUnitTestSupport {
     @MethodSource("parameters")
     @ParameterizedTest(name = "{0}")
     void createPrivateKeySpec(
-            String name, EdDSASupport support, String prvKey, String pubKey, String msg,
-            String signature) throws Exception {
-        initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature);
+            String name, String prvKey, String pubKey, String msg, String signature) throws Exception {
+        initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
         KeySpec keySpec = EdDSAUtils.createKeySpec(privateKey);
         KeyFactory keyFactory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
         PrivateKey generatedKey = keyFactory.generatePrivate(keySpec);
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
index fc2072918..27fc492dc 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
@@ -43,6 +43,7 @@ import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.test.BaseTestSupport;
 import org.apache.sshd.util.test.CoreTestSupportUtils;
@@ -94,7 +95,7 @@ class SignatureFactoriesTest extends BaseTestSupport implements KeyTypeIndicator
                     curve.isSupported() ? ECDSAPublicKeyEntryDecoder.INSTANCE : null);
         }
         addTests(list, KeyPairProvider.SSH_ED25519, BuiltinSignatures.ed25519, ED25519_SIZES,
-                SecurityUtils.isEDDSACurveSupported() ? SecurityUtils.getEDDSAPublicKeyEntryDecoder() : null);
+                SecurityUtils.isEDDSACurveSupported() ? Ed25519PublicKeyDecoder.INSTANCE : null);
         return Collections.unmodifiableList(list);
     }