You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/11/22 05:05:28 UTC

[5/7] mina-sshd git commit: [SSHD-757] Added some hooks to allow usage of OpenPGP keys inside authorized_keys files

[SSHD-757] Added some hooks to allow usage of OpenPGP keys inside authorized_keys files


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/2f92fe5d
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/2f92fe5d
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/2f92fe5d

Branch: refs/heads/master
Commit: 2f92fe5d219bb688f7e43ca28fb7fb81725aaaca
Parents: fc7a8e7
Author: Lyor Goldstein <lg...@apache.org>
Authored: Mon Nov 19 16:29:38 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Thu Nov 22 07:05:16 2018 +0200

----------------------------------------------------------------------
 CHANGES.md                                      |   5 +
 README.md                                       |  14 ++
 .../client/config/hosts/HostConfigEntry.java    |   7 -
 .../client/config/hosts/KnownHostEntry.java     |   7 -
 .../sshd/common/AlgorithmNameProvider.java      |  27 +++
 .../apache/sshd/common/cipher/BaseCipher.java   |  11 +
 .../sshd/common/cipher/BaseRC4Cipher.java       |   6 +-
 .../sshd/common/cipher/BuiltinCiphers.java      |   8 +-
 .../sshd/common/cipher/CipherInformation.java   |  12 +-
 .../apache/sshd/common/cipher/CipherNone.java   |   5 +
 .../org/apache/sshd/common/cipher/ECCurves.java |  14 +-
 .../common/config/ConfigFileReaderSupport.java  |   7 -
 .../common/config/keys/AuthorizedKeyEntry.java  |  71 +++---
 .../sshd/common/config/keys/Identity.java       |  11 +-
 .../sshd/common/config/keys/KeyRandomArt.java   |  10 +-
 .../sshd/common/config/keys/KeyUtils.java       | 113 +++++++++-
 .../sshd/common/config/keys/PublicKeyEntry.java | 222 ++++++++++++++++---
 .../config/keys/PublicKeyEntryDataResolver.java |  71 ++++++
 .../config/keys/PublicKeyEntryDecoder.java      |  19 +-
 .../loader/pem/KeyPairPEMResourceParser.java    |  10 +-
 .../sshd/common/digest/DigestInformation.java   |  12 +-
 .../common/keyprovider/KeySizeIndicator.java    |  30 +++
 .../common/keyprovider/KeyTypeIndicator.java    |  31 +++
 .../apache/sshd/common/mac/MacInformation.java  |  11 +-
 .../apache/sshd/common/signature/Signature.java |  13 +-
 .../sshd/common/util/buffer/BufferUtils.java    |  34 ++-
 .../AbstractGeneratorHostKeyProvider.java       |   8 +-
 .../pubkey/KeySetPublickeyAuthenticator.java    |  17 +-
 .../auth/pubkey/PublickeyAuthenticator.java     |  17 +-
 .../keys/AuthorizedKeysAuthenticator.java       |  28 ++-
 .../DefaultAuthorizedKeysAuthenticator.java     |   5 +-
 .../config/keys/AuthorizedKeyEntryTest.java     |   8 +-
 .../config/keys/AuthorizedKeysTestSupport.java  |   9 +-
 .../signature/SignatureFactoriesTest.java       |  26 ++-
 .../keys/AuthorizedKeysAuthenticatorTest.java   |  15 +-
 .../openpgp/PGPPublicKeyEntryDataResolver.java  | 106 +++++++++
 .../openpgp/PGPPublicKeyEntryDecoder.java       |  87 ++++++++
 .../config/keys/loader/openpgp/PGPUtils.java    | 115 ++++++++++
 .../openpgp/PGPKeyPairResourceParserTest.java   |   6 +-
 .../openpgp/PGPUtilsKeyFingerprintTest.java     | 174 +++++++++++++++
 40 files changed, 1198 insertions(+), 204 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index 5074ade..db1e874 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -66,8 +66,13 @@ accept also an `AttributeRepository` connection context argument (propagated fro
 * The various `ClientIdentitiesWatcher`(s) use a type-safe `ClientIdentityLoaderHolder` and
 `FilePasswordProviderHolder` instead of the generic `Supplier` definition.
 
+* Removed API(s) that used string file paths to create `FileInputStream`-s - using only `java.nio.file.Path`-s
+
 ## Behavioral changes and enhancements
 
+* [SSHD-757](https://issues.apache.org/jira/browse/SSHD-757) - Added hooks and some initial code to allow (limited) usage
+of [OpenPGP](https://www.openpgp.org/) key files - e.g. in `authorized_keys` files or as client identities.
+
 * [SSHD-849](https://issues.apache.org/jira/browse/SSHD-849) - Data forwarding code makes sure all
 pending packets have been sent to the peer channel when closing the tunnel gracefully.
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 3b291a0..811911d 100644
--- a/README.md
+++ b/README.md
@@ -1932,6 +1932,20 @@ This code relies on the [jpgpgj](https://github.com/justinludwig/jpgpj) support
 
 (which in turn automatically uses _Bouncycastle_ - so if one does not want _Bouncycastle_ one cannot use this module).
 
+#### Using OpenPGP authorized keys entries
+
+In order to be able to read `authorized_keys` files that may contain _OpenPGP_ keys references, one needs to register
+the relevant `PublicKeyEntryDataResolver`-s. This is done by calling `PGPPublicKeyEntryDataResolver#registerDefaultKeyEntryDataResolvers`
+once during the _main_ code setup. This will enable the code to read authorized keys entries having the format
+specified in the [OpenSSH PGP configuration](https://www.red-bean.com/~nemo/openssh-gpg/):
+
+```
+    pgp-sign-dss 87C36E60187451050A4F26B134824FC95C781A18 with-comment
+    pgp-sign-rsa 87C36E60187451050A4F26B134824FC95C781A18
+```
+
+Where the key data following the key type specification is the fingerprint value of the referenced key.
+
 ## Useful extra components in _sshd-contrib_
 
 * `InteractivePasswordIdentityProvider` - helps implement a `PasswordIdentityProvider` by delegating calls

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
index c2d3bcb..f94f775 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
@@ -20,7 +20,6 @@ package org.apache.sshd.client.config.hosts;
 
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -790,12 +789,6 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
         }
     }
 
-    public static List<HostConfigEntry> readHostConfigEntries(String filePath) throws IOException {
-        try (InputStream inStream = new FileInputStream(filePath)) {
-            return readHostConfigEntries(inStream, true);
-        }
-    }
-
     public static List<HostConfigEntry> readHostConfigEntries(InputStream inStream, boolean okToClose) throws IOException {
         try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(inStream, okToClose), StandardCharsets.UTF_8)) {
             return readHostConfigEntries(reader, true);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
index 4a8e9ca..bfb444f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
@@ -20,7 +20,6 @@
 package org.apache.sshd.client.config.hosts;
 
 import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -155,12 +154,6 @@ public class KnownHostEntry extends HostPatternsHolder {
         }
     }
 
-    public static List<KnownHostEntry> readKnownHostEntries(String filePath) throws IOException {
-        try (InputStream inStream = new FileInputStream(filePath)) {
-            return readKnownHostEntries(inStream, true);
-        }
-    }
-
     public static List<KnownHostEntry> readKnownHostEntries(InputStream inStream, boolean okToClose) throws IOException {
         try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(inStream, okToClose), StandardCharsets.UTF_8)) {
             return readKnownHostEntries(reader, true);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java
new file mode 100644
index 0000000..87d4959
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface AlgorithmNameProvider {
+    String getAlgorithm();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java
index c3d9426..306b72d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java
@@ -36,13 +36,19 @@ public class BaseCipher implements Cipher {
     private final int ivsize;
     private final int bsize;
     private final String algorithm;
+    private final int keySize;
     private final String transformation;
     private String s;
 
     public BaseCipher(int ivsize, int bsize, String algorithm, String transformation) {
+        this(ivsize, bsize, algorithm, bsize * Byte.SIZE, transformation);
+    }
+
+    public BaseCipher(int ivsize, int bsize, String algorithm, int keySize, String transformation) {
         this.ivsize = ivsize;
         this.bsize = bsize;
         this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm");
+        this.keySize = keySize;
         this.transformation = ValidateUtils.checkNotNullAndNotEmpty(transformation, "No transformation");
     }
 
@@ -52,6 +58,11 @@ public class BaseCipher implements Cipher {
     }
 
     @Override
+    public int getKeySize() {
+        return keySize;
+    }
+
+    @Override
     public String getTransformation() {
         return transformation;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java
index 2e3fc1e..5185fe2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java
@@ -30,7 +30,11 @@ public class BaseRC4Cipher extends BaseCipher {
     public static final int SKIP_SIZE = 1536;
 
     public BaseRC4Cipher(int ivsize, int bsize) {
-        super(ivsize, bsize, "ARCFOUR", "RC4");
+        this(ivsize, bsize, bsize * Byte.SIZE);
+    }
+
+    public BaseRC4Cipher(int ivsize, int bsize, int keySize) {
+        super(ivsize, bsize, "ARCFOUR", keySize, "RC4");
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
index 8884e15..55925f2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
@@ -67,7 +67,7 @@ public enum BuiltinCiphers implements CipherFactory {
     arcfour256(Constants.ARCFOUR256, 8, 32, "ARCFOUR", "RC4") {
         @Override
         public Cipher create() {
-            return new BaseRC4Cipher(getIVSize(), getBlockSize());
+            return new BaseRC4Cipher(getIVSize(), getBlockSize(), getKeySize());
         }
     },
     blowfishcbc(Constants.BLOWFISH_CBC, 8, 16, "Blowfish", "Blowfish/CBC/NoPadding"),
@@ -123,9 +123,7 @@ public enum BuiltinCiphers implements CipherFactory {
         return supported;
     }
 
-    /**
-     * @return The key size (in bits) for the cipher
-     */
+    @Override
     public int getKeySize() {
         return keysize;
     }
@@ -152,7 +150,7 @@ public enum BuiltinCiphers implements CipherFactory {
 
     @Override
     public Cipher create() {
-        return new BaseCipher(getIVSize(), getBlockSize(), getAlgorithm(), getTransformation());
+        return new BaseCipher(getIVSize(), getBlockSize(), getAlgorithm(), getKeySize(), getTransformation());
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java
index f17fd16..96e7d41 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java
@@ -19,15 +19,15 @@
 
 package org.apache.sshd.common.cipher;
 
+import org.apache.sshd.common.AlgorithmNameProvider;
+import org.apache.sshd.common.keyprovider.KeySizeIndicator;
+
 /**
+ * The reported algorithm name refers to the cipher base name - e.g., &quot;AES&quot;, &quot;ARCFOUR&quot;, etc.
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface CipherInformation {
-    /**
-     * @return The cipher's algorithm
-     */
-    String getAlgorithm();
-
+public interface CipherInformation extends AlgorithmNameProvider, KeySizeIndicator {
     /**
      * @return The actual transformation used - e.g., AES/CBC/NoPadding
      */

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java
index 15b6e9f..b0a21d4 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java
@@ -37,6 +37,11 @@ public class CipherNone implements Cipher {
     }
 
     @Override
+    public int getKeySize() {
+        return 0;
+    }
+
+    @Override
     public String getTransformation() {
         return "none";
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
index cb8cd0d..3ccb671 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -45,6 +45,8 @@ import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.digest.DigestFactory;
+import org.apache.sshd.common.keyprovider.KeySizeIndicator;
+import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -55,7 +57,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public enum ECCurves implements NamedResource, OptionalFeature {
+public enum ECCurves implements KeyTypeIndicator, KeySizeIndicator, NamedResource, OptionalFeature {
     nistp256(Constants.NISTP256, new int[]{1, 2, 840, 10045, 3, 1, 7},
             new ECParameterSpec(
                     new EllipticCurve(
@@ -153,7 +155,7 @@ public enum ECCurves implements NamedResource, OptionalFeature {
         this.digestFactory = Objects.requireNonNull(digestFactory, "No digestFactory");
     }
 
-    @Override   // The curve name
+    @Override   // The curve's standard name
     public final String getName() {
         return name;
     }
@@ -166,9 +168,7 @@ public enum ECCurves implements NamedResource, OptionalFeature {
         return oidValue;
     }
 
-    /**
-     * @return The standard key type used to represent this curve
-     */
+    @Override
     public final String getKeyType() {
         return keyType;
     }
@@ -182,9 +182,7 @@ public enum ECCurves implements NamedResource, OptionalFeature {
         return params;
     }
 
-    /**
-     * @return The size (in bits) of the key
-     */
+    @Override
     public final int getKeySize() {
         return keySize;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
index b6cab4f..f61d74e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
@@ -20,7 +20,6 @@
 package org.apache.sshd.common.config;
 
 import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -123,12 +122,6 @@ public final class ConfigFileReaderSupport {
         }
     }
 
-    public static Properties readConfigFile(String path) throws IOException {
-        try (InputStream input = new FileInputStream(path)) {
-            return readConfigFile(input, true);
-        }
-    }
-
     public static Properties readConfigFile(InputStream input, boolean okToClose) throws IOException {
         try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(input, okToClose), StandardCharsets.UTF_8)) {
             return readConfigFile(reader, true);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
index eb7e1fb..2e60968 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
@@ -20,7 +20,6 @@
 package org.apache.sshd.common.config.keys;
 
 import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -90,7 +89,8 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
     }
 
     @Override
-    public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException {
+    public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver)
+            throws IOException, GeneralSecurityException {
         Map<String, String> options = getLoginOptions();
         if (!GenericUtils.isEmpty(options)) {
             int index = 0;
@@ -146,7 +146,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
 
     public static List<PublicKey> resolveAuthorizedKeys(
             PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries)
-                    throws IOException, GeneralSecurityException {
+                throws IOException, GeneralSecurityException {
         if (GenericUtils.isEmpty(entries)) {
             return Collections.emptyList();
         }
@@ -163,7 +163,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
     }
 
     /**
-     * Reads read the contents of an <code>authorized_keys</code> file
+     * Reads read the contents of an {@code authorized_keys} file
      *
      * @param url The {@link URL} to read from
      * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there
@@ -177,7 +177,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
     }
 
     /**
-     * Reads read the contents of an <code>authorized_keys</code> file
+     * Reads read the contents of an {@code authorized_keys} file
      *
      * @param path    {@link Path} to read from
      * @param options The {@link OpenOption}s to use - if unspecified then appropriate
@@ -194,41 +194,26 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
     }
 
     /**
-     * Reads read the contents of an <code>authorized_keys</code> file
+     * Reads read the contents of an {@code authorized_keys} file
      *
-     * @param filePath The file path to read from
-     * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there
-     * @throws IOException If failed to read or parse the entries
-     * @see #readAuthorizedKeys(InputStream, boolean)
-     */
-    public static List<AuthorizedKeyEntry> readAuthorizedKeys(String filePath) throws IOException {
-        try (InputStream in = new FileInputStream(filePath)) {
-            return readAuthorizedKeys(in, true);
-        }
-    }
-
-    /**
-     * Reads read the contents of an <code>authorized_keys</code> file
-     *
-     * @param in        The {@link InputStream}
-     * @param okToClose <code>true</code> if method may close the input stream
-     *                  regardless of whether successful or failed
+     * @param in The {@link InputStream} to use to read the contents of an {@code authorized_keys} file
+     * @param okToClose {@code true if method may close the input regardless success or failure
      * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there
      * @throws IOException If failed to read or parse the entries
      * @see #readAuthorizedKeys(Reader, boolean)
      */
     public static List<AuthorizedKeyEntry> readAuthorizedKeys(InputStream in, boolean okToClose) throws IOException {
-        try (Reader rdr = new InputStreamReader(NoCloseInputStream.resolveInputStream(in, okToClose), StandardCharsets.UTF_8)) {
+        try (Reader rdr = new InputStreamReader(
+                NoCloseInputStream.resolveInputStream(in, okToClose), StandardCharsets.UTF_8)) {
             return readAuthorizedKeys(rdr, true);
         }
     }
 
     /**
-     * Reads read the contents of an <code>authorized_keys</code> file
+     * Reads read the contents of an {@code authorized_keys} file
      *
-     * @param rdr       The {@link Reader}
-     * @param okToClose <code>true</code> if method may close the input stream
-     *                  regardless of whether successful or failed
+     * @param rdr The {@link Reader} to use to read the contents of an {@code authorized_keys} file
+     * @param okToClose {@code true if method may close the input regardless success or failure
      * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there
      * @throws IOException If failed to read or parse the entries
      * @see #readAuthorizedKeys(BufferedReader)
@@ -240,21 +225,19 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
     }
 
     /**
-     * @param rdr The {@link BufferedReader} to use to read the contents of
-     *            an <code>authorized_keys</code> file
+     * @param rdr The {@link BufferedReader} to use to read the contents of an {@code authorized_keys} file
      * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there
      * @throws IOException If failed to read or parse the entries
      * @see #parseAuthorizedKeyEntry(String)
      */
     public static List<AuthorizedKeyEntry> readAuthorizedKeys(BufferedReader rdr) throws IOException {
         List<AuthorizedKeyEntry> entries = null;
-
         for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
             AuthorizedKeyEntry entry;
             try {
                 entry = parseAuthorizedKeyEntry(line);
-                if (entry == null) {    // null, empty or comment line
-                    continue;
+                if (entry == null) {
+                    continue;   // null, empty or comment line
                 }
             } catch (RuntimeException | Error e) {
                 throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")"
@@ -276,13 +259,27 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
     }
 
     /**
-     * @param value Original line from an <code>authorized_keys</code> file
+     * @param value Original line from an {@code authorized_keys} file
      * @return {@link AuthorizedKeyEntry} or {@code null} if the line is
      * {@code null}/empty or a comment line
      * @throws IllegalArgumentException If failed to parse/decode the line
-     * @see #COMMENT_CHAR
+     * @see #parseAuthorizedKeyEntry(String, PublicKeyEntryDataResolver)
      */
     public static AuthorizedKeyEntry parseAuthorizedKeyEntry(String value) throws IllegalArgumentException {
+        return parseAuthorizedKeyEntry(value, null);
+    }
+
+    /**
+     * @param value Original line from an {@code authorized_keys} file
+     * @param resolver The {@link PublicKeyEntryDataResolver} to use - if {@code null}
+     * one will be automatically resolved from the key type
+     * @return {@link AuthorizedKeyEntry} or {@code null} if the line is
+     * {@code null}/empty or a comment line
+     * @throws IllegalArgumentException If failed to parse/decode the line
+     */
+    public static AuthorizedKeyEntry parseAuthorizedKeyEntry(
+            String value, PublicKeyEntryDataResolver resolver)
+                throws IllegalArgumentException {
         String line = GenericUtils.replaceWhitespaceAndTrim(value);
         if (GenericUtils.isEmpty(line) || (line.charAt(0) == COMMENT_CHAR) /* comment ? */) {
             return null;
@@ -310,7 +307,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
         } else {
             String encData = (endPos < (line.length() - 1)) ? line.substring(0, endPos).trim() : line;
             String comment = (endPos < (line.length() - 1)) ? line.substring(endPos + 1).trim() : null;
-            entry = parsePublicKeyEntry(new AuthorizedKeyEntry(), encData);
+            entry = parsePublicKeyEntry(new AuthorizedKeyEntry(), encData, resolver);
             entry.setComment(comment);
         }
 
@@ -318,7 +315,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
     }
 
     /**
-     * Parses a single line from an <code>authorized_keys</code> file that is <U>known</U>
+     * Parses a single line from an {@code authorized_keys} file that is <U>known</U>
      * to contain login options and separates it to the options and the rest of the line.
      *
      * @param entryLine The line to be parsed

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java
----------------------------------------------------------------------
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 eaec413..0d9c854 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
@@ -22,20 +22,17 @@ 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;
 
 /**
- * Represents an SSH key type
+ * Represents an SSH key type - the referenced algorithm is the one used to generate
+ * the key - e.g., &quot;RSA&quot;, &quot;DSA&quot;, &quot;EC&quot;.
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface Identity extends NamedResource, OptionalFeature {
-    /**
-     * @return The key algorithm - e.g., RSA, DSA, EC
-     */
-    String getAlgorithm();
-
+public interface Identity extends AlgorithmNameProvider, NamedResource, OptionalFeature {
     Class<? extends PublicKey> getPublicKeyType();
 
     Class<? extends PrivateKey> getPrivateKeyType();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
index cf0b1c9..13c854c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java
@@ -31,9 +31,11 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 
+import org.apache.sshd.common.AlgorithmNameProvider;
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.keyprovider.KeySizeIndicator;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -52,7 +54,7 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @see <a href="http://sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf">Original article</a>
  * @see <a href="http://opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c">C implementation</a>
  */
-public class KeyRandomArt {
+public class KeyRandomArt implements AlgorithmNameProvider, KeySizeIndicator {
     public static final int FLDBASE = 8;
     public static final int FLDSIZE_Y = FLDBASE + 1;
     public static final int FLDSIZE_X = FLDBASE * 2 + 1;
@@ -117,10 +119,16 @@ public class KeyRandomArt {
         field[x][y] = (char) len;
     }
 
+    /**
+     * @return The algorithm that was used to generate the key - e.g.,
+     * &quot;RSA&quot;, &quot;DSA&quot;, &quot;EC&quot;.
+     */
+    @Override
     public String getAlgorithm() {
         return algorithm;
     }
 
+    @Override
     public int getKeySize() {
         return keySize;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
----------------------------------------------------------------------
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 c8cfe9c..fec4f4d 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
@@ -55,9 +55,11 @@ import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.NavigableSet;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.common.Factory;
@@ -292,8 +294,7 @@ public final class KeyUtils {
 
     /**
      * @param decoder The decoder to register
-     * @throws IllegalArgumentException if no decoder or not key type or no
-     *                                  supported names for the decoder
+     * @throws IllegalArgumentException if no decoder or not key type or no supported names for the decoder
      * @see PublicKeyEntryDecoder#getPublicKeyType()
      * @see PublicKeyEntryDecoder#getSupportedTypeNames()
      */
@@ -307,21 +308,111 @@ public final class KeyUtils {
             BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder);
         }
 
-        Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key type");
+        registerPublicKeyEntryDecoderKeyTypes(decoder);
+    }
+
+    /**
+     * Registers the specified decoder for all the types it {@link PublicKeyEntryDecoder#getSupportedTypeNames() supports}
+     *
+     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register
+     * @see #registerPublicKeyEntryDecoderForKeyType(String, PublicKeyEntryDecoder)
+     */
+    public static void registerPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder<?, ?> decoder) {
+        Objects.requireNonNull(decoder, "No decoder specified");
+
+        Collection<String> names =
+            ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key types");
+        for (String n : names) {
+            PublicKeyEntryDecoder<?, ?> prev = registerPublicKeyEntryDecoderForKeyType(n, decoder);
+            if (prev != null) {
+                //noinspection UnnecessaryContinue
+                continue;   // debug breakpoint
+            }
+        }
+    }
+
+    /**
+     * @param keyType The key (never {@code null}/empty) key type
+     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register
+     * @return The previously registered decoder for this key type - {@code null} if none
+     */
+    public static PublicKeyEntryDecoder<?, ?> registerPublicKeyEntryDecoderForKeyType(String keyType, PublicKeyEntryDecoder<?, ?> decoder) {
+        keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
+        Objects.requireNonNull(decoder, "No decoder specified");
+
         synchronized (BY_KEY_TYPE_DECODERS_MAP) {
-            for (String n : names) {
-                PublicKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
-                if (prev != null) {
-                    //noinspection UnnecessaryContinue
-                    continue;   // debug breakpoint
-                }
+            return BY_KEY_TYPE_DECODERS_MAP.put(keyType, decoder);
+        }
+    }
+
+    /**
+     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister
+     * @return The case <U>insensitive</U> {@link NavigableSet} of all the effectively un-registered key types
+     * out of all the {@link PublicKeyEntryDecoder#getSupportedTypeNames() supported} ones.
+     * @see #unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder)
+     */
+    public static NavigableSet<String> unregisterPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
+        Objects.requireNonNull(decoder, "No decoder specified");
+
+        Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
+        Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
+        synchronized (BY_KEY_CLASS_DECODERS_MAP) {
+            BY_KEY_CLASS_DECODERS_MAP.remove(pubType);
+            BY_KEY_CLASS_DECODERS_MAP.remove(prvType);
+        }
+
+        return unregisterPublicKeyEntryDecoderKeyTypes(decoder);
+    }
+
+    /**
+     * Unregisters the specified decoder for all the types it supports
+     *
+     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister
+     * @return The case <U>insensitive</U> {@link NavigableSet} of all the effectively un-registered key types
+     * out of all the {@link PublicKeyEntryDecoder#getSupportedTypeNames() supported} ones.
+     * @see #unregisterPublicKeyEntryDecoderForKeyType(String)
+     */
+    public static NavigableSet<String> unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder<?, ?> decoder) {
+        Objects.requireNonNull(decoder, "No decoder specified");
+
+        Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(
+            decoder.getSupportedTypeNames(), "No supported key types");
+        NavigableSet<String> removed = Collections.emptyNavigableSet();
+        for (String n : names) {
+            PublicKeyEntryDecoder<?, ?> prev = unregisterPublicKeyEntryDecoderForKeyType(n);
+            if (prev == null) {
+                continue;
+            }
+
+            if (removed.isEmpty()) {
+                removed = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+            }
+
+            if (!removed.add(n)) {
+                //noinspection UnnecessaryContinue
+                continue;   // debug breakpoint
             }
         }
+
+        return removed;
+    }
+
+    /**
+     * Unregister the decoder registered for the specified key type
+     *
+     * @param keyType The key (never {@code null}/empty) key type
+     * @return The unregistered {@link PublicKeyEntryDecoder} - {@code null} if none registered for this key type
+     */
+    public static PublicKeyEntryDecoder<?, ?> unregisterPublicKeyEntryDecoderForKeyType(String keyType) {
+        keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
+
+        synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+            return BY_KEY_TYPE_DECODERS_MAP.remove(keyType);
+        }
     }
 
     /**
-     * @param keyType The {@code OpenSSH} key type string -  e.g., {@code ssh-rsa, ssh-dss}
-     *                - ignored if {@code null}/empty
+     * @param keyType The {@code OpenSSH} key type string -  e.g., {@code ssh-rsa, ssh-dss} - ignored if {@code null}/empty
      * @return The registered {@link PublicKeyEntryDecoder} or {code null} if not found
      */
     public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(String keyType) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
----------------------------------------------------------------------
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 1db3d2b..49a5e41 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
@@ -28,11 +28,15 @@ import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
-import java.util.Base64;
+import java.util.Collections;
+import java.util.NavigableMap;
 import java.util.Objects;
+import java.util.TreeMap;
 
+import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * <P>Represents a {@link PublicKey} whose data is formatted according to
@@ -44,14 +48,12 @@ import org.apache.sshd.common.util.NumberUtils;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class PublicKeyEntry implements Serializable {
-
+public class PublicKeyEntry implements Serializable, KeyTypeIndicator {
     /**
      * Character used to denote a comment line in the keys file
      */
     public static final char COMMENT_CHAR = '#';
 
-
     /**
      * Standard folder name used by OpenSSH to hold key files
      */
@@ -59,8 +61,11 @@ public class PublicKeyEntry implements Serializable {
 
     private static final long serialVersionUID = -585506072687602760L;
 
+    private static final NavigableMap<String, PublicKeyEntryDataResolver> KEY_DATA_RESOLVERS = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
     private String keyType;
     private byte[] keyData;
+    private PublicKeyEntryDataResolver keyDataResolver = PublicKeyEntryDataResolver.DEFAULT;
 
     public PublicKeyEntry() {
         super();
@@ -71,6 +76,7 @@ public class PublicKeyEntry implements Serializable {
         this.keyData = keyData;
     }
 
+    @Override
     public String getKeyType() {
         return keyType;
     }
@@ -87,6 +93,25 @@ public class PublicKeyEntry implements Serializable {
         this.keyData = value;
     }
 
+    public PublicKeyEntryDataResolver getKeyDataResolver() {
+        return keyDataResolver;
+    }
+
+    public void setKeyDataResolver(PublicKeyEntryDataResolver keyDataResolver) {
+        this.keyDataResolver = keyDataResolver;
+    }
+
+    /**
+     * If a {@link PublicKeyEntryDataResolver} has been set, then uses it - otherwise
+     * uses the {@link PublicKeyEntryDataResolver#DEFAULT default one}.
+     *
+     * @return The resolved instance
+     */
+    public PublicKeyEntryDataResolver resolvePublicKeyEntryDataResolver() {
+        PublicKeyEntryDataResolver resolver = getKeyDataResolver();
+        return (resolver == null) ? PublicKeyEntryDataResolver.DEFAULT : resolver;
+    }
+
     /**
      * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if
      * none of the built-in ones can be used. If {@code null} and no built-in
@@ -97,7 +122,8 @@ public class PublicKeyEntry implements Serializable {
      * @throws IOException              If failed to decode the key
      * @throws GeneralSecurityException If failed to generate the key
      */
-    public PublicKey resolvePublicKey(PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException {
+    public PublicKey resolvePublicKey(PublicKeyEntryResolver fallbackResolver)
+            throws IOException, GeneralSecurityException {
         String kt = getKeyType();
         PublicKeyEntryResolver decoder = KeyUtils.getPublicKeyEntryDecoder(kt);
         if (decoder == null) {
@@ -120,10 +146,11 @@ public class PublicKeyEntry implements Serializable {
      * @throws GeneralSecurityException If failed to generate the key
      * @see #resolvePublicKey(PublicKeyEntryResolver)
      */
-    public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException {
+    public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver)
+            throws IOException, GeneralSecurityException {
         PublicKey key = resolvePublicKey(fallbackResolver);
         if (key != null) {
-            appendPublicKeyEntry(sb, key);
+            appendPublicKeyEntry(sb, key, resolvePublicKeyEntryDataResolver());
         }
         return key;
     }
@@ -161,9 +188,82 @@ public class PublicKeyEntry implements Serializable {
 
     @Override
     public String toString() {
-        byte[] data = getKeyData();
-        Base64.Encoder encoder = Base64.getEncoder();
-        return getKeyType() + " " + (NumberUtils.isEmpty(data) ? "<no-key>" : encoder.encodeToString(data));
+        PublicKeyEntryDataResolver resolver = resolvePublicKeyEntryDataResolver();
+        String encData = resolver.encodeEntryKeyData(getKeyData());
+        return getKeyType() + " " + (GenericUtils.isEmpty(encData) ? "<no-key>" : encData);
+    }
+
+    /**
+     * Registers a specialized decoder for the public key entry data bytes instead of the
+     * {@link PublicKeyEntryDataResolver#DEFAULT default} one.
+     *
+     * @param keyType The key-type value (case <U>insensitive</U>) that will trigger the
+     * usage of this decoder - e.g., &quot;ssh-rsa&quot;, &quot;pgp-sign-dss&quot;, etc.
+     * @param decoder The decoder to use
+     */
+    public static void registerKeyDataEntryResolver(String keyType, PublicKeyEntryDataResolver resolver) {
+        ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
+        Objects.requireNonNull(resolver, "No resolver provided");
+
+        synchronized (KEY_DATA_RESOLVERS) {
+            KEY_DATA_RESOLVERS.put(keyType, resolver);
+        }
+    }
+
+    /**
+     * @param keyType The key-type value (case <U>insensitive</U>) that may have been
+     * previously {@link #registerKeyDataDecoder(String, PublicKeyEntryDataResolver) registered}
+     * - e.g., &quot;ssh-rsa&quot;, &quot;pgp-sign-dss&quot;, etc.
+     * @return The registered resolver instance - {@code null} if none was registered
+     */
+    public static PublicKeyEntryDataResolver unregisterKeyDataEntryResolver(String keyType) {
+        keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
+
+        synchronized (KEY_DATA_RESOLVERS) {
+            return KEY_DATA_RESOLVERS.remove(keyType);
+        }
+    }
+
+    /**
+     * @param keyType keyType The key-type value (case <U>insensitive</U>) whose data is to
+     * be resolved - e.g., &quot;ssh-rsa&quot;, &quot;pgp-sign-dss&quot;, etc.
+     * @return If a specific resolver has been previously
+     * {@link #registerKeyDataDecoder(String, PublicKeyEntryDataResolver) registered} then uses it,
+     * otherwise the {@link PublicKeyEntryDataResolver#DEFAULT default} one.
+     */
+    public static PublicKeyEntryDataResolver resolveKeyDataEntryResolver(String keyType) {
+        keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
+
+        PublicKeyEntryDataResolver resolver;
+        synchronized (KEY_DATA_RESOLVERS) {
+            resolver = KEY_DATA_RESOLVERS.get(keyType);
+        }
+
+        if (resolver != null) {
+            return resolver; // debug breakpoint
+        }
+
+        return PublicKeyEntryDataResolver.DEFAULT;
+    }
+
+    /**
+     * @return A snapshot of the currently registered specialized {@link PublicKeyEntryDataResolver}-s,
+     * where key=the key-type value (case <U>insensitive</U>) - e.g., &quot;ssh-rsa&quot;,
+     * &quot;pgp-sign-dss&quot;, etc., value=the associated {@link PublicKeyEntryDataResolver}
+     * for the key type
+     */
+    public static NavigableMap<String, PublicKeyEntryDataResolver> getRegisteredKeyDataEntryResolvers() {
+        NavigableMap<String, PublicKeyEntryDataResolver> decoders;
+        synchronized (KEY_DATA_RESOLVERS) {
+            if (KEY_DATA_RESOLVERS.isEmpty()) {
+                return Collections.emptyNavigableMap();
+            }
+
+            decoders = new TreeMap<>(KEY_DATA_RESOLVERS.comparator());
+            decoders.putAll(KEY_DATA_RESOLVERS);
+        }
+
+        return decoders;
     }
 
     /**
@@ -171,27 +271,61 @@ public class PublicKeyEntry implements Serializable {
      * (anything beyond the BASE64 data is ignored) - ignored if {@code null}/empty
      * @return A {@link PublicKeyEntry} or {@code null} if no data
      * @throws IllegalArgumentException if bad format found
-     * @see #parsePublicKeyEntry(PublicKeyEntry, String)
+     * @see #parsePublicKeyEntry(String, PublicKeyEntryDataResolver)
      */
     public static PublicKeyEntry parsePublicKeyEntry(String encData) throws IllegalArgumentException {
+        return parsePublicKeyEntry(encData, (PublicKeyEntryDataResolver) null);
+    }
+
+    /**
+     * @param encData Assumed to contain at least {@code key-type base64-data}
+     * (anything beyond the BASE64 data is ignored) - ignored if {@code null}/empty
+     * @param decoder The {@link PublicKeyEntryDataResolver} to use in order to decode
+     * the key data string into its bytes - if {@code null} then one is
+     * automatically {@link #resolveKeyDataEntryResolver(String) resolved}
+     * @return A {@link PublicKeyEntry} or {@code null} if no data
+     * @throws IllegalArgumentException if bad format found
+     * @see #parsePublicKeyEntry(PublicKeyEntry, String, PublicKeyEntryDataResolver)
+     */
+    public static PublicKeyEntry parsePublicKeyEntry(
+            String encData, PublicKeyEntryDataResolver decoder)
+                throws IllegalArgumentException {
         String data = GenericUtils.replaceWhitespaceAndTrim(encData);
         if (GenericUtils.isEmpty(data)) {
             return null;
         } else {
-            return parsePublicKeyEntry(new PublicKeyEntry(), data);
+            return parsePublicKeyEntry(new PublicKeyEntry(), data, decoder);
         }
     }
 
     /**
-     * @param <E>   The generic entry type
-     * @param entry The {@link PublicKeyEntry} whose contents are to be
-     *              updated - ignored if {@code null}
+     * @param <E> The generic entry type
+     * @param entry The {@link PublicKeyEntry} whose contents are to be updated - ignored if {@code null}
      * @param encData Assumed to contain at least {@code key-type base64-data} (anything
-     *              beyond the BASE64 data is ignored) - ignored if {@code null}/empty
+     * beyond the BASE64 data is ignored) - ignored if {@code null}/empty
      * @return The updated entry instance
      * @throws IllegalArgumentException if bad format found
+     * @see #parsePublicKeyEntry(PublicKeyEntry, String, PublicKeyEntryDataResolver)
      */
-    public static <E extends PublicKeyEntry> E parsePublicKeyEntry(E entry, String encData) throws IllegalArgumentException {
+    public static <E extends PublicKeyEntry> E parsePublicKeyEntry(E entry, String encData)
+            throws IllegalArgumentException {
+        return parsePublicKeyEntry(entry, encData, null);
+    }
+
+    /**
+     * @param <E> The generic entry type
+     * @param entry The {@link PublicKeyEntry} whose contents are to be updated - ignored if {@code null}
+     * @param encData Assumed to contain at least {@code key-type base64-data} (anything
+     * beyond the BASE64 data is ignored) - ignored if {@code null}/empty
+     * @param decoder The {@link PublicKeyEntryDataResolver} to use in order to decode
+     * the key data string into its bytes - if {@code null} then one is
+     * automatically {@link #resolveKeyDataEntryResolver(String) resolved}
+     * @return The updated entry instance
+     * @throws IllegalArgumentException if bad format found
+     */
+    public static <E extends PublicKeyEntry> E parsePublicKeyEntry(
+            E entry, String encData, PublicKeyEntryDataResolver decoder)
+                throws IllegalArgumentException {
         String data = GenericUtils.replaceWhitespaceAndTrim(encData);
         if (GenericUtils.isEmpty(data) || (entry == null)) {
             return entry;
@@ -203,19 +337,22 @@ public class PublicKeyEntry implements Serializable {
         }
 
         int endPos = data.indexOf(' ', startPos + 1);
-        if (endPos <= startPos) {   // OK if no continuation beyond the BASE64 encoded data
+        if (endPos <= startPos) {   // OK if no continuation beyond the encoded key data
             endPos = data.length();
         }
 
         String keyType = data.substring(0, startPos);
+        if (decoder == null) {
+            decoder = resolveKeyDataEntryResolver(keyType);
+        }
         String b64Data = data.substring(startPos + 1, endPos).trim();
-        Base64.Decoder decoder = Base64.getDecoder();
-        byte[] keyData = decoder.decode(b64Data);
+        byte[] keyData = decoder.decodeEntryKeyData(b64Data);
         if (NumberUtils.isEmpty(keyData)) {
             throw new IllegalArgumentException("Bad format (no BASE64 key data): " + data);
         }
 
         entry.setKeyType(keyType);
+        entry.setKeyDataResolver(decoder);
         entry.setKeyData(keyData);
         return entry;
     }
@@ -224,11 +361,26 @@ public class PublicKeyEntry implements Serializable {
      * @param key The {@link PublicKey}
      * @return The {@code OpenSSH} encoded data
      * @throws IllegalArgumentException If failed to encode
-     * @see #appendPublicKeyEntry(Appendable, PublicKey)
+     * @see #toString(PublicKey, PublicKeyEntryDataResolver)
      */
     public static String toString(PublicKey key) throws IllegalArgumentException {
+        return toString(key, null);
+    }
+
+    /**
+     * @param key The {@link PublicKey}
+     * @param encoder The {@link PublicKeyEntryDataResolver} to use in order to encode
+     * the key data bytes into a string representation - if {@code null} then one is
+     * automatically {@link #resolveKeyDataEntryResolver(String) resolved}
+     * @return The {@code OpenSSH} encoded data
+     * @throws IllegalArgumentException If failed to encode
+     * @see #appendPublicKeyEntry(Appendable, PublicKey, PublicKeyEntryDataResolver)
+     */
+    public static String toString(
+            PublicKey key, PublicKeyEntryDataResolver encoder)
+                throws IllegalArgumentException {
         try {
-            return appendPublicKeyEntry(new StringBuilder(Byte.MAX_VALUE), key).toString();
+            return appendPublicKeyEntry(new StringBuilder(Byte.MAX_VALUE), key, encoder).toString();
         } catch (IOException e) {
             throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ") to encode: " + e.getMessage(), e);
         }
@@ -242,8 +394,25 @@ public class PublicKeyEntry implements Serializable {
      * @param key The {@link PublicKey} - ignored if {@code null}
      * @return The updated appendable instance
      * @throws IOException If failed to append the data
+     * @see #appendPublicKeyEntry(Appendable, PublicKey, PublicKeyEntryDataResolver)
      */
     public static <A extends Appendable> A appendPublicKeyEntry(A sb, PublicKey key) throws IOException {
+        return appendPublicKeyEntry(sb, key, null);
+    }
+
+    /**
+     * @param <A> The generic appendable class
+     * @param sb  The {@link Appendable} instance to encode the data into
+     * @param key The {@link PublicKey} - ignored if {@code null}
+     * @param encoder The {@link PublicKeyEntryDataResolver} to use in order to encode
+     * the key data bytes into a string representation - if {@code null} then one is
+     * automatically {@link #resolveKeyDataEntryResolver(String) resolved}
+     * @return The updated appendable instance
+     * @throws IOException If failed to append the data
+     */
+    public static <A extends Appendable> A appendPublicKeyEntry(
+            A sb, PublicKey key, PublicKeyEntryDataResolver encoder)
+                throws IOException {
         if (key == null) {
             return sb;
         }
@@ -258,9 +427,12 @@ public class PublicKeyEntry implements Serializable {
         try (ByteArrayOutputStream s = new ByteArrayOutputStream(Byte.MAX_VALUE)) {
             String keyType = decoder.encodePublicKey(s, key);
             byte[] bytes = s.toByteArray();
-            Base64.Encoder encoder = Base64.getEncoder();
-            String b64Data = encoder.encodeToString(bytes);
-            sb.append(keyType).append(' ').append(b64Data);
+            if (encoder == null) {
+                encoder = resolveKeyDataEntryResolver(keyType);
+            }
+
+            String encData = encoder.encodeEntryKeyData(bytes);
+            sb.append(keyType).append(' ').append(encData);
         }
 
         return sb;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java
new file mode 100644
index 0000000..96a4bb9
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java
@@ -0,0 +1,71 @@
+/*
+ * 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.util.Base64;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyEntryDataResolver {
+    PublicKeyEntryDataResolver DEFAULT = new PublicKeyEntryDataResolver() {
+        @Override
+        public String toString() {
+            return "DEFAULT";
+        }
+    };
+
+    /**
+     * Decodes the public key entry data bytes from their string representation - by
+     * default it assume {@link Base64} encoding.
+     *
+     * @param encData The encoded data - ignored if {@code null}/empty
+     * @return The decoded data bytes
+     */
+    default byte[] decodeEntryKeyData(String encData) {
+        if (GenericUtils.isEmpty(encData)) {
+            return GenericUtils.EMPTY_BYTE_ARRAY;
+        }
+
+        Base64.Decoder decoder = Base64.getDecoder();
+        return decoder.decode(encData);
+    }
+
+    /**
+     * Encodes the public key entry data bytes into their string representation - by
+     * default it assume {@link Base64} encoding.
+     *
+     * @param keyData The key data bytes - ignored if {@code null}/empty
+     * @return The encoded data bytes
+     */
+    default String encodeEntryKeyData(byte[] keyData) {
+        if (NumberUtils.isEmpty(keyData)) {
+            return "";
+        }
+
+        Base64.Encoder encoder = Base64.getEncoder();
+        return encoder.encodeToString(keyData);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
----------------------------------------------------------------------
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 ad42781..1d6b7aa 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
@@ -50,34 +50,37 @@ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends Privat
         ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
         Collection<String> supported = getSupportedTypeNames();
         if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) {
-            return decodePublicKey(keyData);
+            return decodePublicKey(keyType, keyData);
         }
 
         throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported);
     }
 
     /**
-     * @param keyData The key data bytes in {@code OpenSSH} format (after
-     *                BASE64 decoding) - ignored if {@code null}/empty
+     * @param keyType The {@code OpenSSH} reported key type
+     * @param keyData The key data bytes in {@code OpenSSH} format (after BASE64
+     * decoding) - ignored if {@code null}/empty
      * @return The decoded {@link PublicKey} - or {@code null} if no data
      * @throws IOException              If failed to decode the key
      * @throws GeneralSecurityException If failed to generate the key
      */
-    default PUB decodePublicKey(byte... keyData) throws IOException, GeneralSecurityException {
-        return decodePublicKey(keyData, 0, NumberUtils.length(keyData));
+    default PUB decodePublicKey(String keyType, byte... keyData) throws IOException, GeneralSecurityException {
+        return decodePublicKey(keyType, keyData, 0, NumberUtils.length(keyData));
     }
 
-    default PUB decodePublicKey(byte[] keyData, int offset, int length) throws IOException, GeneralSecurityException {
+    default PUB decodePublicKey(String keyType, byte[] keyData, int offset, int length)
+            throws IOException, GeneralSecurityException {
         if (length <= 0) {
             return null;
         }
 
         try (InputStream stream = new ByteArrayInputStream(keyData, offset, length)) {
-            return decodePublicKey(stream);
+            return decodePublicKeyByType(keyType, stream);
         }
     }
 
-    default PUB decodePublicKey(InputStream keyData) throws IOException, GeneralSecurityException {
+    default PUB decodePublicKeyByType(String keyType, InputStream keyData)
+            throws IOException, GeneralSecurityException {
         // the actual data is preceded by a string that repeats the key type
         String type = KeyEntryResolver.decodeString(keyData, KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH);
         if (GenericUtils.isEmpty(type)) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
index 07c7e44..d9457f7 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java
@@ -19,17 +19,15 @@
 
 package org.apache.sshd.common.config.keys.loader.pem;
 
+import org.apache.sshd.common.AlgorithmNameProvider;
 import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
 
 /**
+ * The reported algorithm name refers to the encryption algorithm name - e.g., &quot;RSA&quot;, &quot;DSA&quot;
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface KeyPairPEMResourceParser extends KeyPairResourceParser {
-    /**
-     * @return The encryption algorithm name - e.g., &quot;RSA&quot;, &quot;DSA&quot;
-     */
-    String getAlgorithm();
-
+public interface KeyPairPEMResourceParser extends AlgorithmNameProvider, KeyPairResourceParser {
     /**
      * @return The OID used to identify this algorithm in DER encodings - e.g., RSA=1.2.840.113549.1.1.1
      */

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
index ba181dc..400d14a 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java
@@ -19,18 +19,16 @@
 
 package org.apache.sshd.common.digest;
 
+import org.apache.sshd.common.AlgorithmNameProvider;
+
 /**
+ * The reported algorithm name refers to the type of digest being calculated.
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface DigestInformation {
-    /**
-     * @return The digest algorithm name
-     */
-    String getAlgorithm();
-
+public interface DigestInformation extends AlgorithmNameProvider {
     /**
      * @return The number of bytes in the digest's output
      */
     int getBlockSize();
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java
new file mode 100644
index 0000000..1a8e456
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java
@@ -0,0 +1,30 @@
+/*
+ * 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.keyprovider;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeySizeIndicator {
+    /**
+     * @return The number of bits used in the key
+     */
+    int getKeySize();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java
new file mode 100644
index 0000000..1830447
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java
@@ -0,0 +1,31 @@
+/*
+ * 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.keyprovider;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface KeyTypeIndicator {
+    /**
+     * @return The <U>SSH</U> key type name - e.g., &quot;ssh-rsa&quot;, &quot;sshd-dss&quot; etc.
+     */
+    String getKeyType();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java
index 583165f..07a9321 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java
@@ -19,15 +19,14 @@
 
 package org.apache.sshd.common.mac;
 
+import org.apache.sshd.common.AlgorithmNameProvider;
+
 /**
+ * The reported algorithm name refers to the MAC being used
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface MacInformation {
-    /**
-     * @return MAC algorithm name
-     */
-    String getAlgorithm();
-
+public interface MacInformation extends AlgorithmNameProvider {
     /**
      * @return MAC output block size in bytes - may be less than the default
      * - e.g., MD5-96

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java
index fd88a1d..2c78ec1 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java
@@ -21,20 +21,17 @@ package org.apache.sshd.common.signature;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 
+import org.apache.sshd.common.AlgorithmNameProvider;
 import org.apache.sshd.common.util.NumberUtils;
 
 /**
- * Signature interface for SSH used to sign or verify packets
- * Usually wraps a javax.crypto.Signature object
+ * Signature interface for SSH used to sign or verify packets. Usually wraps a
+ * {@code javax.crypto.Signature} object. The reported algorithm name refers to
+ * the signature type being applied.
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface Signature {
-    /**
-     * @return The signature algorithm name
-     */
-    String getAlgorithm();
-
+public interface Signature extends AlgorithmNameProvider {
     /**
      * @param key The {@link PublicKey} to be used for verifying signatures
      * @throws Exception If failed to initialize

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
index c13cd9e..7fff5d0 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
@@ -65,19 +65,27 @@ public final class BufferUtils {
         throw new UnsupportedOperationException("No instance allowed");
     }
 
-    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte... data) {
+    public static void dumpHex(
+            SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte... data) {
         dumpHex(logger, level, prefix, resolver, sep, data, 0, NumberUtils.length(data));
     }
 
-    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte[] data, int offset, int len) {
-        dumpHex(logger, level, prefix, sep, resolver.getIntProperty(HEXDUMP_CHUNK_SIZE, DEFAULT_HEXDUMP_CHUNK_SIZE), data, offset, len);
+    public static void dumpHex(
+            SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver,
+            char sep, byte[] data, int offset, int len) {
+        dumpHex(logger, level, prefix, sep,
+            resolver.getIntProperty(HEXDUMP_CHUNK_SIZE, DEFAULT_HEXDUMP_CHUNK_SIZE),
+            data, offset, len);
     }
 
-    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte... data) {
+    public static void dumpHex(
+            SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte... data) {
         dumpHex(logger, level, prefix, sep, chunkSize, data, 0, NumberUtils.length(data));
     }
 
-    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte[] data, int offset, int len) {
+    public static void dumpHex(
+            SimplifiedLog logger, Level level, String prefix, char sep,
+            int chunkSize, byte[] data, int offset, int len) {
         if ((logger == null) || (level == null) || (!logger.isEnabled(level))) {
             return;
         }
@@ -151,7 +159,9 @@ public final class BufferUtils {
         return appendHex(sb, array, 0, NumberUtils.length(array), sep);
     }
 
-    public static <A extends Appendable> A appendHex(A sb, byte[] array, int offset, int len, char sep) throws IOException {
+    public static <A extends Appendable> A appendHex(
+            A sb, byte[] array, int offset, int len, char sep)
+                throws IOException {
         if (len <= 0) {
             return sb;
         }
@@ -244,7 +254,9 @@ public final class BufferUtils {
      * @throws IllegalArgumentException If invalid HEX sequence length
      * @throws NumberFormatException If invalid HEX characters found
      */
-    public static <S extends OutputStream> int decodeHex(S stream, char separator, CharSequence csq, int start, int end) throws IOException {
+    public static <S extends OutputStream> int decodeHex(
+            S stream, char separator, CharSequence csq, int start, int end)
+                throws IOException {
         int len = end - start;
         ValidateUtils.checkTrue(len >= 0, "Bad HEX sequence length: %d", len);
 
@@ -399,7 +411,9 @@ public final class BufferUtils {
      * @throws IOException If failed to write the value or work buffer to small
      * @see #writeUInt(OutputStream, long, byte[], int, int)
      */
-    public static void writeInt(OutputStream output, int value, byte[] buf, int off, int len) throws IOException {
+    public static void writeInt(
+            OutputStream output, int value, byte[] buf, int off, int len)
+                throws IOException {
         writeUInt(output, value & 0xFFFFFFFFL, buf, off, len);
     }
 
@@ -427,7 +441,9 @@ public final class BufferUtils {
      * @throws IOException If failed to write the value or work buffer to small
      * @see #putUInt(long, byte[], int, int)
      */
-    public static void writeUInt(OutputStream output, long value, byte[] buf, int off, int len) throws IOException {
+    public static void writeUInt(
+            OutputStream output, long value, byte[] buf, int off, int len)
+                throws IOException {
         try {
             int writeLen = putUInt(value, buf, off, len);
             output.write(buf, off, writeLen);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
index 287abec..1ed3a6c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
+++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
@@ -35,11 +35,13 @@ import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.sshd.common.AlgorithmNameProvider;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeySizeIndicator;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.resource.PathResource;
@@ -53,7 +55,9 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public abstract class AbstractGeneratorHostKeyProvider extends AbstractKeyPairProvider {
+public abstract class AbstractGeneratorHostKeyProvider
+        extends AbstractKeyPairProvider
+        implements AlgorithmNameProvider, KeySizeIndicator {
     public static final String DEFAULT_ALGORITHM = KeyUtils.RSA_ALGORITHM;
     public static final boolean DEFAULT_ALLOWED_TO_OVERWRITE = true;
 
@@ -77,6 +81,7 @@ public abstract class AbstractGeneratorHostKeyProvider extends AbstractKeyPairPr
         this.path = (path == null) ? null : path.toAbsolutePath();
     }
 
+    @Override
     public String getAlgorithm() {
         return algorithm;
     }
@@ -85,6 +90,7 @@ public abstract class AbstractGeneratorHostKeyProvider extends AbstractKeyPairPr
         this.algorithm = algorithm;
     }
 
+    @Override
     public int getKeySize() {
         return keySize;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java
index fcba948..3c70f2e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java
@@ -21,6 +21,7 @@ package org.apache.sshd.server.auth.pubkey;
 import java.security.PublicKey;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Objects;
 
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.GenericUtils;
@@ -32,11 +33,20 @@ import org.apache.sshd.server.session.ServerSession;
  */
 public class KeySetPublickeyAuthenticator extends AbstractLoggingBean implements PublickeyAuthenticator {
     private final Collection<? extends PublicKey> keySet;
+    private final Object id;
 
-    public KeySetPublickeyAuthenticator(Collection<? extends PublicKey> keySet) {
+    public KeySetPublickeyAuthenticator(Object id, Collection<? extends PublicKey> keySet) {
+        this.id = id;
         this.keySet = (keySet == null) ? Collections.emptyList() : keySet;
     }
 
+    /**
+     * @return Some kind of mnemonic identifier for the authenticator - used also in {@link #toString()}
+     */
+    public Object getId() {
+        return id;
+    }
+
     public final Collection<? extends PublicKey> getKeySet() {
         return keySet;
     }
@@ -62,4 +72,9 @@ public class KeySetPublickeyAuthenticator extends AbstractLoggingBean implements
         }
         return matchFound;
     }
+
+    @Override
+    public String toString() {
+        return Objects.toString(getId());
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
index a498478..b6a8ab6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
@@ -49,14 +49,23 @@ public interface PublickeyAuthenticator {
      */
     boolean authenticate(String username, PublicKey key, ServerSession session) throws AsyncAuthException;
 
+    /**
+     * @param id Some kind of mnemonic identifier for the authenticator - used also in {@link #toString()}
+     * @param fallbackResolver The public key resolver to use if none of the default registered ones works
+     * @param entries The entries to parse - ignored if {@code null}/empty
+     * @return A wrapper with all the parsed keys
+     * @throws IOException If failed to parse the keys data
+     * @throws GeneralSecurityException If failed to generate the relevant keys from the parsed data
+     */
     static PublickeyAuthenticator fromAuthorizedEntries(
-                PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries)
-            throws IOException, GeneralSecurityException {
-        Collection<PublicKey> keys = AuthorizedKeyEntry.resolveAuthorizedKeys(fallbackResolver, entries);
+            Object id, PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries)
+                throws IOException, GeneralSecurityException {
+        Collection<PublicKey> keys =
+            AuthorizedKeyEntry.resolveAuthorizedKeys(fallbackResolver, entries);
         if (GenericUtils.isEmpty(keys)) {
             return RejectAllPublickeyAuthenticator.INSTANCE;
         } else {
-            return new KeySetPublickeyAuthenticator(keys);
+            return new KeySetPublickeyAuthenticator(id, keys);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
index a2d577f..3e9ca3a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
@@ -82,7 +82,8 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement
         boolean debugEnabled = log.isDebugEnabled();
         if (!isValidUsername(username, session)) {
             if (debugEnabled) {
-                log.debug("authenticate(" + username + ")[" + session + "][" + key.getAlgorithm() + "] invalid user name - file = " + getPath());
+                log.debug("authenticate({})[{}][{}] invalid user name - file = {}",
+                    username, session, key.getAlgorithm(), getPath());
             }
             return false;
         }
@@ -92,15 +93,15 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement
                 Objects.requireNonNull(resolvePublickeyAuthenticator(username, session), "No delegate");
             boolean accepted = delegate.authenticate(username, key, session);
             if (debugEnabled) {
-                log.debug("authenticate(" + username + ")[" + session + "][" + key.getAlgorithm() + "] accepted " + accepted + " from " + getPath());
+                log.debug("authenticate({})[{}][{}] invalid user name - accepted={} from file = {}",
+                    username, session, key.getAlgorithm(), accepted, getPath());
             }
 
             return accepted;
         } catch (Throwable e) {
             if (debugEnabled) {
-                log.debug("authenticate(" + username + ")[" + session + "][" + getPath() + "]"
-                        + " failed (" + e.getClass().getSimpleName() + ")"
-                        + " to resolve delegate: " + e.getMessage());
+                log.debug("authenticate({})[{}] failed ({}) to authenticate {} key from {}: {}",
+                    username, session, e.getClass().getSimpleName(), key.getAlgorithm(), getPath(), e.getMessage());
             }
 
             if (log.isTraceEnabled()) {
@@ -127,24 +128,33 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement
                 Collection<AuthorizedKeyEntry> entries = reloadAuthorizedKeys(path, username, session);
                 if (GenericUtils.size(entries) > 0) {
                     PublickeyAuthenticator authDelegate =
-                        PublickeyAuthenticator.fromAuthorizedEntries(getFallbackPublicKeyEntryResolver(), entries);
+                        createDelegateAuthenticator(path, entries, getFallbackPublicKeyEntryResolver());
                     delegateHolder.set(authDelegate);
                 }
             } else {
-                log.info("resolvePublickeyAuthenticator(" + username + ")[" + session + "] no authorized keys file at " + path);
+                log.info("resolvePublickeyAuthenticator({})[{}] no authorized keys file at {}", username, session, path);
             }
         }
 
         return delegateHolder.get();
     }
 
+    protected PublickeyAuthenticator createDelegateAuthenticator(
+            Path path, Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
+                throws IOException, GeneralSecurityException {
+        return PublickeyAuthenticator.fromAuthorizedEntries(path, fallbackResolver, entries);
+    }
+
     protected PublicKeyEntryResolver getFallbackPublicKeyEntryResolver() {
         return PublicKeyEntryResolver.IGNORING;
     }
 
-    protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(Path path, String username, ServerSession session) throws IOException {
+    protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(
+            Path path, String username, ServerSession session)
+                throws IOException, GeneralSecurityException {
         Collection<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(path);
-        log.info("reloadAuthorizedKeys(" + username + ")[" + session + "] loaded " + GenericUtils.size(entries) + " keys from " + path);
+        log.info("reloadAuthorizedKeys({})[{}] loaded {} keys from {}",
+            username, session, GenericUtils.size(entries), path);
         updateReloadAttributes();
         return entries;
     }