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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9959954  [SSHD-917] Add support for SSH2 public key file format
9959954 is described below

commit 9959954f0d34278dc3691fffea688186fa61100d
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Wed May 15 16:18:25 2019 +0300

    [SSHD-917] Add support for SSH2 public key file format
---
 CHANGES.md                                         |   7 +-
 README.md                                          |   3 +-
 .../sshd/client/config/hosts/HostConfigEntry.java  |   3 +-
 .../config/hosts/HostConfigEntryResolver.java      |   2 +-
 .../common/config/keys/AuthorizedKeyEntry.java     |  20 ++
 .../sshd/common/config/keys/KeyEntryResolver.java  |  85 ++++++++
 .../sshd/common/config/keys/PublicKeyEntry.java    |  16 +-
 .../common/config/keys/PublicKeyEntryDecoder.java  |  59 +-----
 .../common/config/keys/PublicKeyEntryResolver.java |   8 +-
 .../config/keys/PublicKeyRawDataDecoder.java       |  82 ++++++++
 .../common/config/keys/PublicKeyRawDataReader.java | 120 +++++++++++
 .../config/keys/impl/DSSPublicKeyEntryDecoder.java |   6 +-
 .../keys/impl/ECDSAPublicKeyEntryDecoder.java      |   6 +-
 .../config/keys/impl/RSAPublicKeyDecoder.java      |   6 +-
 .../keys/loader/AbstractKeyPairResourceParser.java |  32 ++-
 .../openssh/OpenSSHKeyPairResourceParser.java      |  14 +-
 .../pem/AbstractPEMResourceKeyPairParser.java      |  37 ++--
 .../loader/pem/DSSPEMResourceKeyPairParser.java    |   3 +-
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  |   2 +-
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  |   3 +-
 .../loader/pem/RSAPEMResourceKeyPairParser.java    |   3 +-
 .../loader/ssh2/Ssh2PublicKeyEntryDecoder.java     | 224 +++++++++++++++++++++
 .../org/apache/sshd/common/util/NumberUtils.java   |   6 +-
 .../BouncyCastleKeyPairResourceParser.java         |   7 +-
 .../security/eddsa/Ed25519PublicKeyDecoder.java    |   6 +-
 .../KeyUtilsFingerprintCaseSensitivityTest.java    |   3 +-
 .../keys/KeyUtilsFingerprintGenerationTest.java    |   3 +-
 .../common/config/keys/PublicKeyEntryTest.java     |   3 +-
 .../OpenSSHKeyPairResourceParserTestSupport.java   |   4 +-
 .../Ssh2PublicKeyEntryDecoderByKeyTypeTest.java    | 121 +++++++++++
 .../ssh2/Ssh2PublicKeyEntryDecoderTest.java}       |  46 +++--
 .../loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub  |   1 +
 .../loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2 |   6 +
 .../loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub  |   1 +
 .../loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2 |   6 +
 .../loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub  |   1 +
 .../loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2 |   7 +
 .../loader/ssh2/rfc4716-multi-line-comment.ssh2    |  13 ++
 .../keys/loader/ssh2/rfc4716-multiple-headers.ssh2 |   7 +
 .../config/keys/loader/ssh2/ssh-dss-PublicKey.pub  |   1 +
 .../config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2 |  12 ++
 .../keys/loader/ssh2/ssh-ed25519-PublicKey.pub     |   1 +
 .../keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2    |   4 +
 .../config/keys/loader/ssh2/ssh-rsa-PublicKey.pub  |   1 +
 .../config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2 |  14 ++
 .../KnownHostsServerKeyVerifierTest.java           |   2 +-
 .../common/config/keys/AuthorizedKeyEntryTest.java |   7 +-
 .../pubkey/LdapPublickeyAuthenticatorTest.java     |   4 +-
 .../openpgp/PGPAuthorizedKeyEntriesLoader.java     |   5 +-
 .../loader/openpgp/PGPKeyPairResourceParser.java   |  11 +-
 .../loader/openpgp/PGPUtilsKeyFingerprintTest.java |   5 +-
 .../keys/loader/putty/AbstractPuttyKeyDecoder.java |  33 +--
 .../keys/loader/putty/DSSPuttyKeyDecoder.java      |   3 +-
 .../keys/loader/putty/ECDSAPuttyKeyDecoder.java    |   3 +-
 .../keys/loader/putty/EdDSAPuttyKeyDecoder.java    |   3 +-
 .../keys/loader/putty/RSAPuttyKeyDecoder.java      |   5 +-
 56 files changed, 936 insertions(+), 160 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 182c736..4065378 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -43,6 +43,9 @@ when they are no longer needed once data has been 
successfully copied.
 * The `CommandFactory` and `ShellFactory` have been modified to accept the 
server's `ChannelSession` instance through
 which they are being invoked.
 
+* The various implementations of public/private keys/pairs decoders/loaders 
are provided with a `Map` of any headers that
+may be available in the relevant data file.
+
 ## Minor code helpers
 
 * The `Session` object provides a `isServerSession` method that can be used to 
