[SSHD-500] Implement loading a KeyPair file directly without BouncyCastle help


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

Branch: refs/heads/master
Commit: e767438ae47f579bf001a3fbb47c6b1a4df13b1c
Parents: d13c679
Author: Lyor Goldstein <lyor.goldst...@gmail.com>
Authored: Fri Nov 25 17:38:11 2016 +0200
Committer: Lyor Goldstein <lyor.goldst...@gmail.com>
Committed: Fri Nov 25 17:38:11 2016 +0200

----------------------------------------------------------------------
 README.md                                       |  12 +-
 .../sshd/client/config/keys/ClientIdentity.java |   2 +-
 .../config/keys/ClientIdentityLoader.java       |  14 +-
 .../org/apache/sshd/common/cipher/ECCurves.java | 265 ++++++++++++++-
 .../sshd/common/config/keys/KeyUtils.java       | 117 +++++--
 .../keys/impl/ECDSAPublicKeyEntryDecoder.java   | 208 +-----------
 .../keys/loader/AESPrivateKeyObfuscator.java    | 107 ++++++
 .../loader/AbstractKeyPairResourceParser.java   |   8 +-
 .../loader/AbstractPrivateKeyObfuscator.java    | 191 +++++++++++
 .../keys/loader/DESPrivateKeyObfuscator.java    |  76 +++++
 .../keys/loader/KeyPairResourceParser.java      |  18 +
 .../loader/PrivateKeyEncryptionContext.java     | 276 +++++++++++++++
 .../keys/loader/PrivateKeyObfuscator.java       |  64 ++++
 .../OpenSSHDSSPrivateKeyEntryDecoder.java       |   9 +-
 .../openssh/OpenSSHKeyPairResourceParser.java   |   1 +
 .../openssh/OpenSSHRSAPrivateKeyDecoder.java    |  19 +-
 .../pem/AbstractPEMResourceKeyPairParser.java   | 168 +++++++++
 .../loader/pem/DSSPEMResourceKeyPairParser.java | 127 +++++++
 .../pem/ECDSAPEMResourceKeyPairParser.java      | 219 ++++++++++++
 .../loader/pem/KeyPairPEMResourceParser.java    |  37 ++
 .../keys/loader/pem/PEMResourceParserUtils.java | 109 ++++++
 .../pem/PKCS8PEMResourceKeyPairParser.java      | 157 +++++++++
 .../loader/pem/RSAPEMResourceKeyPairParser.java | 137 ++++++++
 .../java/org/apache/sshd/common/kex/ECDH.java   |   3 +-
 .../AbstractResourceKeyPairProvider.java        |   1 -
 .../sshd/common/signature/SignatureDSA.java     |   4 +-
 .../sshd/common/signature/SignatureECDSA.java   |   4 +-
 .../apache/sshd/common/util/GenericUtils.java   |  34 ++
 .../apache/sshd/common/util/NumberUtils.java    |  41 ++-
 .../apache/sshd/common/util/buffer/Buffer.java  |   3 +-
 .../sshd/common/util/buffer/BufferUtils.java    |   4 +
 .../buffer/keys/ECBufferPublicKeyParser.java    |   3 +-
 .../apache/sshd/common/util/io/DERParser.java   | 136 --------
 .../apache/sshd/common/util/io/DERWriter.java   | 118 -------
 .../sshd/common/util/io/der/ASN1Class.java      |  93 +++++
 .../sshd/common/util/io/der/ASN1Object.java     | 338 +++++++++++++++++++
 .../sshd/common/util/io/der/ASN1Type.java       | 118 +++++++
 .../sshd/common/util/io/der/DERParser.java      | 151 +++++++++
 .../sshd/common/util/io/der/DERWriter.java      | 147 ++++++++
 .../common/util/security/SecurityUtils.java     |  30 +-
 .../security/eddsa/EdDSASecurityProvider.java   |  13 +
 .../OpenSSHEd25519PrivateKeyEntryDecoder.java   |   8 +-
 .../sshd/server/config/keys/ServerIdentity.java |   2 +-
 .../client/config/keys/ClientIdentityTest.java  |   4 -
 .../loader/AESPrivateKeyObfuscatorTest.java     |  73 ++++
 .../sshd/common/util/SecurityUtilsTest.java     |  47 ++-
 .../sshd/common/util/io/der/ASN1ClassTest.java  |  61 ++++
 .../sshd/common/util/io/der/ASN1TypeTest.java   |  61 ++++
 .../sshd/common/util/io/der/DERParserTest.java  |  58 ++++
 .../server/config/keys/ServerIdentityTest.java  |   4 -
 50 files changed, 3290 insertions(+), 610 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 16b0bde..bf8b2b8 100644
