You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/09/06 16:03:54 UTC

[44/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common utilities code from sshd-core into sshd-common (new artifact)

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..397a007
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java
@@ -0,0 +1,178 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Objects;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<ECPublicKey, ECPrivateKey> {
+    public static final ECDSAPublicKeyEntryDecoder INSTANCE = new ECDSAPublicKeyEntryDecoder();
+
+    // see rfc5480 section 2.2
+    public static final byte ECPOINT_UNCOMPRESSED_FORM_INDICATOR = 0x04;
+    public static final byte ECPOINT_COMPRESSED_VARIANT_2 = 0x02;
+    public static final byte ECPOINT_COMPRESSED_VARIANT_3 = 0x02;
+
+    public ECDSAPublicKeyEntryDecoder() {
+        super(ECPublicKey.class, ECPrivateKey.class, ECCurves.KEY_TYPES);
+    }
+
+    @Override
+    public ECPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException {
+        ECCurves curve = ECCurves.fromKeyType(keyType);
+        if (curve == null) {
+            throw new InvalidKeySpecException("Not an EC curve name: " + keyType);
+        }
+
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        String keyCurveName = curve.getName();
+        // see rfc5656 section 3.1
+        String encCurveName = KeyEntryResolver.decodeString(keyData);
+        if (!keyCurveName.equals(encCurveName)) {
+            throw new InvalidKeySpecException("Mismatched key curve name (" + keyCurveName + ") vs. encoded one (" + encCurveName + ")");
+        }
+
+        byte[] octets = KeyEntryResolver.readRLEBytes(keyData);
+        ECPoint w;
+        try {
+            w = ECCurves.octetStringToEcPoint(octets);
+            if (w == null) {
+                throw new InvalidKeySpecException("No ECPoint generated for curve=" + keyCurveName
+                        + " from octets=" + BufferUtils.toHex(':', octets));
+            }
+        } catch (RuntimeException e) {
+            throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")"
+                    + " to generate ECPoint for curve=" + keyCurveName
+                    + " from octets=" + BufferUtils.toHex(':', octets)
+                    + ": " + e.getMessage());
+        }
+
+        ECParameterSpec paramSpec = curve.getParameters();
+        return generatePublicKey(new ECPublicKeySpec(w, paramSpec));
+    }
+
+    @Override
+    public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        if (key == null) {
+            return null;
+        }
+
+        ECParameterSpec params = key.getParams();
+        if (params == null) {
+            throw new InvalidKeyException("Missing parameters in key");
+        }
+
+        return generatePublicKey(new ECPublicKeySpec(key.getW(), params));
+    }
+
+    @Override
+    public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        if (key == null) {
+            return null;
+        }
+
+        ECParameterSpec params = key.getParams();
+        if (params == null) {
+            throw new InvalidKeyException("Missing parameters in key");
+        }
+
+        return generatePrivateKey(new ECPrivateKeySpec(key.getS(), params));
+    }
+
+    @Override
+    public String encodePublicKey(OutputStream s, ECPublicKey key) throws IOException {
+        Objects.requireNonNull(key, "No public key provided");
+
+        ECParameterSpec params = Objects.requireNonNull(key.getParams(), "No EC parameters available");
+        ECCurves curve = Objects.requireNonNull(ECCurves.fromCurveParameters(params), "Cannot determine curve");
+        String keyType = curve.getKeyType();
+        String curveName = curve.getName();
+        KeyEntryResolver.encodeString(s, keyType);
+        // see rfc5656 section 3.1
+        KeyEntryResolver.encodeString(s, curveName);
+        ECCurves.ECPointCompression.UNCOMPRESSED.writeECPoint(s, curveName, key.getW());
+        return keyType;
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        if (SecurityUtils.isECCSupported()) {
+            return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
+        } else {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+    }
+
+    @Override
+    public KeyPair generateKeyPair(int keySize) throws GeneralSecurityException {
+        ECCurves curve = ECCurves.fromCurveSize(keySize);
+        if (curve == null) {
+            throw new InvalidKeySpecException("Unknown curve for key size=" + keySize);
+        }
+
+        KeyPairGenerator gen = getKeyPairGenerator();
+        gen.initialize(curve.getParameters());
+        return gen.generateKeyPair();
+    }
+
+    @Override
+    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
+        if (SecurityUtils.isECCSupported()) {
+            return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
+        } else {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..4550815
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
@@ -0,0 +1,117 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
+    public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder();
+
+    public RSAPublicKeyDecoder() {
+        super(RSAPublicKey.class, RSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
+    }
+
+    @Override
+    public RSAPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException {
+        if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
+            throw new InvalidKeySpecException("Unexpected key type: " + keyType);
+        }
+
+        BigInteger e = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger n = KeyEntryResolver.decodeBigInt(keyData);
+
+        return generatePublicKey(new RSAPublicKeySpec(n, e));
+    }
+
+    @Override
+    public String encodePublicKey(OutputStream s, RSAPublicKey key) throws IOException {
+        Objects.requireNonNull(key, "No public key provided");
+        KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_RSA);
+        KeyEntryResolver.encodeBigInt(s, key.getPublicExponent());
+        KeyEntryResolver.encodeBigInt(s, key.getModulus());
+
+        return KeyPairProvider.SSH_RSA;
+    }
+
+    @Override
+    public RSAPublicKey clonePublicKey(RSAPublicKey key) throws GeneralSecurityException {
+        if (key == null) {
+            return null;
+        } else {
+            return generatePublicKey(new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()));
+        }
+    }
+
+    @Override
+    public RSAPrivateKey clonePrivateKey(RSAPrivateKey key) throws GeneralSecurityException {
+        if (key == null) {
+            return null;
+        }
+
+        if (!(key instanceof RSAPrivateCrtKey)) {
+            throw new InvalidKeyException("Cannot clone a non-RSAPrivateCrtKey: " + key.getClass().getSimpleName());
+        }
+
+        RSAPrivateCrtKey rsaPrv = (RSAPrivateCrtKey) key;
+        return generatePrivateKey(
+                new RSAPrivateCrtKeySpec(
+                        rsaPrv.getModulus(),
+                        rsaPrv.getPublicExponent(),
+                        rsaPrv.getPrivateExponent(),
+                        rsaPrv.getPrimeP(),
+                        rsaPrv.getPrimeQ(),
+                        rsaPrv.getPrimeExponentP(),
+                        rsaPrv.getPrimeExponentQ(),
+                        rsaPrv.getCrtCoefficient()));
+    }
+
+    @Override
+    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
+        return SecurityUtils.getKeyPairGenerator(KeyUtils.RSA_ALGORITHM);
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java
new file mode 100644
index 0000000..4437945
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AESPrivateKeyObfuscator.java
@@ -0,0 +1,110 @@
+/*
+ * 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:dev@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 LazyKeyLengthsHolder.KEY_LENGTHS;
+    }
+
+    private static final class LazyKeyLengthsHolder {
+        private static final List<Integer> KEY_LENGTHS = Collections.unmodifiableList(detectSupportedKeySizes());
+
+        private LazyKeyLengthsHolder() {
+            throw new UnsupportedOperationException("No instance allowed");
+        }
+
+        // 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/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..a83bf68
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java
@@ -0,0 +1,181 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+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.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean implements KeyPairResourceParser {
+    private final List<String> beginners;
+    private final List<String> enders;
+    private final List<List<String>> endingMarkers;
+
+    /**
+     * @param beginners The markers that indicate the beginning of a parsing block
+     * @param enders The <U>matching</U> (by position) markers that indicate the end of a parsing block
+     */
+    protected AbstractKeyPairResourceParser(List<String> beginners, List<String> enders) {
+        this.beginners = ValidateUtils.checkNotNullAndNotEmpty(beginners, "No begin markers");
+        this.enders = ValidateUtils.checkNotNullAndNotEmpty(enders, "No end markers");
+        ValidateUtils.checkTrue(
+                beginners.size() == enders.size(), "Mismatched begin(%d)/end(%d) markers sizes", beginners.size(), enders.size());
+        endingMarkers = new ArrayList<>(enders.size());
+        enders.forEach(m -> endingMarkers.add(Collections.singletonList(m)));
+    }
+
+    public List<String> getBeginners() {
+        return beginners;
+    }
+
+    public List<String> getEnders() {
+        return enders;
+    }
+
+    /**
+     * @return A {@link List} of same size as the ending markers, where
+     * each ending marker is encapsulated inside a singleton list and
+     * resides as the <U>same index</U> as the marker it encapsulates
+     */
+    public List<List<String>> getEndingMarkers() {
+        return endingMarkers;
+    }
+
+    @Override
+    public boolean canExtractKeyPairs(String resourceKey, List<String> lines) throws IOException, GeneralSecurityException {
+        return KeyPairResourceParser.containsMarkerLine(lines, getBeginners());
+    }
+
+    @Override
+    public Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
+            throws IOException, GeneralSecurityException {
+        Collection<KeyPair> keyPairs = Collections.emptyList();
+        List<String> beginMarkers = getBeginners();
+        List<List<String>> endMarkers = getEndingMarkers();
+        for (Map.Entry<Integer, Integer> markerPos = KeyPairResourceParser.findMarkerLine(lines, beginMarkers); markerPos != null;) {
+            int startIndex = markerPos.getKey();
+            String startLine = lines.get(startIndex);
+            startIndex++;
+
+            int markerIndex = markerPos.getValue();
+            List<String> ender = endMarkers.get(markerIndex);
+            markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, ender);
+            if (markerPos == null) {
+                throw new StreamCorruptedException("Missing end marker (" + ender + ") after line #" + startIndex);
+            }
+
+            int endIndex = markerPos.getKey();
+            String endLine = lines.get(endIndex);
+            Collection<KeyPair> kps =
+                extractKeyPairs(resourceKey, startLine, endLine, passwordProvider, lines.subList(startIndex, endIndex));
+            if (GenericUtils.isNotEmpty(kps)) {
+                if (GenericUtils.isEmpty(keyPairs)) {
+                    keyPairs = new LinkedList<>(kps);
+                } else {
+                    keyPairs.addAll(kps);
+                }
+            }
+
+            // see if there are more
+            markerPos = KeyPairResourceParser.findMarkerLine(lines, endIndex + 1, beginMarkers);
+        }
+
+        return keyPairs;
+    }
+
+    /**
+     * 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
+     *
+     * @param resourceKey A hint as to the origin of the text lines
+     * @param beginMarker The line containing the begin marker
+     * @param endMarker The line containing the end marker
+     * @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
+     * @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
+     * @see #extractKeyPairs(String, String, String, FilePasswordProvider, byte[])
+     */
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, List<String> lines)
+                    throws IOException, GeneralSecurityException {
+        return extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, KeyPairResourceParser.extractDataBytes(lines));
+    }
+
+    /**
+     * @param resourceKey A hint as to the origin of the text lines
+     * @param beginMarker The line containing the begin marker
+     * @param endMarker The line containing the end marker
+     * @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
+     * @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
+     * @see #extractKeyPairs(String, String, String, FilePasswordProvider, InputStream)
+     */
+    public Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, byte[] bytes)
+                    throws IOException, GeneralSecurityException {
+        if (log.isTraceEnabled()) {
+            BufferUtils.dumpHex(getSimplifiedLogger(), Level.FINER, beginMarker, ':', 16, bytes);
+        }
+
+        try (InputStream bais = new ByteArrayInputStream(bytes)) {
+            return extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, bais);
+        }
+    }
+
+    /**
+     * @param resourceKey A hint as to the origin of the text lines
+     * @param beginMarker The line containing the begin marker
+     * @param endMarker The line containing the end marker
+     * @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}
+     * @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
+     */
+    public abstract Collection<KeyPair> extractKeyPairs(
+            String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
+                    throws IOException, GeneralSecurityException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractPrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractPrivateKeyObfuscator.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractPrivateKeyObfuscator.java
new file mode 100644
index 0000000..ac93ab4
--- /dev/null
+++ b/sshd-common/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:dev@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/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/DESPrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/DESPrivateKeyObfuscator.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/DESPrivateKeyObfuscator.java
new file mode 100644
index 0000000..2043f06
--- /dev/null
+++ b/sshd-common/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:dev@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/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceLoader.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceLoader.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceLoader.java
new file mode 100644
index 0000000..fa6930a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceLoader.java
@@ -0,0 +1,129 @@
+/*
+ * 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * Loads {@link KeyPair}s from text resources
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface KeyPairResourceLoader {
+    /**
+     * An empty loader that never fails but always returns an empty list
+     */
+    KeyPairResourceLoader EMPTY = (resourceKey, passwordProvider, lines) -> Collections.emptyList();
+
+    default Collection<KeyPair> loadKeyPairs(Path path, FilePasswordProvider passwordProvider, OpenOption... options)
+            throws IOException, GeneralSecurityException {
+        return loadKeyPairs(path, passwordProvider, StandardCharsets.UTF_8, options);
+    }
+
+    default Collection<KeyPair> loadKeyPairs(Path path, FilePasswordProvider passwordProvider, Charset cs, OpenOption... options)
+            throws IOException, GeneralSecurityException {
+        try (InputStream stream = Files.newInputStream(path, options)) {
+            return loadKeyPairs(path.toString(), passwordProvider, stream, cs);
+        }
+    }
+
+    default Collection<KeyPair> loadKeyPairs(URL url, FilePasswordProvider passwordProvider)
+            throws IOException, GeneralSecurityException {
+        return loadKeyPairs(url, passwordProvider, StandardCharsets.UTF_8);
+    }
+
+    default Collection<KeyPair> loadKeyPairs(URL url, FilePasswordProvider passwordProvider, Charset cs)
+            throws IOException, GeneralSecurityException {
+        try (InputStream stream = Objects.requireNonNull(url, "No URL").openStream()) {
+            return loadKeyPairs(url.toExternalForm(), passwordProvider, stream, cs);
+        }
+    }
+
+    default Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, String data)
+            throws IOException, GeneralSecurityException {
+        try (Reader reader = new StringReader((data == null) ? "" : data)) {
+            return loadKeyPairs(resourceKey, passwordProvider, reader);
+        }
+    }
+
+    default Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, InputStream stream)
+            throws IOException, GeneralSecurityException {
+        return loadKeyPairs(resourceKey, passwordProvider, stream, StandardCharsets.UTF_8);
+    }
+
+    default Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, InputStream stream, Charset cs)
+            throws IOException, GeneralSecurityException {
+        try (Reader reader = new InputStreamReader(
+                Objects.requireNonNull(stream, "No stream instance"), Objects.requireNonNull(cs, "No charset"))) {
+            return loadKeyPairs(resourceKey, passwordProvider, reader);
+        }
+    }
+
+    default Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, Reader r)
+            throws IOException, GeneralSecurityException {
+        try (BufferedReader br = new BufferedReader(Objects.requireNonNull(r, "No reader instance"), IoUtils.DEFAULT_COPY_SIZE)) {
+            return loadKeyPairs(resourceKey, passwordProvider, br);
+        }
+    }
+
+    default Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, BufferedReader r)
+            throws IOException, GeneralSecurityException {
+        return loadKeyPairs(resourceKey, passwordProvider, IoUtils.readAllLines(r));
+    }
+
+    /**
+     * Loads key pairs from the given resource text lines
+     *
+     * @param resourceKey A hint as to the origin of the text lines
+     * @param passwordProvider The {@link FilePasswordProvider} to use
+     * in case the data is encrypted - may be {@code null} if no encrypted
+     * data is expected
+     * @param lines The {@link List} of lines as read from the resource
+     * @return The extracted {@link KeyPair}s - may be {@code null}/empty if none.
+     * <B>Note:</B> the resource loader may decide to skip unknown lines if
+     * more than one key pair type is encoded in it
+     * @throws IOException If failed to process the lines
+     * @throws GeneralSecurityException If failed to generate the keys from the
+     * parsed data
+     */
+    Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
+            throws IOException, GeneralSecurityException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
new file mode 100644
index 0000000..80fc2c5
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/KeyPairResourceParser.java
@@ -0,0 +1,192 @@
+/*
+ * 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.security.KeyPair;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeyPairResourceParser extends KeyPairResourceLoader {
+    /**
+     * An empty parser that never fails, but always report that it cannot
+     * extract key pairs and returns empty list if asked to load
+     */
+    KeyPairResourceParser EMPTY = new KeyPairResourceParser() {
+        @Override
+        public Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
+                throws IOException, GeneralSecurityException {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean canExtractKeyPairs(String resourceKey, List<String> lines) throws IOException, GeneralSecurityException {
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    /**
+     * @param resourceKey A hint as to the origin of the text lines
+     * @param lines The resource lines
+     * @return {@code true} if the parser can extract some key pairs from the lines
+     * @throws IOException If failed to process the lines
+     * @throws GeneralSecurityException If failed to extract information regarding
+     * the possibility to extract the key pairs
+     */
+    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
+     * @see #joinDataLines(Collection)
+     */
+    static byte[] extractDataBytes(Collection<String> lines) {
+        String data = joinDataLines(lines);
+        Base64.Decoder decoder = Base64.getDecoder();
+        return decoder.decode(data);
+    }
+
+    static String joinDataLines(Collection<String> lines) {
+        String data = GenericUtils.join(lines, ' ');
+        data = data.replaceAll("\\s", "");
+        data = data.trim();
+        return data;
+    }
+
+    static boolean containsMarkerLine(List<String> lines, String marker) {
+        return containsMarkerLine(lines, Collections.singletonList(ValidateUtils.checkNotNullAndNotEmpty(marker, "No marker")));
+    }
+
+    static boolean containsMarkerLine(List<String> lines, List<String> markers) {
+        return findMarkerLine(lines, markers) != null;
+    }
+
+    /**
+     * Attempts to locate a line that contains one of the markers
+     *
+     * @param lines The list of lines to scan - ignored if {@code null}/empty
+     * @param markers The markers to match - ignored if {@code null}/empty
+     * @return A {@link SimpleImmutableEntry} whose key is the <U>first</U> line index
+     * that matched and value the matched marker index - {@code null} if no match found
+     * @see #findMarkerLine(List, int, List)
+     */
+    static SimpleImmutableEntry<Integer, Integer> findMarkerLine(List<String> lines, List<String> markers) {
+        return findMarkerLine(lines, 0, markers);
+    }
+
+    /**
+     * Attempts to locate a line that contains one of the markers
+     *
+     * @param lines The list of lines to scan - ignored if {@code null}/empty
+     * @param startLine The scan start line index
+     * @param markers The markers to match - ignored if {@code null}/empty
+     * @return A {@link SimpleImmutableEntry} whose key is the <U>first</U> line index
+     * that matched and value the matched marker index - {@code null} if no match found
+     */
+    static SimpleImmutableEntry<Integer, Integer> findMarkerLine(List<String> lines, int startLine, List<String> markers) {
+        if (GenericUtils.isEmpty(lines) || GenericUtils.isEmpty(markers)) {
+            return null;
+        }
+
+        for (int lineIndex = startLine; lineIndex < lines.size(); lineIndex++) {
+            String l = lines.get(lineIndex);
+            for (int markerIndex = 0; markerIndex < markers.size(); markerIndex++) {
+                String m = markers.get(markerIndex);
+                if (l.contains(m)) {
+                    return new SimpleImmutableEntry<>(lineIndex, markerIndex);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    static KeyPairResourceParser aggregate(KeyPairResourceParser... parsers) {
+        return aggregate(Arrays.asList(ValidateUtils.checkNotNullAndNotEmpty(parsers, "No parsers to aggregate")));
+    }
+
+    static KeyPairResourceParser aggregate(Collection<? extends KeyPairResourceParser> parsers) {
+        ValidateUtils.checkNotNullAndNotEmpty(parsers, "No parsers to aggregate");
+        return new KeyPairResourceParser() {
+            @Override
+            public Collection<KeyPair> loadKeyPairs(String resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
+                    throws IOException, GeneralSecurityException {
+                Collection<KeyPair> keyPairs = Collections.emptyList();
+                for (KeyPairResourceParser p : parsers) {
+                    if (!p.canExtractKeyPairs(resourceKey, lines)) {
+                        continue;
+                    }
+
+                    Collection<KeyPair> kps = p.loadKeyPairs(resourceKey, passwordProvider, lines);
+                    if (GenericUtils.isEmpty(kps)) {
+                        continue;
+                    }
+
+                    if (GenericUtils.isEmpty(keyPairs)) {
+                        keyPairs = new LinkedList<>(kps);
+                    } else {
+                        keyPairs.addAll(kps);
+                    }
+                }
+
+                return keyPairs;
+            }
+
+            @Override
+            public boolean canExtractKeyPairs(String resourceKey, List<String> lines) throws IOException, GeneralSecurityException {
+                for (KeyPairResourceParser p : parsers) {
+                    if (p.canExtractKeyPairs(resourceKey, lines)) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+
+            @Override
+            public String toString() {
+                return KeyPairResourceParser.class.getSimpleName() + "[aggregate]";
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
new file mode 100644
index 0000000..5e03cdb
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
@@ -0,0 +1,270 @@
+/*
+ * 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.NavigableSet;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@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 =
+            Stream.of(AESPrivateKeyObfuscator.INSTANCE, DESPrivateKeyObfuscator.INSTANCE)
+                .collect(Collectors.toMap(AbstractPrivateKeyObfuscator::getCipherName, Function.identity()));
+
+    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 NavigableSet<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 algorithm 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/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyObfuscator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyObfuscator.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyObfuscator.java
new file mode 100644
index 0000000..d8d2db5
--- /dev/null
+++ b/sshd-common/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:dev@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/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
new file mode 100644
index 0000000..6188a04
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
@@ -0,0 +1,139 @@
+/*
+ * 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.openssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHDSSPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<DSAPublicKey, DSAPrivateKey> {
+    public static final OpenSSHDSSPrivateKeyEntryDecoder INSTANCE = new OpenSSHDSSPrivateKeyEntryDecoder();
+
+    public OpenSSHDSSPrivateKeyEntryDecoder() {
+        super(DSAPublicKey.class, DSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS)));
+    }
+
+    @Override
+    public DSAPrivateKey decodePrivateKey(String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
+            throws IOException, GeneralSecurityException {
+        if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we were invoked directly
+            throw new InvalidKeySpecException("Unexpected key type: " + keyType);
+        }
+
+        BigInteger p = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger q = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger g = KeyEntryResolver.decodeBigInt(keyData);
+        BigInteger y = KeyEntryResolver.decodeBigInt(keyData);
+        Objects.requireNonNull(y, "No public key data");   // TODO run some validation on it
+        BigInteger x = KeyEntryResolver.decodeBigInt(keyData);
+
+        return generatePrivateKey(new DSAPrivateKeySpec(x, p, q, g));
+    }
+
+    @Override
+    public String encodePrivateKey(OutputStream s, DSAPrivateKey key) throws IOException {
+        Objects.requireNonNull(key, "No private key provided");
+
+        DSAParams keyParams = Objects.requireNonNull(key.getParams(), "No DSA params available");
+        BigInteger p = keyParams.getP();
+        KeyEntryResolver.encodeBigInt(s, p);
+        KeyEntryResolver.encodeBigInt(s, keyParams.getQ());
+
+        BigInteger g = keyParams.getG();
+        KeyEntryResolver.encodeBigInt(s, g);
+
+        BigInteger x = key.getX();
+        BigInteger y = g.modPow(x, p);
+        KeyEntryResolver.encodeBigInt(s, y);
+        KeyEntryResolver.encodeBigInt(s, x);
+        return KeyPairProvider.SSH_DSS;
+    }
+
+    @Override
+    public boolean isPublicKeyRecoverySupported() {
+        return true;
+    }
+
+    @Override
+    public DSAPublicKey recoverPublicKey(DSAPrivateKey privateKey) throws GeneralSecurityException {
+        return KeyUtils.recoverDSAPublicKey(privateKey);
+    }
+
+    @Override
+    public DSAPublicKey clonePublicKey(DSAPublicKey key) throws GeneralSecurityException {
+        if (key == null) {
+            return null;
+        }
+
+        DSAParams params = key.getParams();
+        if (params == null) {
+            throw new InvalidKeyException("Missing parameters in key");
+        }
+
+        return generatePublicKey(new DSAPublicKeySpec(key.getY(), params.getP(), params.getQ(), params.getG()));
+    }
+
+    @Override
+    public DSAPrivateKey clonePrivateKey(DSAPrivateKey key) throws GeneralSecurityException {
+        if (key == null) {
+            return null;
+        }
+
+        DSAParams params = key.getParams();
+        if (params == null) {
+            throw new InvalidKeyException("Missing parameters in key");
+        }
+
+        return generatePrivateKey(new DSAPrivateKeySpec(key.getX(), params.getP(), params.getQ(), params.getG()));
+    }
+
+    @Override
+    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
+        return SecurityUtils.getKeyPairGenerator(KeyUtils.DSS_ALGORITHM);
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
new file mode 100644
index 0000000..bceca59
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
@@ -0,0 +1,164 @@
+/*
+ * 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.openssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Objects;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<ECPublicKey, ECPrivateKey> {
+    public static final OpenSSHECDSAPrivateKeyEntryDecoder INSTANCE = new OpenSSHECDSAPrivateKeyEntryDecoder();
+
+    public OpenSSHECDSAPrivateKeyEntryDecoder() {
+        super(ECPublicKey.class, ECPrivateKey.class, ECCurves.KEY_TYPES);
+    }
+
+    @Override
+    public ECPrivateKey decodePrivateKey(String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
+            throws IOException, GeneralSecurityException {
+        ECCurves curve = ECCurves.fromKeyType(keyType);
+        if (curve == null) {
+            throw new InvalidKeySpecException("Not an EC curve name: " + keyType);
+        }
+
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        String keyCurveName = curve.getName();
+        // see rfc5656 section 3.1
+        String encCurveName = KeyEntryResolver.decodeString(keyData);
+        if (!keyCurveName.equals(encCurveName)) {
+            throw new InvalidKeySpecException("Mismatched key curve name (" + keyCurveName + ") vs. encoded one (" + encCurveName + ")");
+        }
+
+        byte[] pubKey = KeyEntryResolver.readRLEBytes(keyData);
+        Objects.requireNonNull(pubKey, "No public point");  // TODO validate it is a valid ECPoint
+        BigInteger s = KeyEntryResolver.decodeBigInt(keyData);
+        ECParameterSpec params = curve.getParameters();
+        return generatePrivateKey(new ECPrivateKeySpec(s, params));
+    }
+
+    @Override
+    public String encodePrivateKey(OutputStream s, ECPrivateKey key) throws IOException {
+        Objects.requireNonNull(key, "No private key provided");
+        return null;
+    }
+
+    @Override
+    public ECPublicKey recoverPublicKey(ECPrivateKey prvKey) throws GeneralSecurityException {
+        ECCurves curve = ECCurves.fromECKey(prvKey);
+        if (curve == null) {
+            throw new InvalidKeyException("Unknown curve");
+        }
+        // TODO see how we can figure out the public value
+        return super.recoverPublicKey(prvKey);
+    }
+
+    @Override
+    public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        if (key == null) {
+            return null;
+        }
+
+        ECParameterSpec params = key.getParams();
+        if (params == null) {
+            throw new InvalidKeyException("Missing parameters in key");
+        }
+
+        return generatePublicKey(new ECPublicKeySpec(key.getW(), params));
+    }
+
+    @Override
+    public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        if (key == null) {
+            return null;
+        }
+
+        ECParameterSpec params = key.getParams();
+        if (params == null) {
+            throw new InvalidKeyException("Missing parameters in key");
+        }
+
+        return generatePrivateKey(new ECPrivateKeySpec(key.getS(), params));
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        if (SecurityUtils.isECCSupported()) {
+            return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
+        } else {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+    }
+
+    @Override
+    public KeyPair generateKeyPair(int keySize) throws GeneralSecurityException {
+        ECCurves curve = ECCurves.fromCurveSize(keySize);
+        if (curve == null) {
+            throw new InvalidKeySpecException("Unknown curve for key size=" + keySize);
+        }
+
+        KeyPairGenerator gen = getKeyPairGenerator();
+        gen.initialize(curve.getParameters());
+        return gen.generateKeyPair();
+    }
+
+    @Override
+    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
+        if (SecurityUtils.isECCSupported()) {
+            return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
+        } else {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+    }
+}