distinguish between
@@ -126,6 +129,8 @@ and therefore closing all currently tracked file/directory 
handles.
 
 * [SSHD-909](https://issues.apache.org/jira/browse/SSHD-909) - SFTP versions 
extension handler ignores non-numerical versions when resolving the available 
ones.
 
-* [SSHD-913](https://issues.apache.org/jira/browse/SSHD-913) - Provide channel 
session instance to command and/or shell factories creators     
+* [SSHD-913](https://issues.apache.org/jira/browse/SSHD-913) - Provide channel 
session instance to command and/or shell factories creators
 
 * [SSHD-912](https://issues.apache.org/jira/browse/SSHD-912) - Use separate 
locks for Future(s) and Session/Channel instances.
+
+* [SSHD-917](https://issues.apache.org/jira/browse/SSHD-917) - Add support for 
SSH2 public key file format.
diff --git a/README.md b/README.md
index 24ac222..49b4e38 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@ based applications requiring SSH support.
 * [RFC 4335 - The Secure Shell (SSH) Session Channel Break 
Extension](https://tools.ietf.org/html/rfc4335)
 * [RFC 4344 - The Secure Shell (SSH) Transport Layer Encryption 
Modes](https://tools.ietf.org/html/rfc4344)
 * [RFC 4345 - Improved Arcfour Modes for the Secure Shell (SSH) Transport 
Layer Protocol](https://tools.ietf.org/html/rfc4345)
+* [RFC 4716 - The Secure Shell (SSH) Public Key File 
Format](https://tools.ietf.org/html/rfc4716)
 * [RFC 5480 - Elliptic Curve Cryptography Subject Public Key 
Information](https://tools.ietf.org/html/rfc5480)
 * [RFC 6668 - SHA-2 Data Integrity Verification for the Secure Shell (SSH) 
Transport Layer Protocol](https://tools.ietf.org/html/rfc6668)
 * [RFC 8160 - IUTF8 Terminal Mode in Secure Shell 
(SSH)](https://tools.ietf.org/html/rfc8160)
@@ -59,10 +60,8 @@ based applications requiring SSH support.
 
 * Java 8+ (as of version 1.3)
 
-
 * [Slf4j](http://www.slf4j.org/)
 
-
 The code only requires the core abstract 
[slf4j-api](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) module. The 
actual
 implementation of the logging API can be selected from the many existing 
adaptors.
 
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 475bd11..0ad02e8 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
@@ -61,9 +61,10 @@ import org.apache.sshd.common.util.io.NoCloseReader;
 
 /**
  * Represents an entry in the client's configuration file as defined by
- * the <A 
HREF="http://www.gsp.com/cgi-bin/man.cgi?topic=ssh_config";>ssh_config</A>
+ * the <A HREF="https://linux.die.net/man/5/ssh_config";>ssh_config</A>
  * configuration file format
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ * @see <A 
HREF="https://www.cyberciti.biz/faq/create-ssh-config-file-on-linux-unix/";>OpenSSH
 Config File Examples</A>
  */
 public class HostConfigEntry extends HostPatternsHolder implements 
MutableUserHolder {
     /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java
 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java
index f3fff52..ce36aae 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java
@@ -26,7 +26,7 @@ import org.apache.sshd.common.AttributeRepository;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see <A 
HREF="http://www.gsp.com/cgi-bin/man.cgi?topic=ssh_config";>ssh_config</A>
+ * @see <A HREF="https://linux.die.net/man/5/ssh_config";>ssh_config</A>
  */
 @FunctionalInterface
 public interface HostConfigEntryResolver {
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 a573c44..304f10d 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
@@ -32,6 +32,7 @@ import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -88,6 +89,25 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
         }
     }
 
+    /**
+     * @param session The {@link SessionContext} for invoking this load 
command - may
+     * be {@code null} if not invoked within a session context (e.g., offline 
tool or session unknown).
+     * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if
+     * none of the built-in ones can be used. If {@code null} and no built-in
+     * resolver can be used then an {@link InvalidKeySpecException} is thrown.
+     * @return The resolved {@link PublicKey} - or {@code null} if could not be
+     * resolved. <B>Note:</B> may be called only after key type and data bytes
+     * have been set or exception(s) may be thrown
+     * @throws IOException              If failed to decode the key
+     * @throws GeneralSecurityException If failed to generate the key
+     * @see PublicKeyEntry#resolvePublicKey(SessionContext, Map, 
PublicKeyEntryResolver)
+     */
+    public PublicKey resolvePublicKey(
+            SessionContext session, PublicKeyEntryResolver fallbackResolver)
+                throws IOException, GeneralSecurityException {
+        return resolvePublicKey(session, getLoginOptions(), fallbackResolver);
+    }
+
     @Override
     public PublicKey appendPublicKey(
             SessionContext session, Appendable sb, PublicKeyEntryResolver 
fallbackResolver)
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 8b6dcc8..a10322a 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
@@ -33,7 +33,10 @@ 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;
 
+import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 
 /**
@@ -200,4 +203,86 @@ public interface KeyEntryResolver<PUB extends PublicKey, 
PRV extends PrivateKey>
                 | ((bytes[2] & 0xFF) << 8)
                 | (bytes[3] & 0xFF);
     }
+
+    static Map.Entry<String, Integer> decodeString(byte[] buf, int maxChars) {
+        return decodeString(buf, 0, NumberUtils.length(buf), maxChars);
+    }
+
+    static Map.Entry<String, Integer> decodeString(byte[] buf, int offset, int 
available, int maxChars) {
+        return decodeString(buf, offset, available, StandardCharsets.UTF_8, 
maxChars);
+    }
+
+    static Map.Entry<String, Integer> decodeString(byte[] buf, Charset cs, int 
maxChars) {
+        return decodeString(buf, 0, NumberUtils.length(buf), cs, maxChars);
+    }
+
+    /**
+     * Decodes a run-length encoded string
+     *
+     * @param buf The buffer with the data bytes
+     * @param offset The offset in the buffer to decode the string
+     * @param available The max. available data starting from the offset
+     * @param cs The {@link Charset} to use to decode the string
+     * @param maxChars Max. allowed characters in string - if more than
+     * that is encoded then an {@link IndexOutOfBoundsException} will be thrown
+     * @return The decoded string + the offset of the next byte after it
+     * @see #readRLEBytes(byte[], int, int, int)
+     */
+    static Map.Entry<String, Integer> decodeString(
+            byte[] buf, int offset, int available, Charset cs, int maxChars) {
+        Map.Entry<byte[], Integer> result =
+            readRLEBytes(buf, offset, available, maxChars * 4 /* in case UTF-8 
with weird characters */);
+        byte[] bytes = result.getKey();
+        Integer nextOffset = result.getValue();
+        return new SimpleImmutableEntry<>(new String(bytes, cs), nextOffset);
+    }
+
+    static Map.Entry<byte[], Integer> readRLEBytes(byte[] buf, int maxAllowed) 
{
+        return readRLEBytes(buf, 0, NumberUtils.length(buf), maxAllowed);
+    }
+
+    /**
+     * Decodes a run-length encoded byte array
+     *
+     * @param buf The buffer with the data bytes
+     * @param offset The offset in the buffer to decode the array
+     * @param available The max. available data starting from the offset
+     * @param maxChars Max. allowed data in decoded buffer - if more than
+     * that is encoded then an {@link IndexOutOfBoundsException} will be thrown
+     * @return The decoded data buffer + the offset of the next byte after it
+     */
+    static Map.Entry<byte[], Integer> readRLEBytes(byte[] buf, int offset, int 
available, int maxAllowed) {
+        int len = decodeInt(buf, offset, available);
+        if (len > maxAllowed) {
+            throw new IndexOutOfBoundsException("Requested block length (" + 
len + ") exceeds max. allowed (" + maxAllowed + ")");
+        }
+        if (len < 0) {
+            throw new IndexOutOfBoundsException("Negative block length 
requested: " + len);
+        }
+
+        available -= Integer.BYTES;
+        if (len > available) {
+            throw new IndexOutOfBoundsException("Requested block length (" + 
len + ") exceeds remaing (" + available + ")");
+        }
+
+        byte[] bytes = new byte[len];
+        offset += Integer.BYTES;
+        System.arraycopy(buf, offset, bytes, 0, len);
+        return new SimpleImmutableEntry<>(bytes, Integer.valueOf(offset + 
len));
+    }
+
+    static int decodeInt(byte[] buf) {
+        return decodeInt(buf, 0, NumberUtils.length(buf));
+    }
+
+    static int decodeInt(byte[] buf, int offset, int available) {
+        if (available < Integer.BYTES) {
+            throw new IndexOutOfBoundsException("Available data length (" + 
available + ") cannot accommodate integer encoding");
+        }
+
+        return ((buf[offset] & 0xFF) << 24)
+                | ((buf[offset + 1] & 0xFF) << 16)
+                | ((buf[offset + 2] & 0xFF) << 8)
+                | (buf[offset + 3] & 0xFF);
+    }
 }
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 4cabe0d..1c6dfbd 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
@@ -32,6 +32,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.NavigableMap;
 import java.util.Objects;
 import java.util.TreeMap;
@@ -119,6 +120,7 @@ public class PublicKeyEntry implements Serializable, 
KeyTypeIndicator {
     /**
      * @param session The {@link SessionContext} for invoking this load 
command - may
      * be {@code null} if not invoked within a session context (e.g., offline 
tool or session unknown).
+     * @param headers Any headers that may have been available when data was 
read
      * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if
      * none of the built-in ones can be used. If {@code null} and no built-in
      * resolver can be used then an {@link InvalidKeySpecException} is thrown.
@@ -128,8 +130,9 @@ public class PublicKeyEntry implements Serializable, 
KeyTypeIndicator {
      * @throws IOException              If failed to decode the key
      * @throws GeneralSecurityException If failed to generate the key
      */
-    public PublicKey resolvePublicKey(SessionContext session, 
PublicKeyEntryResolver fallbackResolver)
-            throws IOException, GeneralSecurityException {
+    public PublicKey resolvePublicKey(
+            SessionContext session, Map<String, String> headers, 
PublicKeyEntryResolver fallbackResolver)
+                throws IOException, GeneralSecurityException {
         String kt = getKeyType();
         PublicKeyEntryResolver decoder = KeyUtils.getPublicKeyEntryDecoder(kt);
         if (decoder == null) {
@@ -139,7 +142,7 @@ public class PublicKeyEntry implements Serializable, 
KeyTypeIndicator {
             throw new InvalidKeySpecException("No decoder available for key 
type=" + kt);
         }
 
-        return decoder.resolve(session, kt, getKeyData());
+        return decoder.resolve(session, kt, getKeyData(), headers);
     }
 
     /**
@@ -157,7 +160,7 @@ public class PublicKeyEntry implements Serializable, 
KeyTypeIndicator {
     public PublicKey appendPublicKey(
             SessionContext session, Appendable sb, PublicKeyEntryResolver 
fallbackResolver)
                 throws IOException, GeneralSecurityException {
-        PublicKey key = resolvePublicKey(session, fallbackResolver);
+        PublicKey key = resolvePublicKey(session, Collections.emptyMap(), 
fallbackResolver);
         if (key != null) {
             appendPublicKeyEntry(sb, key, resolvePublicKeyEntryDataResolver());
         }
@@ -225,7 +228,10 @@ public class PublicKeyEntry implements Serializable, 
KeyTypeIndicator {
 
         List<PublicKey> keys = new ArrayList<>(numEntries);
         for (PublicKeyEntry e : entries) {
-            PublicKey k = e.resolvePublicKey(session, fallbackResolver);
+            Map<String, String> headers = (e instanceof AuthorizedKeyEntry)
+                ? ((AuthorizedKeyEntry) e).getLoginOptions()
+                : Collections.emptyMap();
+            PublicKey k = e.resolvePublicKey(session, headers, 
fallbackResolver);
             if (k != null) {
                 keys.add(k);
             }
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 a1704a1..d7285fd 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
@@ -19,7 +19,6 @@
 
 package org.apache.sshd.common.config.keys;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -29,11 +28,11 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Collection;
+import java.util.Map;
 
 import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
@@ -44,48 +43,25 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends 
PrivateKey>
-        extends KeyEntryResolver<PUB, PRV>, PublicKeyEntryResolver {
+        extends KeyEntryResolver<PUB, PRV>, PublicKeyRawDataDecoder<PUB>, 
PublicKeyEntryResolver {
 
     @Override
-    default PublicKey resolve(SessionContext session, String keyType, byte[] 
keyData)
-            throws IOException, GeneralSecurityException {
+    default PublicKey resolve(
+            SessionContext session, String keyType, byte[] keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
         ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
         Collection<String> supported = getSupportedKeyTypes();
         if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) 
{
-            return decodePublicKey(session, keyType, keyData);
+            return decodePublicKey(session, keyType, keyData, headers);
         }
 
         throw new InvalidKeySpecException("resolve(" + keyType + ") not in 
listed supported types: " + supported);
     }
 
-    /**
-     * @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).
-     * @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(SessionContext session, String keyType, 
byte... keyData)
-            throws IOException, GeneralSecurityException {
-        return decodePublicKey(session, keyType, keyData, 0, 
NumberUtils.length(keyData));
-    }
-
-    default PUB decodePublicKey(SessionContext session, 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 decodePublicKeyByType(session, keyType, stream);
-        }
-    }
-
-    default PUB decodePublicKeyByType(SessionContext session, String keyType, 
InputStream keyData)
-            throws IOException, GeneralSecurityException {
+    @Override
+    default PUB 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
         String type = KeyEntryResolver.decodeString(keyData, 
KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH);
         if (GenericUtils.isEmpty(type)) {
@@ -97,23 +73,10 @@ public interface PublicKeyEntryDecoder<PUB extends 
PublicKey, PRV extends Privat
             throw new InvalidKeySpecException("Reported key type (" + type + 
") not in supported list: " + supported);
         }
 
-        return decodePublicKey(session, type, keyData);
+        return decodePublicKey(session, type, keyData, headers);
     }
 
     /**
-     * @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).
-     * @param keyType The reported / encode key type
-     * @param keyData The key data bytes stream positioned after the key type 
decoding
-     *                and making sure it is one of the supported types
-     * @return The decoded {@link 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)
-        throws IOException, GeneralSecurityException;
-
-    /**
      * Encodes the {@link PublicKey} using the {@code OpenSSH} format - same
      * one used by the {@code decodePublicKey} method(s)
      *
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
index 5875251..f94665e 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
+import java.util.Map;
 
 import org.apache.sshd.common.session.SessionContext;
 
@@ -36,7 +37,7 @@ public interface PublicKeyEntryResolver {
      */
     PublicKeyEntryResolver IGNORING = new PublicKeyEntryResolver() {
         @Override
-        public PublicKey resolve(SessionContext session, String keyType, 
byte[] keyData)
+        public PublicKey resolve(SessionContext session, String keyType, 
byte[] keyData, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
             return null;
         }
@@ -52,7 +53,7 @@ public interface PublicKeyEntryResolver {
      */
     PublicKeyEntryResolver FAILING = new PublicKeyEntryResolver() {
         @Override
-        public PublicKey resolve(SessionContext session, String keyType, 
byte[] keyData)
+        public PublicKey resolve(SessionContext session, String keyType, 
byte[] keyData, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
             throw new InvalidKeySpecException("Failing resolver on key type=" 
+ keyType);
         }
@@ -68,10 +69,11 @@ public interface PublicKeyEntryResolver {
      * be {@code null} if not invoked within a session context (e.g., offline 
tool or session unknown).
      * @param keyType The {@code OpenSSH} reported key type
      * @param keyData The {@code OpenSSH} encoded key data
+     * @param headers Any headers that may have been available when data was 
read
      * @return The extracted {@link PublicKey} - ignored if {@code null}
      * @throws IOException If failed to parse the key data
      * @throws GeneralSecurityException If failed to generate the key
      */
-    PublicKey resolve(SessionContext session, String keyType, byte[] keyData)
+    PublicKey resolve(SessionContext session, String keyType, byte[] keyData, 
Map<String, String> headers)
         throws IOException, GeneralSecurityException;
 }
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
new file mode 100644
index 0000000..d1e345b
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java
@@ -0,0 +1,82 @@
+/*
+ * 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.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.Map;
+
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.NumberUtils;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyRawDataDecoder<PUB extends PublicKey> {
+    /**
+     * @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).
+     * @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
+     * @param headers Any headers that may have been available when data was 
read
+     * @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(
+            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(
+            SessionContext session, String keyType, byte[] keyData, int 
offset, int length, Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
+        if (length <= 0) {
+            return null;
+        }
+
+        try (InputStream stream = new ByteArrayInputStream(keyData, offset, 
length)) {
+            return decodePublicKeyByType(session, keyType, stream, headers);
+        }
+    }
+
+    PUB decodePublicKeyByType(
+        SessionContext session, String keyType, InputStream keyData, 
Map<String, String> headers)
+            throws IOException, GeneralSecurityException;
+
+    /**
+     * @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).
+     * @param keyType The reported / encode key type
+     * @param keyData The key data bytes stream positioned after the key type 
decoding
+     * and making sure it is one of the supported types
+     * @param headers Any headers that may have been available when data was 
read
+     * @return The decoded {@link 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)
+        throws IOException, GeneralSecurityException;
+}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataReader.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataReader.java
new file mode 100644
index 0000000..3567439
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataReader.java
@@ -0,0 +1,120 @@
+/*
+ * 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.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.resource.IoResource;
+import org.apache.sshd.common.util.io.resource.PathResource;
+import org.apache.sshd.common.util.io.resource.URLResource;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyRawDataReader<PUB extends PublicKey> {
+    default PUB readPublicKey(SessionContext session, Path path, OpenOption... 
options)
+            throws IOException, GeneralSecurityException {
+        return readPublicKey(session, path, StandardCharsets.UTF_8, options);
+    }
+
+    default PUB readPublicKey(
+            SessionContext session, Path path, Charset cs, OpenOption... 
options)
+                throws IOException, GeneralSecurityException {
+        return readPublicKey(session, new PathResource(path, options), cs);
+    }
+
+    default PUB readPublicKey(SessionContext session, URL url)
+            throws IOException, GeneralSecurityException {
+        return readPublicKey(session, url, StandardCharsets.UTF_8);
+    }
+
+    default PUB readPublicKey(SessionContext session, URL url, Charset cs)
+            throws IOException, GeneralSecurityException {
+        return readPublicKey(session, new URLResource(url), cs);
+    }
+
+    default PUB readPublicKey(SessionContext session, IoResource<?> resource)
+            throws IOException, GeneralSecurityException {
+        return readPublicKey(session, resource, StandardCharsets.UTF_8);
+    }
+
+    default PUB readPublicKey(
+            SessionContext session, IoResource<?> resource, Charset cs)
+                throws IOException, GeneralSecurityException {
+        try (InputStream stream = Objects.requireNonNull(resource, "No 
resource data").openInputStream()) {
+            return readPublicKey(session, resource, stream, cs);
+        }
+    }
+
+    default PUB readPublicKey(
+            SessionContext session, NamedResource resourceKey, InputStream 
stream)
+                throws IOException, GeneralSecurityException {
+        return readPublicKey(session, resourceKey, stream, 
StandardCharsets.UTF_8);
+    }
+
+    default PUB readPublicKey(
+            SessionContext session, NamedResource resourceKey, InputStream 
stream, Charset cs)
+                throws IOException, GeneralSecurityException {
+        try (Reader reader = new InputStreamReader(
+                Objects.requireNonNull(stream, "No stream instance"), 
Objects.requireNonNull(cs, "No charset"))) {
+            return readPublicKey(session, resourceKey, reader);
+        }
+    }
+
+    default PUB readPublicKey(
+            SessionContext session, NamedResource resourceKey, Reader rdr)
+                throws IOException, GeneralSecurityException {
+        try (BufferedReader br = new 
BufferedReader(Objects.requireNonNull(rdr, "No reader instance"), 
IoUtils.DEFAULT_COPY_SIZE)) {
+            return readPublicKey(session, resourceKey, br);
+        }
+    }
+
+    default PUB readPublicKey(
+            SessionContext session, NamedResource resourceKey, BufferedReader 
rdr)
+                throws IOException, GeneralSecurityException {
+        List<String> lines = IoUtils.readAllLines(rdr);
+        try {
+            return readPublicKey(session, resourceKey, lines);
+        } finally {
+            lines.clear();   // clean up sensitive data a.s.a.p.
+        }
+    }
+
+    PUB readPublicKey(SessionContext session, NamedResource resourceKey, 
List<String> lines)
+        throws IOException, GeneralSecurityException;
+}
+
+
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 d429224..f57634b 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
@@ -34,6 +34,7 @@ import java.security.spec.DSAPrivateKeySpec;
 import java.security.spec.DSAPublicKeySpec;
 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;
@@ -53,8 +54,9 @@ public class DSSPublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecoder<DSAP
     }
 
     @Override
-    public DSAPublicKey decodePublicKey(SessionContext session, String 
keyType, InputStream keyData)
-            throws IOException, GeneralSecurityException {
+    public DSAPublicKey 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
             throw new InvalidKeySpecException("Unexpected key type: " + 
keyType);
         }
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 24471ce..877b696 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
@@ -35,6 +35,7 @@ import java.security.spec.ECPoint;
 import java.security.spec.ECPrivateKeySpec;
 import java.security.spec.ECPublicKeySpec;
 import java.security.spec.InvalidKeySpecException;
+import java.util.Map;
 import java.util.Objects;
 
 import org.apache.sshd.common.cipher.ECCurves;
@@ -64,8 +65,9 @@ public class ECDSAPublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecoder<EC
     }
 
     @Override
-    public ECPublicKey decodePublicKey(SessionContext session, String keyType, 
InputStream keyData)
-            throws IOException, GeneralSecurityException {
+    public ECPublicKey decodePublicKey(
+            SessionContext session, String keyType, InputStream keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
         ECCurves curve = ECCurves.fromKeyType(keyType);
         if (curve == null) {
             throw new InvalidKeySpecException("Not an EC curve name: " + 
keyType);
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 9c2730d..1de4ce0 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
@@ -35,6 +35,7 @@ import java.security.spec.RSAPrivateCrtKeySpec;
 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;
@@ -59,8 +60,9 @@ public class RSAPublicKeyDecoder extends 
AbstractPublicKeyEntryDecoder<RSAPublic
     }
 
     @Override
-    public RSAPublicKey decodePublicKey(SessionContext session, String 
keyType, InputStream keyData)
-            throws IOException, GeneralSecurityException {
+    public RSAPublicKey decodePublicKey(
+            SessionContext session, String keyType, InputStream keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
         // Not really required, but allow it
         String canonicalName = KeyUtils.getCanonicalKeyType(keyType);
         if (!KeyPairProvider.SSH_RSA.equals(canonicalName)) { // just in case 
we were invoked directly
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
index 457b0e4..5d0fae0 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
@@ -25,6 +25,7 @@ import java.io.InputStream;
 import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
+import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -109,8 +110,15 @@ public abstract class AbstractKeyPairResourceParser 
extends AbstractLoggingBean
 
             int endIndex = markerPos.getKey();
             String endLine = lines.get(endIndex);
-            Collection<KeyPair> kps = extractKeyPairs(session, resourceKey,
-                startLine, endLine, passwordProvider, 
lines.subList(startIndex, endIndex));
+            Map.Entry<? extends Map<String, String>, ? extends List<String>> 
result =
+                separateDataLinesFromHeaders(
+                    session, resourceKey, startLine, endLine, 
lines.subList(startIndex, endIndex));
+            Map<String, String> headers = result.getKey();
+            List<String> dataLines = result.getValue();
+            Collection<KeyPair> kps = extractKeyPairs(
+                session, resourceKey, startLine, endLine, passwordProvider,
+                (dataLines == null) ? Collections.emptyList() : dataLines,
+                (headers == null) ? Collections.emptyMap() : headers);
             if (GenericUtils.isNotEmpty(kps)) {
                 if (GenericUtils.isEmpty(keyPairs)) {
                     keyPairs = new LinkedList<>(kps);
@@ -126,6 +134,12 @@ public abstract class AbstractKeyPairResourceParser 
extends AbstractLoggingBean
         return keyPairs;
     }
 
+    protected Map.Entry<Map<String, String>, List<String>> 
separateDataLinesFromHeaders(
+            SessionContext session, NamedResource resourceKey, String 
startLine, String endLine, List<String> dataLines)
+                throws IOException, GeneralSecurityException {
+        return new SimpleImmutableEntry<>(Collections.emptyMap(), dataLines);
+    }
+
     /**
      * Extracts the key pairs within a <U>single</U> delimited by markers 
block of lines. By
      * default cleans up the empty lines, joins them and converts them from 
BASE64
@@ -138,6 +152,7 @@ public abstract class AbstractKeyPairResourceParser extends 
AbstractLoggingBean
      * @param passwordProvider The {@link FilePasswordProvider} to use
      * in case the data is encrypted - may be {@code null} if no encrypted
      * @param lines The block of lines between the markers
+     * @param headers Any headers that may have been available when data was 
read
      * @return The extracted {@link KeyPair}s - may be {@code null}/empty if 
none.
      * @throws IOException If failed to parse the data
      * @throws GeneralSecurityException If failed to generate the keys
@@ -146,11 +161,11 @@ public abstract class AbstractKeyPairResourceParser 
extends AbstractLoggingBean
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            List<String> lines)
+            List<String> lines, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines);
         try {
-            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, dataBytes);
+            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, dataBytes, headers);
         } finally {
             Arrays.fill(dataBytes, (byte) 0);   // clean up sensitive data 
a.s.a.p.
         }
@@ -165,6 +180,7 @@ public abstract class AbstractKeyPairResourceParser extends 
AbstractLoggingBean
      * @param passwordProvider The {@link FilePasswordProvider} to use
      * in case the data is encrypted - may be {@code null} if no encrypted
      * @param bytes The decoded bytes from the lines containing the data
+     * @param headers Any headers that may have been available when data was 
read
      * @return The extracted {@link KeyPair}s - may be {@code null}/empty if 
none.
      * @throws IOException If failed to parse the data
      * @throws GeneralSecurityException If failed to generate the keys
@@ -173,14 +189,14 @@ public abstract class AbstractKeyPairResourceParser 
extends AbstractLoggingBean
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            byte[] bytes)
+            byte[] bytes, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         if (log.isTraceEnabled()) {
             BufferUtils.dumpHex(getSimplifiedLogger(), Level.FINER, 
beginMarker, ':', 16, bytes);
         }
 
         try (InputStream bais = new ByteArrayInputStream(bytes)) {
-            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, bais);
+            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, bais, headers);
         }
     }
 
@@ -193,6 +209,7 @@ public abstract class AbstractKeyPairResourceParser extends 
AbstractLoggingBean
      * @param passwordProvider The {@link FilePasswordProvider} to use
      * in case the data is encrypted - may be {@code null} if no encrypted
      * @param stream The decoded data {@link InputStream}
+     * @param headers Any headers that may have been available when data was 
read
      * @return The extracted {@link KeyPair}s - may be {@code null}/empty if 
none.
      * @throws IOException If failed to parse the data
      * @throws GeneralSecurityException If failed to generate the keys
@@ -200,6 +217,7 @@ public abstract class AbstractKeyPairResourceParser extends 
AbstractLoggingBean
     public abstract Collection<KeyPair> extractKeyPairs(
         SessionContext session, NamedResource resourceKey,
         String beginMarker, String endMarker,
-        FilePasswordProvider passwordProvider, InputStream stream)
+        FilePasswordProvider passwordProvider,
+        InputStream stream, Map<String, String> headers)
             throws IOException, GeneralSecurityException;
 }
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 0144d57..7d22579 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
@@ -106,7 +106,7 @@ public class OpenSSHKeyPairResourceParser extends 
AbstractKeyPairResourceParser
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            InputStream stream)
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         boolean debugEnabled = log.isDebugEnabled();
 
@@ -114,7 +114,7 @@ public class OpenSSHKeyPairResourceParser extends 
AbstractKeyPairResourceParser
 
         String cipher = KeyEntryResolver.decodeString(stream, 
MAX_CIPHER_NAME_LENGTH);
         OpenSSHKdfOptions kdfOptions =
-            resolveKdfOptions(session, resourceKey, beginMarker, endMarker, 
stream);
+            resolveKdfOptions(session, resourceKey, beginMarker, endMarker, 
stream, headers);
         OpenSSHParserContext context = new OpenSSHParserContext(cipher, 
kdfOptions);
         int numKeys = KeyEntryResolver.decodeInt(stream);
         if (numKeys <= 0) {
@@ -131,7 +131,7 @@ public class OpenSSHKeyPairResourceParser extends 
AbstractKeyPairResourceParser
         List<PublicKey> publicKeys = new ArrayList<>(numKeys);
         boolean traceEnabled = log.isTraceEnabled();
         for (int index = 1; index <= numKeys; index++) {
-            PublicKey pubKey = readPublicKey(session, resourceKey, context, 
stream);
+            PublicKey pubKey = readPublicKey(session, resourceKey, context, 
stream, headers);
             ValidateUtils.checkNotNull(pubKey, "Empty public key #%d in %s", 
index, resourceKey);
             if (traceEnabled) {
                 log.trace("extractKeyPairs({}) read public key #{}: {} {}",
@@ -198,7 +198,7 @@ public class OpenSSHKeyPairResourceParser extends 
AbstractKeyPairResourceParser
 
     protected OpenSSHKdfOptions resolveKdfOptions(
             SessionContext session, NamedResource resourceKey,
-            String beginMarker, String endMarker, InputStream stream)
+            String beginMarker, String endMarker, InputStream stream, 
Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         String kdfName = KeyEntryResolver.decodeString(stream, 
OpenSSHKdfOptions.MAX_KDF_NAME_LENGTH);
         byte[] kdfOptions = KeyEntryResolver.readRLEBytes(stream, 
OpenSSHKdfOptions.MAX_KDF_OPTIONS_SIZE);
@@ -215,7 +215,9 @@ public class OpenSSHKeyPairResourceParser extends 
AbstractKeyPairResourceParser
     }
 
     protected PublicKey readPublicKey(
-            SessionContext session, NamedResource resourceKey, 
OpenSSHParserContext context, InputStream stream)
+            SessionContext session, NamedResource resourceKey,
+            OpenSSHParserContext context,
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         byte[] keyData = KeyEntryResolver.readRLEBytes(stream, 
MAX_PUBLIC_KEY_DATA_SIZE);
         try (InputStream bais = new ByteArrayInputStream(keyData)) {
@@ -225,7 +227,7 @@ public class OpenSSHKeyPairResourceParser extends 
AbstractKeyPairResourceParser
                 throw new NoSuchAlgorithmException("Unsupported key type (" + 
keyType + ") in " + resourceKey);
             }
 
-            return decoder.decodePublicKey(session, keyType, bais);
+            return decoder.decodePublicKey(session, keyType, bais, headers);
         }
     }
 
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
index 95438e5..75cfa64 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
@@ -30,6 +30,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import javax.security.auth.login.CredentialException;
 import javax.security.auth.login.FailedLoginException;
@@ -79,7 +81,7 @@ public abstract class AbstractPEMResourceKeyPairParser
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            List<String> lines)
+            List<String> lines, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         if (GenericUtils.isEmpty(lines)) {
             return Collections.emptyList();
@@ -89,6 +91,7 @@ public abstract class AbstractPEMResourceKeyPairParser
         byte[] initVector = null;
         String algInfo = null;
         int dataStartIndex = -1;
+        boolean hdrsAvailable = GenericUtils.isNotEmpty(headers);
         for (int index = 0; index < lines.size(); index++) {
             String line = GenericUtils.trimToEmpty(lines.get(index));
             if (GenericUtils.isEmpty(line)) {
@@ -102,28 +105,36 @@ public abstract class AbstractPEMResourceKeyPairParser
                 break;
             }
 
-            if (line.startsWith("Proc-Type:")) {
+            String hdrName = line.substring(0, headerPos).trim();
+            String hdrValue = line.substring(headerPos + 1).trim();
+            if (!hdrsAvailable) {
+                Map<String, String> accHeaders = GenericUtils.isEmpty(headers)
+                    ? new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
+                    : headers;
+                accHeaders.put(hdrName, hdrValue);
+            }
+
+            if (hdrName.equalsIgnoreCase("Proc-Type")) {
                 if (encrypted != null) {
                     throw new StreamCorruptedException("Multiple encryption 
indicators in " + resourceKey);
                 }
 
-                line = line.substring(headerPos + 1).trim();
-                line = line.toUpperCase();
+                hdrValue = hdrValue.toUpperCase();
                 encrypted = Boolean.valueOf(line.contains("ENCRYPTED"));
-            } else if (line.startsWith("DEK-Info:")) {
+            } else if (hdrName.equalsIgnoreCase("DEK-Info")) {
                 if ((initVector != null) || (algInfo != null)) {
                     throw new StreamCorruptedException("Multiple encryption 
settings in " + resourceKey);
                 }
 
-                line = line.substring(headerPos + 1).trim();
-                headerPos = line.indexOf(',');
-                if (headerPos < 0) {
-                    throw new StreamCorruptedException(resourceKey + ": 
Missing encryption data values separator in line '" + line + "'");
+                int infoPos = hdrValue.indexOf(',');
+                if (infoPos < 0) {
+                    throw new StreamCorruptedException(
+                        resourceKey + ": Missing encryption data values 
separator in line '" + line + "'");
                 }
 
-                algInfo = line.substring(0, headerPos).trim();
+                algInfo = hdrValue.substring(0, infoPos).trim();
 
-                String algInitVector = line.substring(headerPos + 1).trim();
+                String algInitVector = hdrValue.substring(infoPos + 1).trim();
                 initVector = 
BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, algInitVector);
             }
         }
@@ -156,7 +167,7 @@ public abstract class AbstractPEMResourceKeyPairParser
                         encryptedData = 
KeyPairResourceParser.extractDataBytes(dataLines);
                         decodedData = applyPrivateKeyCipher(encryptedData, 
encContext, false);
                         try (InputStream bais = new 
ByteArrayInputStream(decodedData)) {
-                            keys = extractKeyPairs(session, resourceKey, 
beginMarker, endMarker, passwordProvider, bais);
+                            keys = extractKeyPairs(session, resourceKey, 
beginMarker, endMarker, passwordProvider, bais, headers);
                         }
                     } finally {
                         Arrays.fill(encryptedData, (byte) 0); // get rid of 
sensitive data a.s.a.p.
@@ -188,7 +199,7 @@ public abstract class AbstractPEMResourceKeyPairParser
             }
         }
 
-        return super.extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, dataLines);
+        return super.extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, dataLines, headers);
     }
 
     protected byte[] applyPrivateKeyCipher(
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
index df645e1..220f357 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
@@ -33,6 +33,7 @@ import java.security.spec.DSAPublicKeySpec;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
@@ -73,7 +74,7 @@ public class DSSPEMResourceKeyPairParser extends 
AbstractPEMResourceKeyPairParse
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            InputStream stream)
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         KeyPair kp = 
decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, 
false);
         return Collections.singletonList(kp);
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
index 17b69a8..50c19e3 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
@@ -77,7 +77,7 @@ public class ECDSAPEMResourceKeyPairParser extends 
AbstractPEMResourceKeyPairPar
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            InputStream stream)
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = 
decodeECPrivateKeySpec(stream, false);
         if (!SecurityUtils.isECCSupported()) {
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 c755ea8..7fb995e 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
@@ -32,6 +32,7 @@ import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
@@ -70,7 +71,7 @@ public class PKCS8PEMResourceKeyPairParser extends 
AbstractPEMResourceKeyPairPar
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            InputStream stream)
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         // Save the data before getting the algorithm OID since we will need it
         byte[] encBytes = IoUtils.toByteArray(stream);
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
index b830063..8a227a8 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
@@ -34,6 +34,7 @@ import java.security.spec.RSAPublicKeySpec;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
@@ -74,7 +75,7 @@ public class RSAPEMResourceKeyPairParser extends 
AbstractPEMResourceKeyPairParse
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            InputStream stream)
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         KeyPair kp = 
decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, 
false);
         return Collections.singletonList(kp);
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
new file mode 100644
index 0000000..3d8141f
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java
@@ -0,0 +1,224 @@
+/*
+ * 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.loader.ssh2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyTypeNamesSupport;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.config.keys.PublicKeyRawDataDecoder;
+import org.apache.sshd.common.config.keys.PublicKeyRawDataReader;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Decodes a public key file encoded according to
+ * <A HREF="https://tools.ietf.org/html/rfc4716";>The Secure Shell (SSH) Public 
Key File Format</A>
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class Ssh2PublicKeyEntryDecoder
+        implements PublicKeyRawDataDecoder<PublicKey>, PublicKeyEntryResolver,
+        PublicKeyRawDataReader<PublicKey>, KeyTypeNamesSupport {
+    public static final NavigableSet<String> SUPPORTED_KEY_TYPES =
+        Collections.unmodifiableNavigableSet(
+            GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
+                KeyPairProvider.SSH_RSA, KeyPairProvider.SSH_DSS, 
KeyPairProvider.SSH_ED25519,
+                KeyPairProvider.ECDSA_SHA2_NISTP256, 
KeyPairProvider.ECDSA_SHA2_NISTP384, KeyPairProvider.ECDSA_SHA2_NISTP521));
+
+    public static final String BEGIN_MARKER = "BEGIN SSH2 PUBLIC KEY";
+    public static final List<String> START_MARKERS = 
Collections.singletonList(BEGIN_MARKER);
+
+    public static final String END_MARKER = "END SSH2 PUBLIC KEY";
+    public static final List<String> STOP_MARKERS = 
Collections.singletonList(END_MARKER);
+
+    /**
+     * According to <A 
HREF="https://tools.ietf.org/html/rfc4716#section-3.3";>RFC-4716 section 3.3</A>:
+     *
+     * <P><code>
+     *      A line is continued if the last character in the line is a 
&quot;\&quot;.  If
+     *      the last character of a line is a &quot;\&quot;, then the logical 
contents of
+     *      the line are formed by removing the &quot;\&quot; and the line 
termination
+     *      characters, and appending the contents of the next line.
+     * </code></P>
+     */
+    public static final char HEADER_CONTINUATION_INDICATOR = '\\';
+
+    public static final Ssh2PublicKeyEntryDecoder INSTANCE = new 
Ssh2PublicKeyEntryDecoder();
+
+    public Ssh2PublicKeyEntryDecoder() {
+        super();
+    }
+
+    @Override
+    public NavigableSet<String> getSupportedKeyTypes() {
+        return SUPPORTED_KEY_TYPES;
+    }
+
+    @Override
+    public PublicKey resolve(
+            SessionContext session, String keyType, byte[] keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
+        ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
+        Collection<String> supported = getSupportedKeyTypes();
+        if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) 
{
+            return decodePublicKey(session, keyType, keyData, headers);
+        }
+
+        throw new InvalidKeySpecException("resolve(" + keyType + ") not in 
listed supported types: " + supported);
+    }
+
+    @Override
+    public PublicKey decodePublicKey(
+            SessionContext session, String keyType, InputStream keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
+        return decodePublicKeyByType(session, keyType, keyData, headers);
+    }
+
+    @Override
+    public PublicKey decodePublicKeyByType(
+            SessionContext session, String keyType, InputStream keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
+        PublicKeyEntryDecoder<?, ?> decoder = 
KeyUtils.getPublicKeyEntryDecoder(keyType);
+        if (decoder == null) {
+            throw new InvalidKeySpecException("No decoder for key type=" + 
keyType);
+        }
+
+        return decoder.decodePublicKeyByType(session, keyType, keyData, 
headers);
+    }
+
+    @Override
+    public PublicKey readPublicKey(SessionContext session, NamedResource 
resourceKey, List<String> lines)
+            throws IOException, GeneralSecurityException {
+        Map.Entry<Integer, Integer> markerPos = 
KeyPairResourceParser.findMarkerLine(lines, START_MARKERS);
+        if (markerPos == null) {
+            return null;    // be lenient
+        }
+
+        int startIndex = markerPos.getKey();
+        String startLine = lines.get(startIndex);
+        startIndex++;      // skip the starting marker
+
+        markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, 
STOP_MARKERS);
+        if (markerPos == null) {
+            throw new StreamCorruptedException("Missing end marker (" + 
END_MARKER + ") after line #" + startIndex);
+        }
+
+        int endIndex = markerPos.getKey();
+        String endLine = lines.get(endIndex);
+        Map.Entry<? extends Map<String, String>, ? extends List<String>> 
result =
+            separateDataLinesFromHeaders(
+                session, resourceKey, startLine, endLine, 
lines.subList(startIndex, endIndex));
+        Map<String, String> headers = result.getKey();
+        List<String> dataLines = result.getValue();
+        return readPublicKey(session, resourceKey, BEGIN_MARKER, END_MARKER,
+            (dataLines == null) ? Collections.emptyList() : dataLines,
+            (headers == null) ? Collections.emptyMap() : headers);
+    }
+
+    public PublicKey readPublicKey(
+            SessionContext session, NamedResource resourceKey,
+            String beginMarker, String endMarker,
+            List<String> lines, Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
+        byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines);
+        try {
+            return readPublicKey(session, resourceKey, beginMarker, endMarker, 
dataBytes, headers);
+        } finally {
+            Arrays.fill(dataBytes, (byte) 0);   // clean up sensitive data 
a.s.a.p.
+        }
+    }
+
+    public PublicKey readPublicKey(
+            SessionContext session, NamedResource resourceKey,
+            String beginMarker, String endMarker,
+            byte[] dataBytes, Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
+        Map.Entry<String, Integer> result =
+            KeyEntryResolver.decodeString(dataBytes, 
KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH);
+        String keyType = result.getKey();
+        return resolve(session, keyType, dataBytes, headers);
+    }
+
+    protected Map.Entry<Map<String, String>, List<String>> 
separateDataLinesFromHeaders(
+            SessionContext session, NamedResource resourceKey, String 
startLine, String endLine, List<String> lines)
+                throws IOException, GeneralSecurityException {
+        // According to RFC-4716: The Header-tag is case-insensitive
+        Map<String, String> headers = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        int len = lines.size();
+        for (int index = 0; index < len; index++) {
+            String l = lines.get(index);
+            l = l.trim();
+            if (l.isEmpty()) {
+                continue;
+            }
+
+            int pos = l.indexOf(':');
+            // assume all the rest are data lines
+            if (pos < 0) {
+                return new SimpleImmutableEntry<>(headers, 
lines.subList(index, len));
+            }
+
+            String name = l.substring(0, pos).trim();
+            String value = l.substring(pos + 1).trim();
+            int vLen = value.length();
+            if (value.charAt(vLen - 1) == HEADER_CONTINUATION_INDICATOR) {
+                value = value.substring(0, vLen - 1);
+                for (index++ /* skip current line */; index < len; index++) {
+                    l = lines.get(index);
+                    vLen = l.length();
+
+                    if (l.charAt(vLen - 1) == HEADER_CONTINUATION_INDICATOR) {
+                        value += l.substring(0, vLen - 1);
+                        continue;   // still continuation
+                    }
+
+                    value += l;
+                    break;  // no more continuations
+                }
+            }
+
+            headers.put(name, value.trim());
+        }
+
+        throw new StreamCorruptedException(
+            "No viable data lines found in " + resourceKey.getName() + " after 
" + startLine);
+    }
+}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
index 5cfe631..de6abde 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
@@ -247,15 +247,15 @@ public final class NumberUtils {
     }
 
     public static int length(byte... a) {
-        return a == null ? 0 : a.length;
+        return (a == null) ? 0 : a.length;
     }
 
     public static int length(int... a) {
-        return a == null ? 0 : a.length;
+        return (a == null) ? 0 : a.length;
     }
 
     public static int length(long... a) {
-        return a == null ? 0 : a.length;
+        return (a == null) ? 0 : a.length;
     }
 
     public static List<Integer> asList(int... values) {
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java
index 7709606..2bf2042 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java
@@ -32,6 +32,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import javax.security.auth.login.CredentialException;
 import javax.security.auth.login.FailedLoginException;
@@ -81,7 +82,7 @@ public class BouncyCastleKeyPairResourceParser extends 
AbstractKeyPairResourcePa
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            List<String> lines)
+            List<String> lines, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         StringBuilder writer = new StringBuilder(beginMarker.length() + 
endMarker.length() + lines.size() * 80);
         writer.append(beginMarker).append(IoUtils.EOL);
@@ -91,7 +92,7 @@ public class BouncyCastleKeyPairResourceParser extends 
AbstractKeyPairResourcePa
         String data = writer.toString();
         byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
         try (InputStream bais = new ByteArrayInputStream(dataBytes)) {
-            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, bais);
+            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, bais, headers);
         }
     }
 
@@ -100,7 +101,7 @@ public class BouncyCastleKeyPairResourceParser extends 
AbstractKeyPairResourcePa
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            InputStream stream)
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         KeyPair kp = loadKeyPair(session, resourceKey, stream, 
passwordProvider);
         return (kp == null) ? Collections.emptyList() : 
Collections.singletonList(kp);
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 ad7d92e..9c57d2c 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
@@ -25,6 +25,7 @@ import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
@@ -91,8 +92,9 @@ public final class Ed25519PublicKeyDecoder extends 
AbstractPublicKeyEntryDecoder
     }
 
     @Override
-    public EdDSAPublicKey decodePublicKey(SessionContext session, String 
keyType, InputStream keyData)
-            throws IOException, GeneralSecurityException {
+    public EdDSAPublicKey decodePublicKey(
+            SessionContext session, String keyType, InputStream keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
         byte[] seed = KeyEntryResolver.readRLEBytes(keyData, 
MAX_ALLOWED_SEED_LEN);
         return 
EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed));
     }
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java
index a11c32f..a6e442d 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java
@@ -25,6 +25,7 @@ import java.security.PublicKey;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 
 import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
 import org.apache.sshd.util.test.JUnitTestSupport;
@@ -71,7 +72,7 @@ public class KeyUtilsFingerprintCaseSensitivityTest extends 
JUnitTestSupport {
     @BeforeClass
     public static void beforeClass() throws GeneralSecurityException, 
IOException {
         PublicKeyEntry keyEntry = 
PublicKeyEntry.parsePublicKeyEntry(KEY_STRING);
-        key = keyEntry.resolvePublicKey(null, PublicKeyEntryResolver.FAILING);
+        key = keyEntry.resolvePublicKey(null, Collections.emptyMap(), 
PublicKeyEntryResolver.FAILING);
     }
 
     @Parameters(name = "expected={0}, test={1}")
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java
index 968a405..73684dd 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java
@@ -119,7 +119,8 @@ public class KeyUtilsFingerprintGenerationTest extends 
JUnitTestSupport {
             String keyValue = kentry.getKey();
             try {
                 PublicKeyEntry keyEntry = 
PublicKeyEntry.parsePublicKeyEntry(keyValue);
-                PublicKey key = keyEntry.resolvePublicKey(null, 
PublicKeyEntryResolver.FAILING);
+                PublicKey key = keyEntry.resolvePublicKey(
+                    null, Collections.emptyMap(), 
PublicKeyEntryResolver.FAILING);
                 for (Map.Entry<DigestFactory, String> dentry : 
kentry.getValue()) {
                     DigestFactory factory = dentry.getKey();
                     String fingerprint = dentry.getValue();
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java
index 45c8921..c82a33d 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java
@@ -23,6 +23,7 @@ import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
+import java.util.Collections;
 
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
@@ -51,7 +52,7 @@ public class PublicKeyEntryTest extends JUnitTestSupport {
         for (PublicKeyEntryResolver resolver : new PublicKeyEntryResolver[]{
             null, PublicKeyEntryResolver.FAILING, 
PublicKeyEntryResolver.IGNORING}) {
             try {
-                PublicKey key = entry.resolvePublicKey(null, resolver);
+                PublicKey key = entry.resolvePublicKey(null, 
Collections.emptyMap(), resolver);
                 assertSame("Mismatched successful resolver", 
PublicKeyEntryResolver.IGNORING, resolver);
                 assertNull("Unexpected success for resolver=" + resolver + ": 
" + KeyUtils.getFingerPrint(key), key);
             } catch (GeneralSecurityException e) {
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java
index 310be4e..d268f75 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java
@@ -23,6 +23,7 @@ import java.net.URL;
 import java.security.KeyPair;
 import java.security.PublicKey;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
@@ -84,7 +85,8 @@ public abstract class OpenSSHKeyPairResourceParserTestSupport 
extends JUnitTestS
         assertEquals("Mismatched public keys count", 1, 
GenericUtils.size(entries));
 
         AuthorizedKeyEntry entry = entries.get(0);
-        PublicKey pubEntry = entry.resolvePublicKey(null, 
PublicKeyEntryResolver.FAILING);
+        PublicKey pubEntry = entry.resolvePublicKey(
+            null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING);
         assertNotNull("Cannot retrieve public key", pubEntry);
 
         testLoadKeyPairs(encrypted, resourceKey, pairs, pubEntry);
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderByKeyTypeTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderByKeyTypeTest.java
new file mode 100644
index 0000000..64c94be
--- /dev/null
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderByKeyTypeTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.loader.ssh2;
+
+import java.io.InputStream;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+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.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see 
https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class Ssh2PublicKeyEntryDecoderByKeyTypeTest extends JUnitTestSupport {
+    private final String keyType;
+
+    public Ssh2PublicKeyEntryDecoderByKeyTypeTest(String keyType) {
+        this.keyType = keyType;
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return new ArrayList<Object[]>() {
+            // Not serializing it
+            private static final long serialVersionUID = 1L;
+
+            {
+                addTestCases(Arrays.asList(KeyPairProvider.SSH_RSA, 
KeyPairProvider.SSH_DSS));
+                if (SecurityUtils.isECCSupported()) {
+                    addTestCases(ECCurves.KEY_TYPES);
+                }
+                if (SecurityUtils.isEDDSACurveSupported()) {
+                    addKey(KeyPairProvider.SSH_ED25519);
+                }
+            }
+
+            private void addTestCases(Collection<String> keys) {
+                for (String k : keys) {
+                    addKey(k);
+                }
+            }
+
+            private void addKey(String k) {
+                add(new Object[] {k});
+            }
+        };
+    }
+
+    @Test
+    public void testDecodePublicKey() throws Exception {
+        PublicKey expected;
+        try (InputStream keyData = getPublicKeyDataStream("pub")) {
+            Collection<? extends PublicKeyEntry> entries =
+                AuthorizedKeyEntry.readAuthorizedKeys(keyData, true);
+            List<PublicKey> keys =
+                PublicKeyEntry.resolvePublicKeyEntries(null, entries, null);
+            assertEquals("Mismatched expected public entries count", 1, 
GenericUtils.size(keys));
+
+            expected = keys.get(0);
+        }
+
+        PublicKey actual;
+        try (InputStream keyData = getPublicKeyDataStream("ssh2")) {
+            actual = Ssh2PublicKeyEntryDecoder.INSTANCE.readPublicKey(null, () 
-> keyType, keyData);
+        }
+
+        assertKeyEquals(keyType, expected, actual);
+    }
+
+    private InputStream getPublicKeyDataStream(String suffix) {
+        String resourceName = keyType + "-" + PublicKey.class.getSimpleName() 
+ "." + suffix;
+        InputStream keyData = getClass().getResourceAsStream(resourceName);
+        return ValidateUtils.checkNotNull(keyData, "Missing %s file", 
resourceName);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + keyType + "]";
+    }
+}
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderTest.java
similarity index 51%
copy from 
sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java
copy to 
sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderTest.java
index 45c8921..439926a 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderTest.java
@@ -17,14 +17,13 @@
  * under the License.
  */
 
-package org.apache.sshd.common.config.keys;
+package org.apache.sshd.common.config.keys.loader.ssh2;
 
-import java.security.GeneralSecurityException;
+import java.io.InputStream;
 import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
 
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
@@ -37,26 +36,31 @@ import org.junit.runners.MethodSorters;
  */
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Category({ NoIoTestCase.class })
-public class PublicKeyEntryTest extends JUnitTestSupport {
-    public PublicKeyEntryTest() {
+public class Ssh2PublicKeyEntryDecoderTest extends JUnitTestSupport {
+    public Ssh2PublicKeyEntryDecoderTest() {
         super();
     }
 
     @Test
-    public void testFallbackResolver() throws Exception {
-        PublicKeyEntry entry =
-            PublicKeyEntry.parsePublicKeyEntry(
-                GenericUtils.join(
-                    Arrays.asList(getCurrentTestName(), "AAAA", 
getClass().getSimpleName()), ' '));
-        for (PublicKeyEntryResolver resolver : new PublicKeyEntryResolver[]{
-            null, PublicKeyEntryResolver.FAILING, 
PublicKeyEntryResolver.IGNORING}) {
-            try {
-                PublicKey key = entry.resolvePublicKey(null, resolver);
-                assertSame("Mismatched successful resolver", 
PublicKeyEntryResolver.IGNORING, resolver);
-                assertNull("Unexpected success for resolver=" + resolver + ": 
" + KeyUtils.getFingerPrint(key), key);
-            } catch (GeneralSecurityException e) {
-                assertObjectInstanceOf("Mismatched thrown exception for 
resolver=" + resolver, InvalidKeySpecException.class, e);
-            }
+    public void testMultiLineComment() throws Exception {
+        testDecoder("rfc4716-multi-line-comment.ssh2");
+    }
+
+    @Test
+    public void testMultipleHeaders() throws Exception {
+        testDecoder("rfc4716-multiple-headers.ssh2");
+    }
+
+    private PublicKey testDecoder(String resourceName) throws Exception {
+        PublicKey key;
+        try (InputStream stream = ValidateUtils.checkNotNull(
+                getClass().getResourceAsStream(resourceName), "Missing test 
resource: %s", resourceName)) {
+            key = Ssh2PublicKeyEntryDecoder.INSTANCE.readPublicKey(null, () -> 
resourceName, stream);
         }
+        assertNotNull("No key loaded from " + resourceName, key);
+
+        String keyType = KeyUtils.getKeyType(key);
+        assertNotNull("Unknown key type loaded from " + resourceName, keyType);
+        return key;
     }
 }
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub
new file mode 100644
index 0000000..1c9763f
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKGx7cPcJC999nqfX4vBsOYPLBBsLJTaJz1Ag0emFGfZoznoZYi6RQyxy8ZSBWn61TPiyKWoI6USmXJ6Rj1kA5w=
 root@osv-linux
\ No newline at end of file
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2
new file mode 100644
index 0000000..669f876
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2
@@ -0,0 +1,6 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "256-bit ECDSA, converted by lyor@localhost.localdomain from "
+AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKGx7cPcJC999nqfX4
+vBsOYPLBBsLJTaJz1Ag0emFGfZoznoZYi6RQyxy8ZSBWn61TPiyKWoI6USmXJ6Rj1kA5w=
+
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub
new file mode 100644
index 0000000..93ceef0
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 
AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBFImZtcTj842stlcVHLFBFxTEx7lu3jW9aZCvd0r9fUNKZ6LbRPh6l1oJ4ozArnw7XreQBUc5oNd9HB5RNJ8jl1nWXY5cXBA7McZrKZrYmk+zxNhH6UL+kMLaJkyngJHQw==
 root@osv-linux
\ No newline at end of file
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2
new file mode 100644
index 0000000..718b19f
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2
@@ -0,0 +1,6 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "384-bit ECDSA, converted by lyor@localhost.localdomain from "
+AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBFImZtcTj842stlcVH
+LFBFxTEx7lu3jW9aZCvd0r9fUNKZ6LbRPh6l1oJ4ozArnw7XreQBUc5oNd9HB5RNJ8jl1n
+WXY5cXBA7McZrKZrYmk+zxNhH6UL+kMLaJkyngJHQw==
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub
new file mode 100644
index 0000000..520b64e
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 
AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACg4siCq1iqr4U/spXmw6b2VwBMsof7XLQGoD9wfwUikb8XWthNSmPP1nL6rlzJ5j8Bezn9BSSDfVAJfgqxmGIHdgHRVc0mkdq1/Q/DKhBgRyjZc29eo0o2ck3SNGNVaAabRYj6ck/iub/U6trKM7bdqy/joYYMwZdxLyYW5YxkPbqEfQ==
 root@osv-linux
\ No newline at end of file
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2
new file mode 100644
index 0000000..e938cf9
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2
@@ -0,0 +1,7 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "521-bit ECDSA, converted by lyor@localhost.localdomain from "
+AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACg4siCq1iqr4U/sp
+Xmw6b2VwBMsof7XLQGoD9wfwUikb8XWthNSmPP1nL6rlzJ5j8Bezn9BSSDfVAJfgqxmGIH
+dgHRVc0mkdq1/Q/DKhBgRyjZc29eo0o2ck3SNGNVaAabRYj6ck/iub/U6trKM7bdqy/joY
+YMwZdxLyYW5YxkPbqEfQ==
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multi-line-comment.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multi-line-comment.ssh2
new file mode 100644
index 0000000..ca5089d
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multi-line-comment.ssh2
@@ -0,0 +1,13 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: This is my public key for use on \
+servers which I don't like.
+AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET
+W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH
+YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c
+vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf
+J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA
+vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB
+AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS
+n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5
+sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multiple-headers.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multiple-headers.ssh2
new file mode 100644
index 0000000..e4d4461
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multiple-headers.ssh2
@@ -0,0 +1,7 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "1024-bit RSA, converted from OpenSSH by m...@example.com"
+x-command: /home/me/bin/lock-in-guest.sh
+AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb
+YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ
+5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.pub
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.pub
new file mode 100644
index 0000000..c3feece
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.pub
@@ -0,0 +1 @@
+ssh-dss 
AAAAB3NzaC1kc3MAAACBAPdrHLUwF2HhMkL5PbYMooN7uxecOuAlGzWMFwOX+o91AOguW+9N3ygeCBeQjMjpPHaV2IncoAMyQODWwFJTnN6civ3CKx8RA4s2p8g46KtH1nyNQsZWUM76k/CmzQ/1tw6RokIB8AE22Ho5rPc0mN8UPxmaZYNVo3atKLUneaOfAAAAFQDlt7TQxpK2LToeivbrlXwaC2OL/QAAAIBr0xTyQEnhnX5Sytau5WvvZ5yQTCM49xEV4DoyhIzvQYZ1hWQvJDvwwyMoAVlxux5hPeWyNr5zgtDvIStE4dIYWj4iZCdgZjcKizaZLy1EEe1nhiYqwGZLdmtviUR9q4DWmAOlwatQBq+eIogAH8L1ym+P4ilnfr6selqngkonuQAAAIBA/g1HOO1q7OsMm+zvdzQM9zMCQsBqf73Si68i4jQKYzEWtKosggRpfdwH8/h2Qq0GwyHQLHAmmhd+
 [...]
\ No newline at end of file
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2
new file mode 100644
index 0000000..31ee7dc
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2
@@ -0,0 +1,12 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "1024-bit DSA, converted by lyor@localhost.localdomain from O"
+AAAAB3NzaC1kc3MAAACBAPdrHLUwF2HhMkL5PbYMooN7uxecOuAlGzWMFwOX+o91AOguW+
+9N3ygeCBeQjMjpPHaV2IncoAMyQODWwFJTnN6civ3CKx8RA4s2p8g46KtH1nyNQsZWUM76
+k/CmzQ/1tw6RokIB8AE22Ho5rPc0mN8UPxmaZYNVo3atKLUneaOfAAAAFQDlt7TQxpK2LT
+oeivbrlXwaC2OL/QAAAIBr0xTyQEnhnX5Sytau5WvvZ5yQTCM49xEV4DoyhIzvQYZ1hWQv
+JDvwwyMoAVlxux5hPeWyNr5zgtDvIStE4dIYWj4iZCdgZjcKizaZLy1EEe1nhiYqwGZLdm
+tviUR9q4DWmAOlwatQBq+eIogAH8L1ym+P4ilnfr6selqngkonuQAAAIBA/g1HOO1q7OsM
+m+zvdzQM9zMCQsBqf73Si68i4jQKYzEWtKosggRpfdwH8/h2Qq0GwyHQLHAmmhd+f5M77W
+PuLTwa4w416oHeU4efX8wHapQBmH+R1TxFSzFaSsbzQkx4/WR7DWwdqhF85Rnz5fD9Evuc
+2aGGYclHeuSBHwShYQ==
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.pub
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.pub
new file mode 100644
index 0000000..128e883
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.pub
@@ -0,0 +1 @@
+ssh-ed25519 
AAAAC3NzaC1lZDI1NTE5AAAAIHROxqPSxyzx9goyWfPYydyTLnjk2ggI/14YwYimjkbd 
root@ubuntu-15
\ No newline at end of file
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2
new file mode 100644
index 0000000..b921867
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2
@@ -0,0 +1,4 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "256-bit ED25519, converted by lyor@localhost.localdomain fro"
+AAAAC3NzaC1lZDI1NTE5AAAAIHROxqPSxyzx9goyWfPYydyTLnjk2ggI/14YwYimjkbd
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.pub
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.pub
new file mode 100644
index 0000000..c59321c
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.pub
@@ -0,0 +1 @@
+ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAACAQCuxjvYVRScrzIuppjdaSi28wWbZqvKhVU9toeJcyqYpNNA8EWyHB5GT7AQkETu2Hh8irYy5O76EzERDz6hgnjsprX4X3pATUXS67VO55z9ml64SJexMEClZnlJoAypDeAx+V+qCn3qKlXdI6kvIQb0YipUwlg0T+CDAhXtRzjJsLbVnLcjtA58eGjwF5oyNX/vTeuW7ZRazxPCOjtUxYu+cYxSM0zM0aqX1ai4sTMeyGaRRkFF0q+dOOxP2/HWrk7Jxof+AeizbcRs3OfnStuIib3c9xn+i0YGON9sdyxGaPFmGoLMuyzk2v3c8sh483wJ7P3VLtouMLBGLi6ArErQRnA0ecc+LTJHR4jV4x8ku211ies5mJLERVsKxmIN9PJu4FhQNofYS2CbMf03bkhl4k6OuMTrw1mGLBedoige9+mQ9ORYxIYuwtjcqDR1jBRsDL0S
 [...]
diff --git 
a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2
 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2
new file mode 100644
index 0000000..61a3c0b
--- /dev/null
+++ 
b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2
@@ -0,0 +1,14 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "4096-bit RSA, converted by lyor@localhost.localdomain from O"
+AAAAB3NzaC1yc2EAAAADAQABAAACAQCuxjvYVRScrzIuppjdaSi28wWbZqvKhVU9toeJcy
+qYpNNA8EWyHB5GT7AQkETu2Hh8irYy5O76EzERDz6hgnjsprX4X3pATUXS67VO55z9ml64
+SJexMEClZnlJoAypDeAx+V+qCn3qKlXdI6kvIQb0YipUwlg0T+CDAhXtRzjJsLbVnLcjtA
+58eGjwF5oyNX/vTeuW7ZRazxPCOjtUxYu+cYxSM0zM0aqX1ai4sTMeyGaRRkFF0q+dOOxP
+2/HWrk7Jxof+AeizbcRs3OfnStuIib3c9xn+i0YGON9sdyxGaPFmGoLMuyzk2v3c8sh483
+wJ7P3VLtouMLBGLi6ArErQRnA0ecc+LTJHR4jV4x8ku211ies5mJLERVsKxmIN9PJu4FhQ
+NofYS2CbMf03bkhl4k6OuMTrw1mGLBedoige9+mQ9ORYxIYuwtjcqDR1jBRsDL0SuqRL8w
+QyY3vQ7EjGpMiEVJnRu/cUFtlNmeX8YARaZX4pKNREq4mVL0QFgThm/NiEaLoruDchAlnC
+tyrLqzy7bivm6oBAKKF+5YVpuridJgHXNzNDtCr5XzB6ZqTqWCgUarh/nyTcVSGQlvBBEU
+mhBi9owjrd2Wz2rrtuShyPJywY+9YNTsgT11WzfrPsV6+VCOygfACvj7RPU8Wyoef74onS
+ncCWikhXs5tLRw==
+---- END SSH2 PUBLIC KEY ----
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
index e5043e8..d6964d8 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
@@ -88,7 +88,7 @@ public class KnownHostsServerKeyVerifierTest extends 
BaseTestSupport {
             SshdSocketAddress hostIdentity = ke.getKey();
             KnownHostEntry entry = ke.getValue();
             AuthorizedKeyEntry authEntry = 
ValidateUtils.checkNotNull(entry.getKeyEntry(), "No key extracted from %s", 
entry);
-            PublicKey key = authEntry.resolvePublicKey(null, 
PublicKeyEntryResolver.FAILING);
+            PublicKey key = authEntry.resolvePublicKey(null, 
Collections.emptyMap(), PublicKeyEntryResolver.FAILING);
             assertNull("Multiple keys for host=" + hostIdentity, 
HOST_KEYS.put(hostIdentity, key));
         }
     }
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
index a43aa1b..cf0c5c4 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
@@ -26,6 +26,7 @@ import java.nio.file.Path;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.sshd.common.util.GenericUtils;
@@ -110,8 +111,10 @@ public class AuthorizedKeyEntryTest extends 
AuthorizedKeysTestSupport {
         Exception err = null;
         for (AuthorizedKeyEntry entry : entries) {
             try {
-                ValidateUtils.checkNotNull(entry.resolvePublicKey(null, 
PublicKeyEntryResolver.FAILING),
-                        "No public key resolved from %s", entry);
+                ValidateUtils.checkNotNull(
+                    entry.resolvePublicKey(null, Collections.emptyMap(), 
PublicKeyEntryResolver.FAILING),
+                    "No public key resolved from %s",
+                    entry);
             } catch (Exception e) {
                 System.err.append("Failed 
(").append(e.getClass().getSimpleName()).append(')')
                         .append(" to resolve key of 
entry=").append(entry.toString())
diff --git 
a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java
 
b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java
index 99b7f32..e67637f 100644
--- 
a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java
+++ 
b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java
@@ -20,6 +20,7 @@
 package org.apache.sshd.server.auth.pubkey;
 
 import java.security.PublicKey;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Map;
 import java.util.Objects;
@@ -66,7 +67,8 @@ public class LdapPublickeyAuthenticatorTest extends 
BaseAuthenticatorTest {
         for (Map.Entry<String, String> ce : credentials.entrySet()) {
             String username = ce.getKey();
             AuthorizedKeyEntry entry = 
AuthorizedKeyEntry.parseAuthorizedKeyEntry(ce.getValue());
-            PublicKey key = Objects.requireNonNull(entry, "No key 
extracted").resolvePublicKey(null, PublicKeyEntryResolver.FAILING);
+            PublicKey key = Objects.requireNonNull(entry, "No key extracted")
+                .resolvePublicKey(null, Collections.emptyMap(), 
PublicKeyEntryResolver.FAILING);
             KEYS_MAP.put(username, key);
         }
     }
diff --git 
a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java
 
b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java
index 3640ed3..d8c1926 100644
--- 
a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java
+++ 
b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java
@@ -45,8 +45,9 @@ import org.bouncycastle.openpgp.PGPException;
  */
 public interface PGPAuthorizedKeyEntriesLoader extends PGPPublicKeyExtractor, 
PublicKeyEntryResolver {
     @Override
-    default PublicKey resolve(SessionContext session, String keyType, byte[] 
keyData)
-            throws IOException, GeneralSecurityException {
+    default PublicKey resolve(
+            SessionContext session, String keyType, byte[] keyData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
         if (!PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType)) {
             return null;
         }
diff --git 
a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
 
b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
index 4edcf83..19f5e2a 100644
--- 
a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
+++ 
b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
@@ -35,6 +35,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
@@ -75,7 +76,7 @@ public class PGPKeyPairResourceParser
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            List<String> lines)
+            List<String> lines, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         // We need to re-construct the original data - including start/end 
markers
         String eol = System.lineSeparator();
@@ -93,7 +94,7 @@ public class PGPKeyPairResourceParser
         String keyData = sb.toString();
         byte[] dataBytes = keyData.getBytes(StandardCharsets.US_ASCII);
         try {
-            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, dataBytes);
+            return extractKeyPairs(session, resourceKey, beginMarker, 
endMarker, passwordProvider, dataBytes, headers);
         } finally {
             Arrays.fill(dataBytes, (byte) 0);   // clean up sensitive data 
a.s.a.p.
         }
@@ -104,10 +105,12 @@ public class PGPKeyPairResourceParser
             SessionContext session, NamedResource resourceKey,
             String beginMarker, String endMarker,
             FilePasswordProvider passwordProvider,
-            InputStream stream)
+            InputStream stream, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         for (int retryCount = 0;; retryCount++) {
-            String password = (passwordProvider == null) ? null : 
passwordProvider.getPassword(session, resourceKey, retryCount);
+            String password = (passwordProvider == null)
+                ? null
+                : passwordProvider.getPassword(session, resourceKey, 
retryCount);
             Collection<KeyPair> keys;
             try {
                 if (retryCount > 0) {
diff --git 
a/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java
 
b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java
index a75928d..fb45826 100644
--- 
a/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java
+++ 
b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java
@@ -211,10 +211,11 @@ public class PGPUtilsKeyFingerprintTest extends 
JUnitTestSupport {
         PGPAuthorizedEntriesTracker tracker = new 
PGPAuthorizedEntriesTracker(file);
         SessionContext session = Mockito.mock(SessionContext.class);
         for (PublicKeyEntry pke : available) {
-            Collection<PublicKey> keys = 
tracker.loadMatchingAuthorizedEntries(session, Collections.singletonList(pke));
+            Collection<PublicKey> keys =
+                tracker.loadMatchingAuthorizedEntries(session, 
Collections.singletonList(pke));
             assertEquals("Mismatched recovered keys count for " + pke, 1, 
GenericUtils.size(keys));
 
-            PublicKey expected = pke.resolvePublicKey(session, tracker);
+            PublicKey expected = pke.resolvePublicKey(session, 
Collections.emptyMap(), tracker);
             PublicKey actual = GenericUtils.head(keys);
             assertKeyEquals(pke.toString(), expected, actual);
         }
diff --git 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java
 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java
index bdf3328..e2113b8 100644
--- 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java
+++ 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java
@@ -34,6 +34,8 @@ import java.util.Base64.Decoder;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import javax.security.auth.login.FailedLoginException;
 
@@ -92,6 +94,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends 
PublicKey, PRV extends
                 throws IOException, GeneralSecurityException {
         List<String> pubLines = Collections.emptyList();
         List<String> prvLines = Collections.emptyList();
+        Map<String, String> headers = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         String prvEncryption = null;
         for (int index = 0, numLines = lines.size(); index < numLines; 
index++) {
             String l = lines.get(index);
@@ -103,6 +106,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends 
PublicKey, PRV extends
 
             String hdrName = l.substring(0, pos).trim();
             String hdrValue = l.substring(pos + 1).trim();
+            headers.put(hdrName, hdrValue);
             switch (hdrName) {
                 case ENCRYPTION_HEADER:
                     if (prvEncryption != null) {
@@ -122,7 +126,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends 
PublicKey, PRV extends
             }
         }
 
-        return loadKeyPairs(session, resourceKey, pubLines, prvLines, 
prvEncryption, passwordProvider);
+        return loadKeyPairs(session, resourceKey, pubLines, prvLines, 
prvEncryption, passwordProvider, headers);
     }
 
     public static List<String> extractDataLines(
@@ -151,17 +155,17 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends 
PublicKey, PRV extends
     public Collection<KeyPair> loadKeyPairs(
             SessionContext session, NamedResource resourceKey,
             List<String> pubLines, List<String> prvLines, String prvEncryption,
-            FilePasswordProvider passwordProvider)
+            FilePasswordProvider passwordProvider, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         return loadKeyPairs(session, resourceKey,
                 KeyPairResourceParser.joinDataLines(pubLines), 
KeyPairResourceParser.joinDataLines(prvLines),
-                prvEncryption, passwordProvider);
+                prvEncryption, passwordProvider, headers);
     }
 
     public Collection<KeyPair> loadKeyPairs(
             SessionContext session, NamedResource resourceKey,
             String pubData, String prvData, String prvEncryption,
-            FilePasswordProvider passwordProvider)
+            FilePasswordProvider passwordProvider, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         byte[] pubBytes = GenericUtils.EMPTY_BYTE_ARRAY;
         byte[] prvBytes = GenericUtils.EMPTY_BYTE_ARRAY;
@@ -171,7 +175,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends 
PublicKey, PRV extends
             prvBytes = b64Decoder.decode(prvData);
             if (GenericUtils.isEmpty(prvEncryption)
                     || 
NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption)) {
-                return loadKeyPairs(resourceKey, pubBytes, prvBytes);
+                return loadKeyPairs(resourceKey, pubBytes, prvBytes, headers);
             }
 
             // format is "<cipher><bits>-<mode>" - e.g., "aes256-cbc"
@@ -208,7 +212,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends 
PublicKey, PRV extends
                     byte[] decBytes = 
PuttyKeyPairResourceParser.decodePrivateKeyBytes(
                         prvBytes, algName, numBits, mode, password);
                     try {
-                        keys = loadKeyPairs(resourceKey, pubBytes, decBytes);
+                        keys = loadKeyPairs(resourceKey, pubBytes, decBytes, 
headers);
                     } finally {
                         Arrays.fill(decBytes, (byte) 0);    // eliminate 
sensitive data a.s.a.p.
                     }
@@ -243,26 +247,29 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends 
PublicKey, PRV extends
         }
     }
 
-    public Collection<KeyPair> loadKeyPairs(NamedResource resourceKey, byte[] 
pubData, byte[] prvData)
-            throws IOException, GeneralSecurityException {
+    public Collection<KeyPair> loadKeyPairs(
+            NamedResource resourceKey, byte[] pubData, byte[] prvData, 
Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
         ValidateUtils.checkNotNullAndNotEmpty(pubData, "No public key data in 
%s", resourceKey);
         ValidateUtils.checkNotNullAndNotEmpty(prvData, "No private key data in 
%s", resourceKey);
         try (InputStream pubStream = new ByteArrayInputStream(pubData);
              InputStream prvStream = new ByteArrayInputStream(prvData)) {
-            return loadKeyPairs(resourceKey, pubStream, prvStream);
+            return loadKeyPairs(resourceKey, pubStream, prvStream, headers);
         }
     }
 
-    public Collection<KeyPair> loadKeyPairs(NamedResource resourceKey, 
InputStream pubData, InputStream prvData)
-            throws IOException, GeneralSecurityException {
+    public Collection<KeyPair> loadKeyPairs(
+            NamedResource resourceKey, InputStream pubData, InputStream 
prvData, Map<String, String> headers)
+                throws IOException, GeneralSecurityException {
         try (PuttyKeyReader pubReader =
                 new PuttyKeyReader(ValidateUtils.checkNotNull(pubData, "No 
public key data in %s", resourceKey));
              PuttyKeyReader prvReader =
                 new PuttyKeyReader(ValidateUtils.checkNotNull(prvData, "No 
private key data in %s", resourceKey))) {
-            return loadKeyPairs(resourceKey, pubReader, prvReader);
+            return loadKeyPairs(resourceKey, pubReader, prvReader, headers);
         }
     }
 
-    public abstract Collection<KeyPair> loadKeyPairs(NamedResource 
resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader)
+    public abstract Collection<KeyPair> loadKeyPairs(
+        NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader 
prvReader, Map<String, String> headers)
             throws IOException, GeneralSecurityException;
 }
diff --git 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java
 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java
index 0c2d3bd..44b41cf 100644
--- 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java
+++ 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java
@@ -32,6 +32,7 @@ import java.security.spec.DSAPrivateKeySpec;
 import java.security.spec.DSAPublicKeySpec;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.KeyUtils;
@@ -50,7 +51,7 @@ public class DSSPuttyKeyDecoder extends 
AbstractPuttyKeyDecoder<DSAPublicKey, DS
 
     @Override
     public Collection<KeyPair> loadKeyPairs(
-            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader)
+            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         pubReader.skip();   // skip version
 
diff --git 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java
 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java
index 1d02aa4..e5b4ca5 100644
--- 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java
+++ 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java
@@ -35,6 +35,7 @@ import java.security.spec.ECPublicKeySpec;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.cipher.ECCurves;
@@ -56,7 +57,7 @@ public class ECDSAPuttyKeyDecoder extends 
AbstractPuttyKeyDecoder<ECPublicKey, E
 
     @Override
     public Collection<KeyPair> loadKeyPairs(
-            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader)
+            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         if (!SecurityUtils.isECCSupported()) {
             throw new NoSuchAlgorithmException("ECC not supported for " + 
resourceKey);
diff --git 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java
 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java
index 07e461e..e13cd09 100644
--- 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java
+++ 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java
@@ -27,6 +27,7 @@ import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
@@ -50,7 +51,7 @@ public class EdDSAPuttyKeyDecoder extends 
AbstractPuttyKeyDecoder<EdDSAPublicKey
 
     @Override
     public Collection<KeyPair> loadKeyPairs(
-            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader)
+            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         if (!SecurityUtils.isEDDSACurveSupported()) {
             throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " 
provider not supported for " + resourceKey);
diff --git 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java
 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java
index e50a52f..a9b1215 100644
--- 
a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java
+++ 
b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java
@@ -33,6 +33,7 @@ import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.KeyUtils;
@@ -51,7 +52,7 @@ public class RSAPuttyKeyDecoder extends 
AbstractPuttyKeyDecoder<RSAPublicKey, RS
 
     @Override
     public Collection<KeyPair> loadKeyPairs(
-            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader)
+            NamedResource resourceKey, PuttyKeyReader pubReader, 
PuttyKeyReader prvReader, Map<String, String> headers)
                 throws IOException, GeneralSecurityException {
         pubReader.skip();   // skip version
 
@@ -67,7 +68,7 @@ public class RSAPuttyKeyDecoder extends 
AbstractPuttyKeyDecoder<RSAPublicKey, RS
         BigInteger primeExponentP = 
privateExp.mod(primeP.subtract(BigInteger.ONE));
         BigInteger primeExponentQ = 
privateExp.mod(primeQ.subtract(BigInteger.ONE));
         RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec(
-                modulus, publicExp, privateExp, primeP, primeQ, 
primeExponentP, primeExponentQ, crtCoef);
+            modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, 
primeExponentQ, crtCoef);
         PrivateKey prvKey = kf.generatePrivate(prvSpec);
         return Collections.singletonList(new KeyPair(pubKey, prvKey));
     }

Reply via email to