--- a/README.md
+++ b/README.md
@@ -17,9 +17,12 @@ The code only requires the core abstract 
[slf4j-api](https://mvnrepository.com/a
 * [Bouncy Castle](https://www.bouncycastle.org/)
 
 
-Required only for reading/writing keys from/to PEM files or for special 
keys/ciphers/etc. that are not part of the standard [Java Cryptography 
Extension](https://en.wikipedia.org/wiki/Java_Cryptography_Extension). See 
[Java Cryptography Architecture (JCA) Reference 
Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html)
 for key classes and explanations as to how _Bouncy Castle_ is plugged in 
(other security providers). **Note:** the required Maven module(s) are defined 
as `optional` so must be added as an
-**explicit** dependency in order to be included in the classpath:
+Required mainly for writing keys from/to PEM files or for special 
keys/ciphers/etc. that are not part of the standard [Java Cryptography 
Extension](https://en.wikipedia.org/wiki/Java_Cryptography_Extension). See 
[Java Cryptography Architecture (JCA) Reference 
Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html)
 for key classes and explanations as to how _Bouncy Castle_ is plugged in 
(other security providers).
+
+**Caveat**: If _Bouncy Castle_ modules are registered, then the code will use 
its implementation of the ciphers, keys, signatures, etc. rather than the 
default JCE provided in the JVM. Furthermore, one can use the 
`BouncyCastleKeyPairResourceParser.INSTANCE` to load standard PEM files instead 
of the core one - either directly or via 
`SecurityUtils#setKeyPairResourceParser` for **global** usage.
 
+ **Note:** the required Maven module(s) are defined as `optional` so must be 
added as an
+**explicit** dependency in order to be included in the classpath:
 
 ```xml
 
@@ -34,9 +37,6 @@ Required only for reading/writing keys from/to PEM files or 
for special keys/cip
 
 ```
 
-**Caveat**: If _Bouncy Castle_ modules are available, then the code will use 
its implementation of the ciphers, keys, signatures, etc. rather than the 
default JCE provided in the JVM.
-
-
 * [MINA core](https://mina.apache.org/mina-project/)
 
 
@@ -120,7 +120,7 @@ While RFC-4256 support is the primary purpose of this 
interface, it can also be
 
 ## Using the `SshClient` to connect to a server
 
-Once the `SshClient` instance is properly configured it needs to be 
`start()`-ed in order to connect to a server. **Note:** one can use a single 
`SshClient` instance to connnect to multiple server as well as modifying the 
default configuration (ciphers, MACs, keys, etc.) on a per-session manner (see 
more in the *Advanced usage* section). Furthermore, one can change almost any 
configured `SshClient` parameter - although its influence on currently 
established sessions depends on the actual changed configuration. Here is how a 
typical usage would look like
+Once the `SshClient` instance is properly configured it needs to be 
`start()`-ed in order to connect to a server. **Note:** one can use a single 
`SshClient` instance to connnect to multiple servers as well as modifying the 
default configuration (ciphers, MACs, keys, etc.) on a per-session manner (see 
more in the *Advanced usage* section). Furthermore, one can change almost any 
configured `SshClient` parameter - although its influence on currently 
established sessions depends on the actual changed configuration. Here is how a 
typical usage would look like
 
 ```java
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
index 5802b2a..9060fec 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
@@ -48,7 +48,7 @@ import org.apache.sshd.common.util.io.IoUtils;
  * Provides keys loading capability from the user's keys folder - e.g., {@code 
id_rsa}
  *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see 
org.apache.sshd.common.util.security.SecurityUtils#isBouncyCastleRegistered()
+ * @see 
org.apache.sshd.common.util.security.SecurityUtils#getKeyPairResourceParser()
  */
 public final class ClientIdentity {
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
index 1f04e6f..8b3a295 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
@@ -19,11 +19,11 @@
 
 package org.apache.sshd.client.config.keys;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 
@@ -41,17 +41,11 @@ public interface ClientIdentityLoader {
      *
      * <P>
      * <B>Note:</B> It calls {@link SecurityUtils#loadKeyPairIdentity(String, 
InputStream, FilePasswordProvider)}
-     * which fails if the {@code Bouncycastle} provider is not registered, 
therefore the
-     * default {@link #isValidLocation(String)} implementation also checks if
-     * {@link SecurityUtils#isBouncyCastleRegistered()}
      * </P>
      */
     ClientIdentityLoader DEFAULT = new ClientIdentityLoader() {
         @Override
         public boolean isValidLocation(String location) throws IOException {
-            if (!SecurityUtils.isBouncyCastleRegistered()) {
-                return false;
-            }
             Path path = toPath(location);
             return Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS);
         }
@@ -70,8 +64,10 @@ public interface ClientIdentityLoader {
         }
 
         private Path toPath(String location) {
-            Path path = new 
File(ValidateUtils.checkNotNullAndNotEmpty(location, "No location")).toPath();
-            return path.toAbsolutePath().normalize();
+            Path path = 
Paths.get(ValidateUtils.checkNotNullAndNotEmpty(location, "No location"));
+            path = path.toAbsolutePath();
+            path = path.normalize();
+            return path;
         }
     };
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java 
b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
index 0e99ec0..8da9f9c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -18,6 +18,11 @@
  */
 package org.apache.sshd.common.cipher;
 
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
 import java.math.BigInteger;
 import java.security.interfaces.ECKey;
 import java.security.spec.ECField;
@@ -35,10 +40,12 @@ import java.util.stream.Collectors;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.OptionalFeature;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.digest.DigestFactory;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
@@ -48,7 +55,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public enum ECCurves implements NamedResource, OptionalFeature {
-    nistp256(Constants.NISTP256,
+    nistp256(Constants.NISTP256, new int[]{1, 2, 840, 10045, 3, 1, 7},
             new ECParameterSpec(
                     new EllipticCurve(
                             new ECFieldFp(new 
BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 
16)),
@@ -61,7 +68,7 @@ public enum ECCurves implements NamedResource, 
OptionalFeature {
                     1),
             32,
             BuiltinDigests.sha256),
-    nistp384(Constants.NISTP384,
+    nistp384(Constants.NISTP384, new int[]{1, 3, 132, 0, 34},
             new ECParameterSpec(
                     new EllipticCurve(
                             new ECFieldFp(new 
BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
 16)),
@@ -74,7 +81,7 @@ public enum ECCurves implements NamedResource, 
OptionalFeature {
                     1),
             48,
             BuiltinDigests.sha384),
-    nistp521(Constants.NISTP521,
+    nistp521(Constants.NISTP521, new int[]{1, 3, 132, 0, 35},
             new ECParameterSpec(
                     new EllipticCurve(
                             new ECFieldFp(new 
BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
@@ -131,13 +138,17 @@ public enum ECCurves implements NamedResource, 
OptionalFeature {
 
     private final String name;
     private final String keyType;
+    private final String oidString;
+    private final List<Integer> oidValue;
     private final ECParameterSpec params;
     private final int keySize;
     private final int numOctets;
     private final DigestFactory digestFactory;
 
-    ECCurves(String name, ECParameterSpec params, int numOctets, DigestFactory 
digestFactory) {
+    ECCurves(String name, int[] oid, ECParameterSpec params, int numOctets, 
DigestFactory digestFactory) {
         this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No curve 
name");
+        this.oidString = NumberUtils.join('.', 
ValidateUtils.checkNotNullAndNotEmpty(oid, "No OID"));
+        this.oidValue = Collections.unmodifiableList(NumberUtils.asList(oid));
         this.keyType = Constants.ECDSA_SHA2_PREFIX + name;
         this.params = ValidateUtils.checkNotNull(params, "No EC params for 
%s", name);
         this.keySize = getCurveSize(params);
@@ -150,6 +161,14 @@ public enum ECCurves implements NamedResource, 
OptionalFeature {
         return name;
     }
 
+    public final String getOID() {
+        return oidString;
+    }
+
+    public final List<Integer> getOIDValue() {
+        return oidValue;
+    }
+
     /**
      * @return The standard key type used to represent this curve
      */
@@ -258,6 +277,49 @@ public enum ECCurves implements NamedResource, 
OptionalFeature {
         return null;
     }
 
+    public static ECCurves fromOIDValue(List<? extends Number> oid) {
+        if (GenericUtils.isEmpty(oid)) {
+            return null;
+        }
+
+        for (ECCurves c : VALUES) {
+            List<? extends Number> v = c.getOIDValue();
+            if (oid.size() != v.size()) {
+                continue;
+            }
+
+            boolean matches = true;
+            for (int index = 0; index < v.size(); index++) {
+                Number exp = v.get(index);
+                Number act = oid.get(index);
+                if (exp.intValue() != act.intValue()) {
+                    matches = false;
+                    break;
+                }
+            }
+
+            if (matches) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    public static ECCurves fromOID(String oid) {
+        if (GenericUtils.isEmpty(oid)) {
+            return null;
+        }
+
+        for (ECCurves c : VALUES) {
+            if (oid.equalsIgnoreCase(c.getOID())) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
     /**
      * @param params The curve's {@link ECParameterSpec}
      * @return The curve's key size in bits
@@ -305,6 +367,59 @@ public enum ECCurves implements NamedResource, 
OptionalFeature {
         return output;
     }
 
+    /**
+     * Converts the given octet string (defined by ASN.1 specifications) to a 
{@link BigInteger}
+     * As octet strings always represent positive integers, a zero-byte is 
prepended to
+     * the given array if necessary (if is MSB equal to 1), then this is 
converted to BigInteger
+     * The conversion is defined in the Section 2.3.8
+     *
+     * @param octets - octet string bytes to be converted
+     * @return The {@link BigInteger} representation of the octet string
+     */
+    public static BigInteger octetStringToInteger(byte... octets) {
+        if (octets == null) {
+            return null;
+        } else if (octets.length == 0) {
+            return BigInteger.ZERO;
+        } else {
+            return new BigInteger(1, octets);
+        }
+    }
+
+    public static ECPoint octetStringToEcPoint(byte... octets) {
+        if (NumberUtils.isEmpty(octets)) {
+            return null;
+        }
+
+        int startIndex = findFirstNonZeroIndex(octets);
+        if (startIndex < 0) {
+            throw new IllegalArgumentException("All zeroes ECPoint N/A");
+        }
+
+        byte indicator = octets[startIndex];
+        ECCurves.ECPointCompression compression = 
ECCurves.ECPointCompression.fromIndicatorValue(indicator);
+        if (compression == null) {
+            throw new UnsupportedOperationException("Unknown compression 
indicator value: 0x" + Integer.toHexString(indicator & 0xFF));
+        }
+
+        // The coordinates actually start after the compression indicator
+        return compression.octetStringToEcPoint(octets, startIndex + 1, 
octets.length - startIndex - 1);
+    }
+
+    private static int findFirstNonZeroIndex(byte... octets) {
+        if (NumberUtils.isEmpty(octets)) {
+            return -1;
+        }
+
+        for (int index = 0; index < octets.length; index++) {
+            if (octets[index] != 0) {
+                return index;
+            }
+        }
+
+        return -1;    // all zeroes
+    }
+
     public static final class Constants {
         /**
          * Standard prefix of NISTP key types when encoded
@@ -315,4 +430,146 @@ public enum ECCurves implements NamedResource, 
OptionalFeature {
         public static final String NISTP384 = "nistp384";
         public static final String NISTP521 = "nistp521";
     }
+
+    /**
+     * The various {@link ECPoint} representation compression indicators
+     *
+     * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD 
Project</a>
+     * @see <A HREF="https://www.ietf.org/rfc/rfc5480.txt";>RFC-5480 - section 
2.2</A>
+     */
+    public enum ECPointCompression {
+        // see http://tools.ietf.org/html/draft-jivsov-ecc-compact-00
+        // see 
http://crypto.stackexchange.com/questions/8914/ecdsa-compressed-public-key-point-back-to-uncompressed-public-key-point
+        VARIANT2((byte) 0x02) {
+            @Override
+            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, 
int len) {
+                byte[] xp = new byte[len];
+                System.arraycopy(octets, startIndex, xp, 0, len);
+                BigInteger x = octetStringToInteger(xp);
+
+                // TODO derive even Y...
+                throw new 
UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + 
") compression support N/A");
+            }
+        },
+        VARIANT3((byte) 0x03) {
+            @Override
+            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, 
int len) {
+                byte[] xp = new byte[len];
+                System.arraycopy(octets, startIndex, xp, 0, len);
+                BigInteger x = octetStringToInteger(xp);
+
+                // TODO derive odd Y...
+                throw new 
UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + 
") compression support N/A");
+            }
+        },
+        UNCOMPRESSED((byte) 0x04) {
+            @Override
+            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, 
int len) {
+                int numElements = len / 2;    /* x, y */
+                if (len != (numElements * 2)) {    // make sure length is not 
odd
+                    throw new IllegalArgumentException("octetStringToEcPoint(" 
+ name() + ") "
+                            + " invalid remainder octets representation: "
+                            + " expected=" + (2 * numElements) + ", actual=" + 
len);
+                }
+
+                byte[] xp = new byte[numElements];
+                byte[] yp = new byte[numElements];
+                System.arraycopy(octets, startIndex, xp, 0, numElements);
+                System.arraycopy(octets, startIndex + numElements, yp, 0, 
numElements);
+
+                BigInteger x = octetStringToInteger(xp);
+                BigInteger y = octetStringToInteger(yp);
+                return new ECPoint(x, y);
+            }
+
+            @Override
+            public void writeECPoint(OutputStream s, String curveName, ECPoint 
p) throws IOException {
+                ECCurves curve = fromCurveName(curveName);
+                if (curve == null) {
+                    throw new StreamCorruptedException("writeECPoint(" + 
name() + ")[" + curveName + "] cannot determine octets count");
+                }
+
+                int numElements = curve.getNumPointOctets();
+                KeyEntryResolver.encodeInt(s, 1 /* the indicator */ + 2 * 
numElements);
+                s.write(getIndicatorValue());
+                writeCoordinate(s, "X", p.getAffineX(), numElements);
+                writeCoordinate(s, "Y", p.getAffineY(), numElements);
+            }
+        };
+
+        public static final Set<ECPointCompression> VALUES =
+                
Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class));
+
+        private final byte indicatorValue;
+
+        ECPointCompression(byte indicator) {
+            indicatorValue = indicator;
+        }
+
+        public final byte getIndicatorValue() {
+            return indicatorValue;
+        }
+
+        public abstract ECPoint octetStringToEcPoint(byte[] octets, int 
startIndex, int len);
+
+        public byte[] ecPointToOctetString(String curveName, ECPoint p) {
+            try (ByteArrayOutputStream baos = new ByteArrayOutputStream((2 * 
66) + Long.SIZE)) {
+                writeECPoint(baos, curveName, p);
+                return baos.toByteArray();
+            } catch (IOException e) {
+                throw new RuntimeException("ecPointToOctetString(" + curveName 
+ ")"
+                        + " failed (" + e.getClass().getSimpleName() + ")"
+                        + " to write data: " + e.getMessage(),
+                        e);
+            }
+        }
+
+        public void writeECPoint(OutputStream s, String curveName, ECPoint p) 
throws IOException {
+            if (s == null) {
+                throw new EOFException("No output stream");
+            }
+
+            throw new StreamCorruptedException("writeECPoint(" + name() + ")[" 
+ p + "] N/A");
+        }
+
+        protected void writeCoordinate(OutputStream s, String n, BigInteger v, 
int numElements) throws IOException {
+            byte[] vp = v.toByteArray();
+            int startIndex = 0;
+            int vLen = vp.length;
+            if (vLen > numElements) {
+                if (vp[0] == 0) {   // skip artificial positive sign
+                    startIndex++;
+                    vLen--;
+                }
+            }
+
+            if (vLen > numElements) {
+                throw new StreamCorruptedException("writeCoordinate(" + name() 
+ ")[" + n + "]"
+                        + " value length (" + vLen + ") exceeds max. (" + 
numElements + ")"
+                        + " for " + v);
+            }
+
+            if (vLen < numElements) {
+                byte[] tmp = new byte[numElements];
+                System.arraycopy(vp, startIndex, tmp, numElements - vLen, 
vLen);
+                vp = tmp;
+            }
+
+            s.write(vp, startIndex, vLen);
+        }
+
+        public static ECPointCompression fromIndicatorValue(int value) {
+            if ((value < 0) || (value > 0xFF)) {
+                return null;    // must be a byte value
+            }
+
+            for (ECPointCompression c : VALUES) {
+                if (value == c.getIndicatorValue()) {
+                    return c;
+                }
+            }
+
+            return null;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index a4787cb..bd56559 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -28,6 +28,7 @@ import java.nio.file.Path;
 import java.nio.file.attribute.PosixFilePermission;
 import java.security.GeneralSecurityException;
 import java.security.Key;
+import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -39,10 +40,13 @@ import java.security.interfaces.ECKey;
 import java.security.interfaces.ECPrivateKey;
 import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -86,6 +90,11 @@ public final class KeyUtils {
     public static final String RSA_ALGORITHM = "RSA";
 
     /**
+     * The most commonly used RSA public key exponent
+     */
+    public static final BigInteger DEFAULT_RSA_PUBLIC_EXPONENT = new 
BigInteger("65537");
+
+    /**
      * Name of algorithm for DSS keys to be used when calling security provider
      */
     public static final String DSS_ALGORITHM = "DSA";
@@ -759,6 +768,33 @@ public final class KeyUtils {
         }
     }
 
+    public static boolean compareKeys(PublicKey k1, PublicKey k2) {
+        if ((k1 instanceof RSAPublicKey) && (k2 instanceof RSAPublicKey)) {
+            return compareRSAKeys(RSAPublicKey.class.cast(k1), 
RSAPublicKey.class.cast(k2));
+        } else if ((k1 instanceof DSAPublicKey) && (k2 instanceof 
DSAPublicKey)) {
+            return compareDSAKeys(DSAPublicKey.class.cast(k1), 
DSAPublicKey.class.cast(k2));
+        } else if ((k1 instanceof ECPublicKey) && (k2 instanceof ECPublicKey)) 
{
+            return compareECKeys(ECPublicKey.class.cast(k1), 
ECPublicKey.class.cast(k2));
+        } else if ((k1 != null) && 
SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
+                    && (k2 != null) && 
SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
+            return SecurityUtils.compareEDDSAPPublicKeys(k1, k2);
+        } else {
+            return false;   // either key is null or not of same class
+        }
+    }
+
+    public static PublicKey recoverPublicKey(PrivateKey key) throws 
GeneralSecurityException {
+        if (key instanceof RSAPrivateKey) {
+            return recoverRSAPublicKey((RSAPrivateKey) key);
+        } else if (key instanceof DSAPrivateKey) {
+            return recoverDSAPublicKey((DSAPrivateKey) key);
+        } else if ((key != null) && 
SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
+            return SecurityUtils.recoverEDDSAPublicKey(key);
+        } else {
+            return null;
+        }
+    }
+
     public static boolean compareKeys(PrivateKey k1, PrivateKey k2) {
         if ((k1 instanceof RSAPrivateKey) && (k2 instanceof RSAPrivateKey)) {
             return compareRSAKeys(RSAPrivateKey.class.cast(k1), 
RSAPrivateKey.class.cast(k2));
@@ -774,72 +810,68 @@ public final class KeyUtils {
         }
     }
 
-    public static boolean compareRSAKeys(RSAPrivateKey k1, RSAPrivateKey k2) {
+    public static boolean compareRSAKeys(RSAPublicKey k1, RSAPublicKey k2) {
         if (Objects.equals(k1, k2)) {
             return true;
         } else if (k1 == null || k2 == null) {
             return false;   // both null is covered by Objects#equals
         } else {
-            return Objects.equals(k1.getModulus(), k2.getModulus())
-                && Objects.equals(k1.getPrivateExponent(), 
k2.getPrivateExponent());
+            return Objects.equals(k1.getPublicExponent(), 
k2.getPublicExponent())
+                && Objects.equals(k1.getModulus(), k2.getModulus());
         }
     }
 
-    public static boolean compareDSAKeys(DSAPrivateKey k1, DSAPrivateKey k2) {
+    public static boolean compareRSAKeys(RSAPrivateKey k1, RSAPrivateKey k2) {
         if (Objects.equals(k1, k2)) {
             return true;
         } else if (k1 == null || k2 == null) {
             return false;   // both null is covered by Objects#equals
         } else {
-            return Objects.equals(k1.getX(), k2.getX())
-                && compareDSAParams(k1.getParams(), k2.getParams());
+            return Objects.equals(k1.getModulus(), k2.getModulus())
+                && Objects.equals(k1.getPrivateExponent(), 
k2.getPrivateExponent());
         }
     }
 
-    public static boolean compareECKeys(ECPrivateKey k1, ECPrivateKey k2) {
-        if (Objects.equals(k1, k2)) {
-            return true;
-        } else if (k1 == null || k2 == null) {
-            return false;   // both null is covered by Objects#equals
+    public static RSAPublicKey recoverRSAPublicKey(RSAPrivateKey privateKey) 
throws GeneralSecurityException {
+        if (privateKey instanceof RSAPrivateCrtKey) {
+            return recoverFromRSAPrivateCrtKey((RSAPrivateCrtKey) privateKey);
         } else {
-            return Objects.equals(k1.getS(), k2.getS())
-                && compareECParams(k1.getParams(), k2.getParams());
+            // Not ideal, but best we can do under the circumstances
+            return recoverRSAPublicKey(privateKey.getModulus(), 
DEFAULT_RSA_PUBLIC_EXPONENT);
         }
     }
 
-    public static boolean compareKeys(PublicKey k1, PublicKey k2) {
-        if ((k1 instanceof RSAPublicKey) && (k2 instanceof RSAPublicKey)) {
-            return compareRSAKeys(RSAPublicKey.class.cast(k1), 
RSAPublicKey.class.cast(k2));
-        } else if ((k1 instanceof DSAPublicKey) && (k2 instanceof 
DSAPublicKey)) {
-            return compareDSAKeys(DSAPublicKey.class.cast(k1), 
DSAPublicKey.class.cast(k2));
-        } else if ((k1 instanceof ECPublicKey) && (k2 instanceof ECPublicKey)) 
{
-            return compareECKeys(ECPublicKey.class.cast(k1), 
ECPublicKey.class.cast(k2));
-        } else if ((k1 != null) && 
SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
-                    && (k2 != null) && 
SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
-            return SecurityUtils.compareEDDSAPPublicKeys(k1, k2);
-        } else {
-            return false;   // either key is null or not of same class
-        }
+    public static RSAPublicKey recoverFromRSAPrivateCrtKey(RSAPrivateCrtKey 
rsaKey) throws GeneralSecurityException {
+        return recoverRSAPublicKey(rsaKey.getPrimeP(), rsaKey.getPrimeQ(), 
rsaKey.getPublicExponent());
     }
 
-    public static boolean compareRSAKeys(RSAPublicKey k1, RSAPublicKey k2) {
+    public static RSAPublicKey recoverRSAPublicKey(BigInteger p, BigInteger q, 
BigInteger publicExponent) throws GeneralSecurityException {
+        return recoverRSAPublicKey(p.multiply(q), publicExponent);
+    }
+
+    public static RSAPublicKey recoverRSAPublicKey(BigInteger modulus, 
BigInteger publicExponent) throws GeneralSecurityException {
+        KeyFactory kf = SecurityUtils.getKeyFactory(RSA_ALGORITHM);
+        return (RSAPublicKey) kf.generatePublic(new RSAPublicKeySpec(modulus, 
publicExponent));
+    }
+
+    public static boolean compareDSAKeys(DSAPublicKey k1, DSAPublicKey k2) {
         if (Objects.equals(k1, k2)) {
             return true;
         } else if (k1 == null || k2 == null) {
             return false;   // both null is covered by Objects#equals
         } else {
-            return Objects.equals(k1.getPublicExponent(), 
k2.getPublicExponent())
-                && Objects.equals(k1.getModulus(), k2.getModulus());
+            return Objects.equals(k1.getY(), k2.getY())
+                && compareDSAParams(k1.getParams(), k2.getParams());
         }
     }
 
-    public static boolean compareDSAKeys(DSAPublicKey k1, DSAPublicKey k2) {
+    public static boolean compareDSAKeys(DSAPrivateKey k1, DSAPrivateKey k2) {
         if (Objects.equals(k1, k2)) {
             return true;
         } else if (k1 == null || k2 == null) {
             return false;   // both null is covered by Objects#equals
         } else {
-            return Objects.equals(k1.getY(), k2.getY())
+            return Objects.equals(k1.getX(), k2.getX())
                 && compareDSAParams(k1.getParams(), k2.getParams());
         }
     }
@@ -856,6 +888,29 @@ public final class KeyUtils {
         }
     }
 
+    // based on code from 
https://github.com/alexo/SAML-2.0/blob/master/java-opensaml/opensaml-security-api/src/main/java/org/opensaml/xml/security/SecurityHelper.java
+    public static DSAPublicKey recoverDSAPublicKey(DSAPrivateKey privateKey) 
throws GeneralSecurityException {
+        DSAParams keyParams = privateKey.getParams();
+        BigInteger p = keyParams.getP();
+        BigInteger x = privateKey.getX();
+        BigInteger q = keyParams.getQ();
+        BigInteger g = keyParams.getG();
+        BigInteger y = g.modPow(x, p);
+        KeyFactory kf = SecurityUtils.getKeyFactory(DSS_ALGORITHM);
+        return (DSAPublicKey) kf.generatePublic(new DSAPublicKeySpec(y, p, q, 
g));
+    }
+
+    public static boolean compareECKeys(ECPrivateKey k1, ECPrivateKey k2) {
+        if (Objects.equals(k1, k2)) {
+            return true;
+        } else if (k1 == null || k2 == null) {
+            return false;   // both null is covered by Objects#equals
+        } else {
+            return Objects.equals(k1.getS(), k2.getS())
+                && compareECParams(k1.getParams(), k2.getParams());
+        }
+    }
+
     public static boolean compareECKeys(ECPublicKey k1, ECPublicKey k2) {
         if (Objects.equals(k1, k2)) {
             return true;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
index d180e9e..56ff39e 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
@@ -19,13 +19,9 @@
 
 package org.apache.sshd.common.config.keys.impl;
 
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.StreamCorruptedException;
-import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
@@ -39,15 +35,11 @@ import java.security.spec.ECPoint;
 import java.security.spec.ECPrivateKeySpec;
 import java.security.spec.ECPublicKeySpec;
 import java.security.spec.InvalidKeySpecException;
-import java.util.Collections;
-import java.util.EnumSet;
 import java.util.Objects;
-import java.util.Set;
 
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.KeyUtils;
-import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
@@ -88,7 +80,7 @@ public class ECDSAPublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecoder<EC
         byte[] octets = KeyEntryResolver.readRLEBytes(keyData);
         ECPoint w;
         try {
-            w = octetStringToEcPoint(octets);
+            w = ECCurves.octetStringToEcPoint(octets);
             if (w == null) {
                 throw new InvalidKeySpecException("No ECPoint generated for 
curve=" + keyCurveName
                         + " from octets=" + BufferUtils.toHex(':', octets));
@@ -150,7 +142,7 @@ public class ECDSAPublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecoder<EC
         KeyEntryResolver.encodeString(s, keyType);
         // see rfc5656 section 3.1
         KeyEntryResolver.encodeString(s, curveName);
-        ECPointCompression.UNCOMPRESSED.writeECPoint(s, curveName, key.getW());
+        ECCurves.ECPointCompression.UNCOMPRESSED.writeECPoint(s, curveName, 
key.getW());
         return keyType;
     }
 
@@ -183,200 +175,4 @@ public class ECDSAPublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecoder<EC
             throw new NoSuchProviderException("ECC not supported");
         }
     }
-
-    public static ECPoint octetStringToEcPoint(byte... octets) {
-        if (NumberUtils.isEmpty(octets)) {
-            return null;
-        }
-
-        int startIndex = findFirstNonZeroIndex(octets);
-        if (startIndex < 0) {
-            throw new IllegalArgumentException("All zeroes ECPoint N/A");
-        }
-
-        byte indicator = octets[startIndex];
-        ECPointCompression compression = 
ECPointCompression.fromIndicatorValue(indicator);
-        if (compression == null) {
-            throw new UnsupportedOperationException("Unknown compression 
indicator value: 0x" + Integer.toHexString(indicator & 0xFF));
-        }
-
-        // The coordinates actually start after the compression indicator
-        return compression.octetStringToEcPoint(octets, startIndex + 1, 
octets.length - startIndex - 1);
-    }
-
-    private static int findFirstNonZeroIndex(byte... octets) {
-        if (NumberUtils.isEmpty(octets)) {
-            return -1;
-        }
-
-        for (int index = 0; index < octets.length; index++) {
-            if (octets[index] != 0) {
-                return index;
-            }
-        }
-
-        return -1;    // all zeroes
-    }
-
-    /**
-     * The various {@link ECPoint} representation compression indicators
-     *
-     * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD 
Project</a>
-     * @see <A HREF="https://www.ietf.org/rfc/rfc5480.txt";>RFC-5480 - section 
2.2</A>
-     */
-    public enum ECPointCompression {
-        // see http://tools.ietf.org/html/draft-jivsov-ecc-compact-00
-        // see 
http://crypto.stackexchange.com/questions/8914/ecdsa-compressed-public-key-point-back-to-uncompressed-public-key-point
-        VARIANT2((byte) 0x02) {
-            @Override
-            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, 
int len) {
-                byte[] xp = new byte[len];
-                System.arraycopy(octets, startIndex, xp, 0, len);
-                BigInteger x = octetStringToInteger(xp);
-
-                // TODO derive even Y...
-                throw new 
UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + 
") compression support N/A");
-            }
-        },
-        VARIANT3((byte) 0x03) {
-            @Override
-            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, 
int len) {
-                byte[] xp = new byte[len];
-                System.arraycopy(octets, startIndex, xp, 0, len);
-                BigInteger x = octetStringToInteger(xp);
-
-                // TODO derive odd Y...
-                throw new 
UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + 
") compression support N/A");
-            }
-        },
-        UNCOMPRESSED((byte) 0x04) {
-            @Override
-            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, 
int len) {
-                int numElements = len / 2;    /* x, y */
-                if (len != (numElements * 2)) {    // make sure length is not 
odd
-                    throw new IllegalArgumentException("octetStringToEcPoint(" 
+ name() + ") "
-                            + " invalid remainder octets representation: "
-                            + " expected=" + (2 * numElements) + ", actual=" + 
len);
-                }
-
-                byte[] xp = new byte[numElements];
-                byte[] yp = new byte[numElements];
-                System.arraycopy(octets, startIndex, xp, 0, numElements);
-                System.arraycopy(octets, startIndex + numElements, yp, 0, 
numElements);
-
-                BigInteger x = octetStringToInteger(xp);
-                BigInteger y = octetStringToInteger(yp);
-                return new ECPoint(x, y);
-            }
-
-            @Override
-            public void writeECPoint(OutputStream s, String curveName, ECPoint 
p) throws IOException {
-                ECCurves curve = ECCurves.fromCurveName(curveName);
-                if (curve == null) {
-                    throw new StreamCorruptedException("writeECPoint(" + 
name() + ")[" + curveName + "] cannot determine octets count");
-                }
-
-                int numElements = curve.getNumPointOctets();
-                KeyEntryResolver.encodeInt(s, 1 /* the indicator */ + 2 * 
numElements);
-                s.write(getIndicatorValue());
-                writeCoordinate(s, "X", p.getAffineX(), numElements);
-                writeCoordinate(s, "Y", p.getAffineY(), numElements);
-            }
-
-        };
-
-        public static final Set<ECPointCompression> VALUES =
-                
Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class));
-
-        private final byte indicatorValue;
-
-        ECPointCompression(byte indicator) {
-            indicatorValue = indicator;
-        }
-
-        public final byte getIndicatorValue() {
-            return indicatorValue;
-        }
-
-        public abstract ECPoint octetStringToEcPoint(byte[] octets, int 
startIndex, int len);
-
-        public byte[] ecPointToOctetString(String curveName, ECPoint p) {
-            try (ByteArrayOutputStream baos = new ByteArrayOutputStream((2 * 
66) + Long.SIZE)) {
-                writeECPoint(baos, curveName, p);
-                return baos.toByteArray();
-            } catch (IOException e) {
-                throw new RuntimeException("ecPointToOctetString(" + curveName 
+ ")"
-                        + " failed (" + e.getClass().getSimpleName() + ")"
-                        + " to write data: " + e.getMessage(),
-                        e);
-            }
-        }
-
-        public void writeECPoint(OutputStream s, String curveName, ECPoint p) 
throws IOException {
-            if (s == null) {
-                throw new EOFException("No output stream");
-            }
-
-            throw new StreamCorruptedException("writeECPoint(" + name() + ")[" 
+ p + "] N/A");
-        }
-
-        protected void writeCoordinate(OutputStream s, String n, BigInteger v, 
int numElements) throws IOException {
-            byte[] vp = v.toByteArray();
-            int startIndex = 0;
-            int vLen = vp.length;
-            if (vLen > numElements) {
-                if (vp[0] == 0) {   // skip artificial positive sign
-                    startIndex++;
-                    vLen--;
-                }
-            }
-
-            if (vLen > numElements) {
-                throw new StreamCorruptedException("writeCoordinate(" + name() 
+ ")[" + n + "]"
-                        + " value length (" + vLen + ") exceeds max. (" + 
numElements + ")"
-                        + " for " + v);
-            }
-
-            if (vLen < numElements) {
-                byte[] tmp = new byte[numElements];
-                System.arraycopy(vp, startIndex, tmp, numElements - vLen, 
vLen);
-                vp = tmp;
-            }
-
-            s.write(vp, startIndex, vLen);
-        }
-
-        public static ECPointCompression fromIndicatorValue(int value) {
-            if ((value < 0) || (value > 0xFF)) {
-                return null;    // must be a byte value
-            }
-
-            for (ECPointCompression c : VALUES) {
-                if (value == c.getIndicatorValue()) {
-                    return c;
-                }
-            }
-
-            return null;
-        }
-
-        /**
-         * Converts the given octet string (defined by ASN.1 specifications) 
to a {@link BigInteger}
-         * As octet strings always represent positive integers, a zero-byte is 
prepended to
-         * the given array if necessary (if is MSB equal to 1), then this is 
converted to BigInteger
-         * The conversion is defined in the Section 2.3.8
-         *
-         * @param octets - octet string bytes to be converted
-         * @return The {@link BigInteger} representation of the octet string
-         */
-        public static BigInteger octetStringToInteger(byte... octets) {
-            if (octets == null) {
-                return null;
-            } else if (octets.length == 0) {
-                return BigInteger.ZERO;
-            } else {
-                return new BigInteger(1, octets);
-            }
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java
new file mode 100644
index 0000000..b2205ed
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class AESPrivateKeyObfuscator extends AbstractPrivateKeyObfuscator {
+    public static final String CIPHER_NAME = "AES";
+    public static final AESPrivateKeyObfuscator INSTANCE = new 
AESPrivateKeyObfuscator();
+
+    public AESPrivateKeyObfuscator() {
+        super(CIPHER_NAME);
+    }
+
+    @Override
+    public List<Integer> getSupportedKeySizes() {
+        return getAvailableKeyLengths();
+    }
+
+    @Override
+    public byte[] applyPrivateKeyCipher(byte[] bytes, 
PrivateKeyEncryptionContext encContext, boolean encryptIt) throws 
GeneralSecurityException {
+        int keyLength = resolveKeyLength(encContext);
+        byte[] keyValue = deriveEncryptionKey(encContext, keyLength / 
Byte.SIZE);
+        return applyPrivateKeyCipher(bytes, encContext, keyLength, keyValue, 
encryptIt);
+    }
+
+    @Override
+    protected int resolveKeyLength(PrivateKeyEncryptionContext encContext) 
throws GeneralSecurityException {
+        String cipherType = encContext.getCipherType();
+        try {
+            int keyLength = Integer.parseInt(cipherType);
+            List<Integer> sizes = getSupportedKeySizes();
+            for (Integer s : sizes) {
+                if (s.intValue() == keyLength) {
+                    return keyLength;
+                }
+            }
+
+            throw new InvalidKeySpecException("Unknown " + getCipherName() + " 
key length: " + cipherType + " - supported: " + sizes);
+        } catch (NumberFormatException e) {
+            throw new InvalidKeySpecException("Bad " + getCipherName() + " key 
length (" + cipherType + "): " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * @return A {@link List} of {@link Integer}s holding the available key
+     * lengths values (in bits) for the JVM. <B>Note:</B> AES 256 requires
+     * special JCE policy extension installation (e.g., for Java 7 see
+     * <A 
HREF="http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html";>this
 link</A>)
+     */
+    @SuppressWarnings("synthetic-access")
+    public static List<Integer> getAvailableKeyLengths() {
+        return LazyValuesHolder.KEY_LENGTHS;
+    }
+
+    private static class LazyValuesHolder {
+        private static final List<Integer> KEY_LENGTHS =
+                Collections.unmodifiableList(detectSupportedKeySizes());
+
+        // AES 256 requires special JCE policy extension installation
+        private static List<Integer> detectSupportedKeySizes() {
+            List<Integer> sizes = new ArrayList<>();
+            for (int keyLength = 128; keyLength < Short.MAX_VALUE /* just so 
it doesn't go forever */; keyLength += 64) {
+                try {
+                    byte[] keyAsBytes = new byte[keyLength / Byte.SIZE];
+                    Key key = new SecretKeySpec(keyAsBytes, CIPHER_NAME);
+                    Cipher c = SecurityUtils.getCipher(CIPHER_NAME);
+                    c.init(Cipher.DECRYPT_MODE, key);
+                    sizes.add(Integer.valueOf(keyLength));
+                } catch (GeneralSecurityException e) {
+                    return sizes;
+                }
+            }
+
+            throw new IllegalStateException("No limit encountered: " + sizes);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
index 7021830..de144be 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
@@ -26,7 +26,6 @@ import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.util.ArrayList;
-import java.util.Base64;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -138,12 +137,7 @@ public abstract class AbstractKeyPairResourceParser 
extends AbstractLoggingBean
     public Collection<KeyPair> extractKeyPairs(
             String resourceKey, String beginMarker, String endMarker, 
FilePasswordProvider passwordProvider, List<String> lines)
                     throws IOException, GeneralSecurityException {
-        String data = GenericUtils.join(lines, ' ');
-        data = data.replaceAll("\\s", "");
-        data = data.trim();
-
-        Base64.Decoder decoder = Base64.getDecoder();
-        return extractKeyPairs(resourceKey, beginMarker, endMarker, 
passwordProvider, decoder.decode(data));
+        return extractKeyPairs(resourceKey, beginMarker, endMarker, 
passwordProvider, KeyPairResourceParser.extractDataBytes(lines));
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractPrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractPrivateKeyObfuscator.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractPrivateKeyObfuscator.java
new file mode 100644
index 0000000..ac93ab4
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractPrivateKeyObfuscator.java
@@ -0,0 +1,191 @@
+/*
+ * 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;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPrivateKeyObfuscator implements 
PrivateKeyObfuscator {
+    private final String algName;
+
+    protected AbstractPrivateKeyObfuscator(String name) {
+        algName = ValidateUtils.checkNotNullAndNotEmpty(name, "No name 
specified");
+    }
+
+    @Override
+    public final String getCipherName() {
+        return algName;
+    }
+
+    @Override
+    public byte[] generateInitializationVector(PrivateKeyEncryptionContext 
encContext) throws GeneralSecurityException {
+        return generateInitializationVector(resolveKeyLength(encContext));
+    }
+
+    @Override
+    public <A extends Appendable> A appendPrivateKeyEncryptionContext(A sb, 
PrivateKeyEncryptionContext encContext) throws IOException {
+        if (encContext == null) {
+            return sb;
+        }
+
+        sb.append("DEK-Info: ").append(encContext.getCipherName())
+          .append('-').append(encContext.getCipherType())
+          .append('-').append(encContext.getCipherMode());
+
+        byte[] initVector = encContext.getInitVector();
+        Objects.requireNonNull(initVector, "No encryption init vector");
+        ValidateUtils.checkTrue(initVector.length > 0, "Empty encryption init 
vector");
+        BufferUtils.appendHex(sb.append(','), BufferUtils.EMPTY_HEX_SEPARATOR, 
initVector);
+        sb.append(System.lineSeparator());
+        return sb;
+    }
+
+    protected byte[] generateInitializationVector(int keyLength) {
+        int keySize = keyLength / Byte.SIZE;
+        if ((keyLength % Byte.SIZE) != 0) { // e.g., if 36-bits then we need 5 
bytes to hold
+            keySize++;
+        }
+
+        byte[] initVector = new byte[keySize];
+        Random randomizer = new SecureRandom();  // TODO consider using some 
pre-created singleton instance
+        randomizer.nextBytes(initVector);
+        return initVector;
+    }
+
+    protected abstract int resolveKeyLength(PrivateKeyEncryptionContext 
encContext) throws GeneralSecurityException;
+
+    // see 
http://martin.kleppmann.com/2013/05/24/improving-security-of-ssh-private-keys.html
+    // see http://www.ict.griffith.edu.au/anthony/info/crypto/openssl.hints 
(Password to Encryption Key section)
+    // see 
http://openssl.6102.n7.nabble.com/DES-EDE3-CBC-technical-details-td24883.html
+    protected byte[] deriveEncryptionKey(PrivateKeyEncryptionContext 
encContext, int outputKeyLength) throws GeneralSecurityException {
+        Objects.requireNonNull(encContext, "No encryption context");
+        ValidateUtils.checkNotNullAndNotEmpty(encContext.getCipherName(), "No 
cipher name");
+        ValidateUtils.checkNotNullAndNotEmpty(encContext.getCipherType(), "No 
cipher type");
+        ValidateUtils.checkNotNullAndNotEmpty(encContext.getCipherMode(), "No 
cipher mode");
+
+        byte[] initVector = Objects.requireNonNull(encContext.getInitVector(), 
"No encryption init vector");
+        ValidateUtils.checkTrue(initVector.length > 0, "Empty encryption init 
vector");
+
+        String password = 
ValidateUtils.checkNotNullAndNotEmpty(encContext.getPassword(), "No encryption 
password");
+        byte[] passBytes = password.getBytes(StandardCharsets.UTF_8);
+        byte[] keyValue = new byte[outputKeyLength];
+        MessageDigest hash = 
SecurityUtils.getMessageDigest(BuiltinDigests.Constants.MD5);
+        byte[]  prevHash = GenericUtils.EMPTY_BYTE_ARRAY;
+        for (int index = 0, remLen = keyValue.length; index < 
keyValue.length;) {
+            hash.reset();    // just making sure
+
+            hash.update(prevHash, 0, prevHash.length);
+            hash.update(passBytes, 0, passBytes.length);
+            hash.update(initVector, 0, Math.min(initVector.length, 8));
+
+            prevHash = hash.digest();
+
+            System.arraycopy(prevHash, 0, keyValue, index, Math.min(remLen, 
prevHash.length));
+            index += prevHash.length;
+            remLen -= prevHash.length;
+        }
+
+        return keyValue;
+    }
+
+    protected byte[] applyPrivateKeyCipher(byte[] bytes, 
PrivateKeyEncryptionContext encContext, int numBits, byte[] keyValue, boolean 
encryptIt)
+            throws GeneralSecurityException {
+        Objects.requireNonNull(encContext, "No encryption context");
+        String cipherName = 
ValidateUtils.checkNotNullAndNotEmpty(encContext.getCipherName(), "No cipher 
name");
+        ValidateUtils.checkNotNullAndNotEmpty(encContext.getCipherType(), "No 
cipher type");
+        String cipherMode = 
ValidateUtils.checkNotNullAndNotEmpty(encContext.getCipherMode(), "No cipher 
mode");
+
+        Objects.requireNonNull(bytes, "No source data");
+        Objects.requireNonNull(keyValue, "No encryption key");
+        ValidateUtils.checkTrue(keyValue.length > 0, "Empty encryption key");
+
+        byte[] initVector = Objects.requireNonNull(encContext.getInitVector(), 
"No encryption init vector");
+        ValidateUtils.checkTrue(initVector.length > 0, "Empty encryption init 
vector");
+
+        String xform = cipherName + "/" + cipherMode + "/NoPadding";
+        int maxAllowedBits = Cipher.getMaxAllowedKeyLength(xform);
+        // see 
http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
+        if (numBits > maxAllowedBits) {
+            throw new InvalidKeySpecException("applyPrivateKeyCipher(" + xform 
+ ")[encrypt=" + encryptIt + "]"
+                                            + " required key length (" + 
numBits + ")"
+                                            + " exceeds max. available: " + 
maxAllowedBits);
+        }
+
+        SecretKeySpec skeySpec = new SecretKeySpec(keyValue, cipherName);
+        IvParameterSpec ivspec = new IvParameterSpec(initVector);
+        Cipher cipher = SecurityUtils.getCipher(xform);
+        int blockSize = cipher.getBlockSize();
+        int dataSize = bytes.length;
+        cipher.init(encryptIt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, 
skeySpec, ivspec);
+        if (blockSize <= 0) {
+            return cipher.doFinal(bytes);
+        }
+
+        int remLen = dataSize % blockSize;
+        if (remLen <= 0) {
+            return cipher.doFinal(bytes);
+        }
+
+        int updateSize = dataSize - remLen;
+        byte[] lastBlock = new byte[blockSize];
+        Arrays.fill(lastBlock, (byte) 10);
+        System.arraycopy(bytes, updateSize, lastBlock, 0, remLen);
+
+        // TODO for some reason, calling cipher.update followed by 
cipher.doFinal does not work
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(dataSize);
+        try {
+            try {
+                byte[] buf = cipher.update(bytes, 0, updateSize);
+                baos.write(buf);
+
+                buf = cipher.doFinal(lastBlock);
+                baos.write(buf);
+            } finally {
+                baos.close();
+            }
+        } catch (IOException e) {
+            throw new GeneralSecurityException("applyPrivateKeyCipher(" + 
xform + ")[encrypt=" + encryptIt + "]"
+                                             + " failed (" + 
e.getClass().getSimpleName() + ")"
+                                             + " to split-write: " + 
e.getMessage(), e);
+        }
+
+        return baos.toByteArray();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/DESPrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/DESPrivateKeyObfuscator.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/DESPrivateKeyObfuscator.java
new file mode 100644
index 0000000..2043f06
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/DESPrivateKeyObfuscator.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class DESPrivateKeyObfuscator extends AbstractPrivateKeyObfuscator {
+    public static final int DEFAULT_KEY_LENGTH = 24 /* hardwired size for 3DES 
*/;
+    public static final List<Integer> AVAILABLE_KEY_LENGTHS =
+            
Collections.unmodifiableList(Collections.singletonList(Integer.valueOf(DEFAULT_KEY_LENGTH)));
+    public static final DESPrivateKeyObfuscator INSTANCE = new 
DESPrivateKeyObfuscator();
+
+    public DESPrivateKeyObfuscator() {
+        super("DES");
+    }
+
+    @Override
+    public byte[] applyPrivateKeyCipher(byte[] bytes, 
PrivateKeyEncryptionContext encContext, boolean encryptIt) throws 
GeneralSecurityException {
+        PrivateKeyEncryptionContext effContext = 
resolveEffectiveContext(encContext);
+        byte[] keyValue = deriveEncryptionKey(effContext, DEFAULT_KEY_LENGTH);
+        return applyPrivateKeyCipher(bytes, effContext, keyValue.length * 
Byte.SIZE, keyValue, encryptIt);
+    }
+
+    @Override
+    public List<Integer> getSupportedKeySizes() {
+        return AVAILABLE_KEY_LENGTHS;
+    }
+
+    @Override
+    protected int resolveKeyLength(PrivateKeyEncryptionContext encContext) 
throws GeneralSecurityException {
+        return DEFAULT_KEY_LENGTH;
+    }
+
+    @Override
+    protected byte[] generateInitializationVector(int keyLength) {
+        return super.generateInitializationVector(8 * Byte.SIZE);
+    }
+
+    public static final PrivateKeyEncryptionContext 
resolveEffectiveContext(PrivateKeyEncryptionContext encContext) {
+        if (encContext == null) {
+            return null;
+        }
+
+        String cipherName = encContext.getCipherName();
+        String cipherType = encContext.getCipherType();
+        PrivateKeyEncryptionContext effContext = encContext;
+        if ("EDE3".equalsIgnoreCase(cipherType)) {
+            cipherName += "ede";
+            effContext = encContext.clone();
+            effContext.setCipherName(cipherName);
+        }
+
+        return effContext;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
index beb592d..6c1f508 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -69,6 +70,23 @@ public interface KeyPairResourceParser extends 
KeyPairResourceLoader {
      */
     boolean canExtractKeyPairs(String resourceKey, List<String> lines) throws 
IOException, GeneralSecurityException;
 
+    /**
+     * Converts the lines assumed to contain BASE-64 encoded data into
+     * the actual content bytes.
+     *
+     * @param lines The data lines - empty lines and spaces are automatically
+     * deleted <U>before</U> BASE-64 decoding takes place.
+     * @return The decoded data bytes
+     */
+    static byte[] extractDataBytes(Collection<String> lines) {
+        String data = GenericUtils.join(lines, ' ');
+        data = data.replaceAll("\\s", "");
+        data = data.trim();
+
+        Base64.Decoder decoder = Base64.getDecoder();
+        return decoder.decode(data);
+    }
+
     static boolean containsMarkerLine(List<String> lines, String marker) {
         return containsMarkerLine(lines, 
Collections.singletonList(ValidateUtils.checkNotNullAndNotEmpty(marker, "No 
marker")));
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
new file mode 100644
index 0000000..d7ce237
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
@@ -0,0 +1,276 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class PrivateKeyEncryptionContext implements Cloneable {
+    public static final String  DEFAULT_CIPHER_MODE = "CBC";
+
+    private static final Map<String, PrivateKeyObfuscator> OBFUSCATORS =
+        new TreeMap<String, 
PrivateKeyObfuscator>(String.CASE_INSENSITIVE_ORDER) {
+            private static final long serialVersionUID = 1L;    // no 
serialization expected
+
+            {
+                for (PrivateKeyObfuscator o : new PrivateKeyObfuscator[]{
+                    AESPrivateKeyObfuscator.INSTANCE, 
DESPrivateKeyObfuscator.INSTANCE
+                }) {
+                    put(o.getCipherName(), o);
+                }
+            }
+    };
+
+    private String  cipherName;
+    private String cipherType;
+    private String cipherMode = DEFAULT_CIPHER_MODE;
+    private String password;
+    private byte[]  initVector;
+    private transient PrivateKeyObfuscator  obfuscator;
+
+    public PrivateKeyEncryptionContext() {
+        super();
+    }
+
+    public PrivateKeyEncryptionContext(String algInfo) {
+        parseAlgorithmInfo(algInfo);
+    }
+
+    public String getCipherName() {
+        return cipherName;
+    }
+
+    public void setCipherName(String value) {
+        cipherName = value;
+    }
+
+    public String getCipherType() {
+        return cipherType;
+    }
+
+    public void setCipherType(String value) {
+        cipherType = value;
+    }
+
+    public String getCipherMode() {
+        return cipherMode;
+    }
+
+    public void setCipherMode(String value) {
+        cipherMode = value;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String value) {
+        password = value;
+    }
+
+    public byte[] getInitVector() {
+        return initVector;
+    }
+
+    public void setInitVector(byte ... values) {
+        initVector = values;
+    }
+
+    public PrivateKeyObfuscator getPrivateKeyObfuscator() {
+        return obfuscator;
+    }
+
+    public void setPrivateKeyObfuscator(PrivateKeyObfuscator value) {
+        obfuscator = value;
+    }
+
+    public PrivateKeyObfuscator resolvePrivateKeyObfuscator() {
+        PrivateKeyObfuscator value = getPrivateKeyObfuscator();
+        if (value != null) {
+            return value;
+        }
+
+        return getRegisteredPrivateKeyObfuscator(getCipherName());
+    }
+
+    public static PrivateKeyObfuscator 
registerPrivateKeyObfuscator(PrivateKeyObfuscator o) {
+        return registerPrivateKeyObfuscator(Objects.requireNonNull(o, "No 
instance provided").getCipherName(), o);
+    }
+
+    public static PrivateKeyObfuscator registerPrivateKeyObfuscator(String 
cipherName, PrivateKeyObfuscator o) {
+        ValidateUtils.checkNotNullAndNotEmpty(cipherName, "No cipher name");
+        Objects.requireNonNull(o, "No instance provided");
+
+        synchronized (OBFUSCATORS) {
+            return OBFUSCATORS.put(cipherName, o);
+        }
+    }
+
+    public static boolean unregisterPrivateKeyObfuscator(PrivateKeyObfuscator 
o) {
+        Objects.requireNonNull(o, "No instance provided");
+        String  cipherName = o.getCipherName();
+        ValidateUtils.checkNotNullAndNotEmpty(cipherName, "No cipher name");
+
+        synchronized (OBFUSCATORS) {
+            PrivateKeyObfuscator prev = OBFUSCATORS.get(cipherName);
+            if (prev != o) {
+                return false;
+            }
+
+            OBFUSCATORS.remove(cipherName);
+        }
+
+        return true;
+    }
+
+    public static PrivateKeyObfuscator unregisterPrivateKeyObfuscator(String 
cipherName) {
+        ValidateUtils.checkNotNullAndNotEmpty(cipherName, "No cipher name");
+
+        synchronized (OBFUSCATORS) {
+            return OBFUSCATORS.remove(cipherName);
+        }
+    }
+
+    public static final PrivateKeyObfuscator 
getRegisteredPrivateKeyObfuscator(String cipherName) {
+        if (GenericUtils.isEmpty(cipherName)) {
+            return null;
+        }
+
+        synchronized (OBFUSCATORS) {
+            return OBFUSCATORS.get(cipherName);
+        }
+    }
+
+    public static final SortedSet<String> 
getRegisteredPrivateKeyObfuscatorCiphers() {
+        synchronized (OBFUSCATORS) {
+            Collection<String> names = OBFUSCATORS.keySet();
+            return GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, 
names);
+        }
+    }
+
+    public static final List<PrivateKeyObfuscator> 
getRegisteredPrivateKeyObfuscators() {
+        synchronized (OBFUSCATORS) {
+            Collection<? extends PrivateKeyObfuscator> l = 
OBFUSCATORS.values();
+            if (GenericUtils.isEmpty(l)) {
+                return Collections.emptyList();
+            } else {
+                return new ArrayList<>(l);
+            }
+        }
+    }
+
+    /**
+     * @param algInfo The algorithm info - format: <I>{@code 
name-type-mode}</I>
+     * @return The updated context instance
+     * @see #parseAlgorithmInfo(PrivateKeyEncryptionContext, String)
+     */
+    public PrivateKeyEncryptionContext parseAlgorithmInfo(String algInfo) {
+        return parseAlgorithmInfo(this, algInfo);
+    }
+
+    @Override
+    public PrivateKeyEncryptionContext clone() {
+        try {
+            PrivateKeyEncryptionContext copy = getClass().cast(super.clone());
+            byte[] v = copy.getInitVector();
+            if (v != null) {
+                v = v.clone();
+                copy.setInitVector(v);
+            }
+            return copy;
+        } catch (CloneNotSupportedException e) { // unexpected
+            throw new RuntimeException("Failed to clone: " + toString());
+        }
+    }
+    @Override
+    public int hashCode() {
+        return GenericUtils.hashCode(getCipherName(), Boolean.TRUE)
+             + GenericUtils.hashCode(getCipherType(), Boolean.TRUE)
+             + GenericUtils.hashCode(getCipherMode(), Boolean.TRUE)
+             + Objects.hashCode(getPassword())
+             + Arrays.hashCode(getInitVector());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        PrivateKeyEncryptionContext other = (PrivateKeyEncryptionContext) obj;
+        return (GenericUtils.safeCompare(getCipherName(), 
other.getCipherName(), false) == 0)
+            && (GenericUtils.safeCompare(getCipherType(), 
other.getCipherType(), false) == 0)
+            && (GenericUtils.safeCompare(getCipherMode(), 
other.getCipherMode(), false) == 0)
+            && (GenericUtils.safeCompare(getPassword(), other.getPassword(), 
true) == 0)
+            && Arrays.equals(getInitVector(), other.getInitVector());
+    }
+
+    @Override
+    public String toString() {
+        return GenericUtils.join(new String[]{getCipherName(), 
getCipherType(), getCipherMode()}, '-');
+    }
+
+    /**
+     * @param <C> Generic context type
+     * @param context The {@link PrivateKeyEncryptionContext} to update
+     * @param algInfo The algorithm info - format: {@code 
<I>name</I>-<I>type</I>-<I>mode</I>}
+     * @return The updated context
+     */
+    public static final <C extends PrivateKeyEncryptionContext> C 
parseAlgorithmInfo(C context, String algInfo) {
+        ValidateUtils.checkNotNullAndNotEmpty(algInfo, "No encryption 
algorithm data");
+
+        String[] cipherData = GenericUtils.split(algInfo, '-');
+        ValidateUtils.checkTrue(cipherData.length == 3, "Bad encryption 
alogrithm data: %s", algInfo);
+
+        context.setCipherName(cipherData[0]);
+        context.setCipherType(cipherData[1]);
+        context.setCipherMode(cipherData[2]);
+        return context;
+    }
+
+    public static final PrivateKeyEncryptionContext 
newPrivateKeyEncryptionContext(PrivateKeyObfuscator o, String password) {
+        return initializeObfuscator(new PrivateKeyEncryptionContext(), o, 
password);
+    }
+
+    public static final <C extends PrivateKeyEncryptionContext> C 
initializeObfuscator(C context, PrivateKeyObfuscator o, String password) {
+        context.setCipherName(o.getCipherName());
+        context.setPrivateKeyObfuscator(o);
+        context.setPassword(password);
+        return context;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyObfuscator.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyObfuscator.java
new file mode 100644
index 0000000..d8d2db5
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyObfuscator.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public interface PrivateKeyObfuscator {
+    /**
+     * @return Basic cipher used to obfuscate
+     */
+    String getCipherName();
+
+    /**
+     * @return A {@link List} of the supported key sizes - <B>Note:</B> every
+     * call returns a and <U>un-modifiable</U> instance.
+     */
+    List<Integer> getSupportedKeySizes();
+
+    /**
+     * @param <A> Appendable generic type
+     * @param sb The {@link Appendable} instance to update
+     * @param encContext
+     * @return Same appendable instance
+     * @throws IOException
+     */
+    <A extends Appendable> A appendPrivateKeyEncryptionContext(A sb, 
PrivateKeyEncryptionContext encContext) throws IOException;
+
+    /**
+     * @param encContext The encryption context
+     * @return An initialization vector suitable to the specified context
+     * @throws GeneralSecurityException
+     */
+    byte[] generateInitializationVector(PrivateKeyEncryptionContext 
encContext) throws GeneralSecurityException;
+
+    /**
+     * @param bytes Original bytes
+     * @param encContext The encryption context
+     * @param encryptIt If {@code true} then encrypt the original bytes, 
otherwise decrypt them
+     * @return The result of applying the cipher to the original bytes
+     * @throws GeneralSecurityException If cannot encrypt/decrypt
+     */
+    byte[] applyPrivateKeyCipher(byte[] bytes, PrivateKeyEncryptionContext 
encContext, boolean encryptIt) throws GeneralSecurityException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
index 02cae65..6188a04 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
@@ -96,14 +96,7 @@ public class OpenSSHDSSPrivateKeyEntryDecoder extends 
AbstractPrivateKeyEntryDec
 
     @Override
     public DSAPublicKey recoverPublicKey(DSAPrivateKey privateKey) throws 
GeneralSecurityException {
-        // based on code from 
https://github.com/alexo/SAML-2.0/blob/master/java-opensaml/opensaml-security-api/src/main/java/org/opensaml/xml/security/SecurityHelper.java
-        DSAParams keyParams = privateKey.getParams();
-        BigInteger p = keyParams.getP();
-        BigInteger x = privateKey.getX();
-        BigInteger q = keyParams.getQ();
-        BigInteger g = keyParams.getG();
-        BigInteger y = g.modPow(x, p);
-        return generatePublicKey(new DSAPublicKeySpec(y, p, q, g));
+        return KeyUtils.recoverDSAPublicKey(privateKey);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
index bc41acb..07f970f 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
@@ -246,6 +246,7 @@ public class OpenSSHKeyPairResourceParser extends 
AbstractKeyPairResourceParser
 
         return stream;
     }
+
     /**
      * @param decoder The decoder to register
      * @throws IllegalArgumentException if no decoder or not key type or no

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e767438a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
index e957d0b..72e003f 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
@@ -81,7 +81,6 @@ public class OpenSSHRSAPrivateKeyDecoder extends 
AbstractPrivateKeyEntryDecoder<
         return generatePrivateKey(new RSAPrivateKeySpec(n, d));
     }
 
-
     @Override
     public boolean isPublicKeyRecoverySupported() {
         return true;
@@ -89,23 +88,7 @@ public class OpenSSHRSAPrivateKeyDecoder extends 
AbstractPrivateKeyEntryDecoder<
 
     @Override
     public RSAPublicKey recoverPublicKey(RSAPrivateKey privateKey) throws 
GeneralSecurityException {
-        if (privateKey instanceof RSAPrivateCrtKey) {
-            return recoverFromRSAPrivateCrtKey((RSAPrivateCrtKey) privateKey);
-        } else {
-            return recoverPublicKey(privateKey.getModulus(), 
privateKey.getPrivateExponent());
-        }
-    }
-
-    protected RSAPublicKey recoverPublicKey(BigInteger modulus, BigInteger 
privateExponent) throws GeneralSecurityException {
-        return generatePublicKey(new RSAPublicKeySpec(modulus, 
DEFAULT_PUBLIC_EXPONENT));
-    }
-
-    protected RSAPublicKey recoverFromRSAPrivateCrtKey(RSAPrivateCrtKey 
rsaKey) throws GeneralSecurityException {
-        BigInteger p = rsaKey.getPrimeP();
-        BigInteger q = rsaKey.getPrimeQ();
-        BigInteger n = p.multiply(q);
-        BigInteger e = rsaKey.getPublicExponent();
-        return generatePublicKey(new RSAPublicKeySpec(n, e));
+        return KeyUtils.recoverRSAPublicKey(privateKey);
     }
 
     @Override

Reply via email to