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 2016/12/09 17:17:00 UTC

mina-sshd git commit: [SSHD-718] Add support for reading Putty key files

Repository: mina-sshd
Updated Branches:
  refs/heads/master 41857c7e8 -> 168adf20a


[SSHD-718] Add support for reading Putty key files


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

Branch: refs/heads/master
Commit: 168adf20ad8ea277130d0fc7f673f7b2ddd61dfb
Parents: 41857c7
Author: Lyor Goldstein <ly...@gmail.com>
Authored: Fri Dec 9 19:17:05 2016 +0200
Committer: Lyor Goldstein <ly...@gmail.com>
Committed: Fri Dec 9 19:17:05 2016 +0200

----------------------------------------------------------------------
 .../loader/putty/AbstractPuttyKeyDecoder.java   | 220 +++++++++++++++++++
 .../keys/loader/putty/DSSPuttyKeyDecoder.java   |  63 ++++++
 .../putty/PuttyKeyPairResourceParser.java       | 199 +++++++++++++++++
 .../keys/loader/putty/PuttyKeyReader.java       |  58 +++++
 .../config/keys/loader/putty/PuttyKeyUtils.java |  58 +++++
 .../keys/loader/putty/RSAPuttyKeyDecoder.java   |  71 ++++++
 .../keys/loader/putty/PuttyKeyUtilsTest.java    | 126 +++++++++++
 .../putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk |  17 ++
 .../putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk |  18 ++
 ...t-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk |  17 ++
 ...t-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk |  18 ++
 .../keys/loader/KeyPairResourceParser.java      |  14 +-
 .../loader/pem/DSSPEMResourceKeyPairParser.java |  25 +--
 .../loader/pem/RSAPEMResourceKeyPairParser.java |  27 ++-
 14 files changed, 903 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java
new file mode 100644
index 0000000..5b29618
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java
@@ -0,0 +1,220 @@
+/*
+ * 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.putty;
+
+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.Base64;
+import java.util.Base64.Decoder;
+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.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPuttyKeyDecoder
+                extends AbstractLoggingBean
+                implements PuttyKeyPairResourceParser {
+    public static final String ENCRYPTION_HEADER = "Encryption";
+
+    private final String keyType;
+    protected AbstractPuttyKeyDecoder(String keyType) {
+        this.keyType = keyType;
+    }
+
+    @Override
+    public String getKeyType() {
+        return keyType;
+    }
+
+    @Override
+    public boolean canExtractKeyPairs(String resourceKey, List<String> lines)
+            throws IOException, GeneralSecurityException {
+        if (!PuttyKeyPairResourceParser.super.canExtractKeyPairs(resourceKey, lines)) {
+            return false;
+        }
+
+        for (String l : lines) {
+            l = GenericUtils.trimToEmpty(l);
+            if (!l.startsWith(KEY_FILE_HEADER_PREFIX)) {
+                continue;
+            }
+
+            int pos = l.indexOf(':');
+            if ((pos <= 0) || (pos >= (l.length() - 1))) {
+                return false;
+            }
+
+            String typeValue = l.substring(pos + 1).trim();
+            return Objects.equals(getKeyType(), typeValue);
+        }
+
+        return false;
+    }
+
+    @Override
+    public Collection<KeyPair> loadKeyPairs(
+            String resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
+                    throws IOException, GeneralSecurityException {
+        List<String> pubLines = Collections.emptyList();
+        List<String> prvLines = Collections.emptyList();
+        String prvEncryption = null;
+        for (int index = 0, numLines = lines.size(); index < numLines; index++) {
+            String l = lines.get(index);
+            l = GenericUtils.trimToEmpty(l);
+            int pos = l.indexOf(':');
+            if ((pos <= 0) || (pos >= (l.length() - 1))) {
+                continue;
+            }
+
+            String hdrName = l.substring(0, pos).trim();
+            String hdrValue = l.substring(pos + 1).trim();
+            switch (hdrName) {
+                case ENCRYPTION_HEADER:
+                    if (prvEncryption != null) {
+                        throw new StreamCorruptedException("Duplicate " + hdrName + " in" + resourceKey);
+                    }
+                    prvEncryption = hdrValue;
+                    break;
+                case PUBLIC_LINES_HEADER:
+                    pubLines = extractDataLines(resourceKey, lines, index + 1, hdrName, hdrValue, pubLines);
+                    index += pubLines.size();
+                    break;
+                case PRIVATE_LINES_HEADER:
+                    prvLines = extractDataLines(resourceKey, lines, index + 1, hdrName, hdrValue, prvLines);
+                    index += prvLines.size();
+                    break;
+                default:    // ignored
+            }
+        }
+
+        return loadKeyPairs(resourceKey, pubLines, prvLines, prvEncryption, passwordProvider);
+    }
+
+    public static List<String> extractDataLines(
+            String resourceKey, List<String> lines, int startIndex, String hdrName, String hdrValue, List<String> curLines)
+                throws IOException {
+        if (GenericUtils.size(curLines) > 0) {
+            throw new StreamCorruptedException("Duplicate " + hdrName + " in " + resourceKey);
+        }
+
+        int numLines;
+        try {
+            numLines = Integer.parseInt(hdrValue);
+        } catch (NumberFormatException e) {
+            throw new StreamCorruptedException("Bad " + hdrName + " value (" + hdrValue + ") in " + resourceKey);
+        }
+
+        int endIndex = startIndex + numLines;
+        int totalLines = lines.size();
+        if (endIndex > totalLines) {
+            throw new StreamCorruptedException("Excessive " + hdrName + " value (" + hdrValue + ") in " + resourceKey);
+        }
+
+        return lines.subList(startIndex, endIndex);
+    }
+
+    public Collection<KeyPair> loadKeyPairs(
+            String resourceKey, List<String> pubLines, List<String> prvLines, String prvEncryption, FilePasswordProvider passwordProvider)
+                throws IOException, GeneralSecurityException {
+        return loadKeyPairs(resourceKey,
+                KeyPairResourceParser.joinDataLines(pubLines), KeyPairResourceParser.joinDataLines(prvLines),
+                prvEncryption, passwordProvider);
+    }
+
+    public Collection<KeyPair> loadKeyPairs(
+            String resourceKey, String pubData, String prvData, String prvEncryption, FilePasswordProvider passwordProvider)
+                throws IOException, GeneralSecurityException {
+        Decoder b64Decoder = Base64.getDecoder();
+        byte[] pubBytes = b64Decoder.decode(pubData);
+        byte[] prvBytes = b64Decoder.decode(prvData);
+        String password = null;
+        if ((GenericUtils.length(prvEncryption) > 0)
+                && (!NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption))) {
+            password = passwordProvider.getPassword(resourceKey);
+        }
+
+        if (GenericUtils.isEmpty(prvEncryption)
+                || NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption)
+                || GenericUtils.isEmpty(password)) {
+            return loadKeyPairs(resourceKey, pubBytes, prvBytes);
+        }
+
+        // format is "<cipher><bits>-<mode>" - e.g., "aes256-cbc"
+        int pos = prvEncryption.indexOf('-');
+        if (pos <= 0) {
+            throw new StreamCorruptedException("Missing private key encryption mode in " + prvEncryption);
+        }
+
+        String mode = prvEncryption.substring(pos + 1).toUpperCase();
+        String algName = null;
+        int numBits = 0;
+        for (int index = 0; index < pos; index++) {
+            char ch = prvEncryption.charAt(index);
+            if ((ch >= '0') && (ch <= '9')) {
+                algName = prvEncryption.substring(0, index).toUpperCase();
+                numBits = Integer.parseInt(prvEncryption.substring(index, pos));
+                break;
+            }
+        }
+
+        if (GenericUtils.isEmpty(algName) || (numBits <= 0)) {
+            throw new StreamCorruptedException("Missing private key encryption algorithm details in " + prvEncryption);
+        }
+
+        prvBytes = PuttyKeyPairResourceParser.decodePrivateKeyBytes(prvBytes, algName, numBits, mode, password);
+        return loadKeyPairs(resourceKey, pubBytes, prvBytes);
+    }
+
+    public Collection<KeyPair> loadKeyPairs(String resourceKey, byte[] pubData, byte[] prvData)
+            throws IOException, GeneralSecurityException {
+        ValidateUtils.checkNotNullAndNotEmpty(pubData, "No public key data in %s", resourceKey);
+        ValidateUtils.checkNotNullAndNotEmpty(prvData, "No private key data in %s", resourceKey);
+        try (InputStream pubStream = new ByteArrayInputStream(pubData);
+             InputStream prvStream = new ByteArrayInputStream(prvData)) {
+            return loadKeyPairs(resourceKey, pubStream, prvStream);
+        }
+    }
+
+    public Collection<KeyPair> loadKeyPairs(String resourceKey, InputStream pubData, InputStream prvData)
+            throws IOException, GeneralSecurityException {
+        try (PuttyKeyReader pubReader =
+                new PuttyKeyReader(ValidateUtils.checkNotNull(pubData, "No public key data in %s", resourceKey));
+             PuttyKeyReader prvReader =
+                new PuttyKeyReader(ValidateUtils.checkNotNull(prvData, "No private key data in %s", resourceKey))) {
+            return loadKeyPairs(resourceKey, pubReader, prvReader);
+        }
+    }
+
+    public abstract Collection<KeyPair> loadKeyPairs(String resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader)
+            throws IOException, GeneralSecurityException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java
new file mode 100644
index 0000000..c03a9d5
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java
@@ -0,0 +1,63 @@
+/*
+ * 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.putty;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+
+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 DSSPuttyKeyDecoder extends AbstractPuttyKeyDecoder {
+    public static final DSSPuttyKeyDecoder INSTANCE = new DSSPuttyKeyDecoder();
+
+    public DSSPuttyKeyDecoder() {
+        super(KeyPairProvider.SSH_DSS);
+    }
+
+    @Override
+    public Collection<KeyPair> loadKeyPairs(String resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader)
+            throws IOException, GeneralSecurityException {
+        pubReader.skip();   // skip version
+
+        BigInteger p = pubReader.readInt();
+        BigInteger q = pubReader.readInt();
+        BigInteger g = pubReader.readInt();
+        BigInteger y = pubReader.readInt();
+        BigInteger x = prvReader.readInt();
+        KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM);
+        PublicKey pubKey = kf.generatePublic(new DSAPublicKeySpec(y, p, q, g));
+        PrivateKey prvKey = kf.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
+        return Collections.singletonList(new KeyPair(pubKey, prvKey));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java
new file mode 100644
index 0000000..774624b
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java
@@ -0,0 +1,199 @@
+/*
+ * 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.putty;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+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.security.SecurityUtils;
+
+//CHECKSTYLE:OFF
+/**
+ * Loads a {@link KeyPair} from PuTTY's &quot;.ppk&quot; file.
+ * <P>Note(s):</P>
+ * <UL>
+ *      <P><LI>
+ *      The file appears to be a text file but it doesn't have a fixed encoding like UTF-8.
+ *      We use UTF-8 as the default encoding - since the important part is all ASCII,
+ *      this shouldn't really hurt the interpretation of the key.
+ *      </LI></P>
+ *
+ *      <P><LI>
+ *      Based on code from <A HREF="https://github.com/kohsuke/trilead-putty-extension">Kohsuke's Trilead Putty Extension</A>
+ *      </LI></P>
+ *
+ *      <P><LI>
+ *      Encrypted keys requires AES-256-CBC support, which is available only if the
+ *      <A HREF="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html">
+ *      Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files</A> are installed
+ *      </LI></P>
+ * </UL>
+ *
+ * <P>Sample PuTTY file format</P>
+ * <PRE>
+ * PuTTY-User-Key-File-2: ssh-rsa
+ * Encryption: none
+ * Comment: rsa-key-20080514
+ * Public-Lines: 4
+ * AAAAB3NzaC1yc2EAAAABJQAAAIEAiPVUpONjGeVrwgRPOqy3Ym6kF/f8bltnmjA2
+ * BMdAtaOpiD8A2ooqtLS5zWYuc0xkW0ogoKvORN+RF4JI+uNUlkxWxnzJM9JLpnvA
+ * HrMoVFaQ0cgDMIHtE1Ob1cGAhlNInPCRnGNJpBNcJ/OJye3yt7WqHP4SPCCLb6nL
+ * nmBUrLM=
+ * Private-Lines: 8
+ * AAAAgGtYgJzpktzyFjBIkSAmgeVdozVhgKmF6WsDMUID9HKwtU8cn83h6h7ug8qA
+ * hUWcvVxO201/vViTjWVz9ALph3uMnpJiuQaaNYIGztGJBRsBwmQW9738pUXcsUXZ
+ * 79KJP01oHn6Wkrgk26DIOsz04QOBI6C8RumBO4+F1WdfueM9AAAAQQDmA4hcK8Bx
+ * nVtEpcF310mKD3nsbJqARdw5NV9kCxPnEsmy7Sy1L4Ob/nTIrynbc3MA9HQVJkUz
+ * 7V0va5Pjm/T7AAAAQQCYbnG0UEekwk0LG1Hkxh1OrKMxCw2KWMN8ac3L0LVBg/Tk
+ * 8EnB2oT45GGeJaw7KzdoOMFZz0iXLsVLNUjNn2mpAAAAQQCN6SEfWqiNzyc/w5n/
+ * lFVDHExfVUJp0wXv+kzZzylnw4fs00lC3k4PZDSsb+jYCMesnfJjhDgkUA0XPyo8
+ * Emdk
+ * Private-MAC: 50c45751d18d74c00fca395deb7b7695e3ed6f77
+ * </PRE>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+//CHECKSTYLE:ON
+public interface PuttyKeyPairResourceParser extends KeyPairResourceParser {
+    String KEY_FILE_HEADER_PREFIX = "PuTTY-User-Key-File";
+    String PUBLIC_LINES_HEADER = "Public-Lines";
+    String PRIVATE_LINES_HEADER = "Private-Lines";
+    String PPK_FILE_SUFFIX = ".ppk";
+
+    List<String> KNOWN_HEADERS =
+            Collections.unmodifiableList(
+                    Arrays.asList(
+                            KEY_FILE_HEADER_PREFIX,
+                            PUBLIC_LINES_HEADER,
+                            PRIVATE_LINES_HEADER));
+
+    /**
+     * Value (case insensitive) used to denote that private key is not encrypted
+     */
+    String NO_PRIVATE_KEY_ENCRYPTION_VALUE = "none";
+
+    /**
+     * @return Type of key being parsed by the resource parser
+     */
+    String getKeyType();
+
+    @Override
+    default boolean canExtractKeyPairs(String resourceKey, List<String> lines)
+            throws IOException, GeneralSecurityException {
+        if (GenericUtils.isEmpty(lines)) {
+            return false;
+        }
+
+        for (String l : lines) {
+            l = GenericUtils.trimToEmpty(l);
+            for (String hdr : KNOWN_HEADERS) {
+                if (l.startsWith(hdr)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    static byte[] decodePrivateKeyBytes(byte[] prvBytes, String algName, int numBits, String algMode, String password)
+            throws GeneralSecurityException {
+        Objects.requireNonNull(prvBytes, "No encrypted key bytes");
+        ValidateUtils.checkNotNullAndNotEmpty(algName, "No encryption algorithm", GenericUtils.EMPTY_OBJECT_ARRAY);
+        ValidateUtils.checkTrue(numBits > 0, "Invalid encryption key size: %d", numBits);
+        ValidateUtils.checkNotNullAndNotEmpty(algMode, "No encryption mode", GenericUtils.EMPTY_OBJECT_ARRAY);
+        ValidateUtils.checkNotNullAndNotEmpty(password, "No encryption password", GenericUtils.EMPTY_OBJECT_ARRAY);
+
+        if (!"AES".equalsIgnoreCase(algName)) {
+            throw new NoSuchAlgorithmException("decodePrivateKeyBytes(" + algName + "-" + numBits + "-" + algMode + ") N/A");
+        }
+
+        return decodePrivateKeyBytes(prvBytes, algName, algMode, numBits, new byte[16], toEncryptionKey(password));
+    }
+
+    static byte[] decodePrivateKeyBytes(
+            byte[] encBytes, String cipherName, String cipherMode, int numBits, byte[] initVector, byte[] keyValue)
+                    throws GeneralSecurityException {
+        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("decodePrivateKeyBytes(" + xform + ")"
+                    + " required key length (" + numBits + ") exceeds max. available: " + maxAllowedBits);
+        }
+
+        SecretKeySpec skeySpec = new SecretKeySpec(keyValue, cipherName);
+        IvParameterSpec ivspec = new IvParameterSpec(initVector);
+        Cipher cipher = SecurityUtils.getCipher(xform);
+        cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec);
+
+        return cipher.doFinal(encBytes);
+    }
+
+    /**
+     * Converts a pass-phrase into a key, by following the convention that PuTTY uses.
+     * Used to decrypt the private key when it's encrypted.
+     * @param passphrase the Password to be used as seed for the key - ignored
+     * if {@code null}/empty
+     * @return The encryption key bytes - {@code null} if no pass-phrase
+     * @throws GeneralSecurityException If cannot retrieve SHA-1 digest
+     * @see <A HREF="http://security.stackexchange.com/questions/71341/how-does-putty-derive-the-encryption-key-in-its-ppk-format">
+     * How does Putty derive the encryption key in its .ppk format ?</A>
+     */
+    static byte[] toEncryptionKey(String passphrase) throws GeneralSecurityException {
+        if (GenericUtils.isEmpty(passphrase)) {
+            return null;
+        }
+
+        MessageDigest hash = SecurityUtils.getMessageDigest(BuiltinDigests.sha1.getAlgorithm());
+        byte[] stateValue = {0, 0, 0, 0};
+        byte[] passBytes = passphrase.getBytes(StandardCharsets.UTF_8);
+        byte[] keyValue = new byte[32];
+        for (int i = 0, remLen = keyValue.length; i < 2; i++) {
+            hash.reset(); // just making sure
+
+            stateValue[3] = (byte) i;
+            hash.update(stateValue);
+            hash.update(passBytes);
+
+            byte[] digest = hash.digest();
+            System.arraycopy(digest, 0, keyValue, i * 20, Math.min(20, remLen));
+            remLen -= 20;
+        }
+
+        return keyValue;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java
new file mode 100644
index 0000000..ae00ef2
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java
@@ -0,0 +1,58 @@
+/*
+ * 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.putty;
+
+import java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+/**
+ * Helper class for {@code Putty} key files decoders
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PuttyKeyReader implements Closeable {
+    private final DataInputStream di;
+
+    public PuttyKeyReader(InputStream s) {
+        di = new DataInputStream(s);
+    }
+
+    public void skip() throws IOException {
+        di.skipBytes(di.readInt());
+    }
+
+    private byte[] read() throws IOException {
+        int len = di.readInt();
+        byte[] r = new byte[len];
+        di.readFully(r);
+        return r;
+    }
+
+    public BigInteger readInt() throws IOException {
+        return new BigInteger(read());
+    }
+
+    @Override
+    public void close() throws IOException {
+        di.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java
new file mode 100644
index 0000000..61ad233
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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.putty;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class PuttyKeyUtils {
+    public static final List<PuttyKeyPairResourceParser> DEFAULT_PARSERS =
+            Collections.unmodifiableList(
+                    Arrays.asList(
+                            RSAPuttyKeyDecoder.INSTANCE,
+                            DSSPuttyKeyDecoder.INSTANCE));
+    public static final SortedMap<String, PuttyKeyPairResourceParser> BY_KEY_TYPE =
+            Collections.unmodifiableSortedMap(
+                    new TreeMap<String, PuttyKeyPairResourceParser>(String.CASE_INSENSITIVE_ORDER) {
+                        // Not serializing it
+                        private static final long serialVersionUID = 1L;
+
+                        {
+                            for (PuttyKeyPairResourceParser p : DEFAULT_PARSERS) {
+                                put(p.getKeyType(), p);
+                            }
+                        }
+            });
+
+    public static final KeyPairResourceParser DEFAULT_INSTANCE =
+            KeyPairResourceParser.aggregate(DEFAULT_PARSERS);
+
+    private PuttyKeyUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java
new file mode 100644
index 0000000..b4f9687
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys.loader.putty;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Collections;
+
+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 RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder {
+    public static final RSAPuttyKeyDecoder INSTANCE = new RSAPuttyKeyDecoder();
+
+    public RSAPuttyKeyDecoder() {
+        super(KeyPairProvider.SSH_RSA);
+    }
+
+    @Override
+    public Collection<KeyPair> loadKeyPairs(String resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader)
+            throws IOException, GeneralSecurityException {
+        pubReader.skip();   // skip version
+
+        KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM);
+        BigInteger publicExp = pubReader.readInt();
+        BigInteger modulus = pubReader.readInt();
+        PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, publicExp));
+
+        BigInteger privateExp = prvReader.readInt();
+        BigInteger primeP = prvReader.readInt();
+        BigInteger primeQ = prvReader.readInt();
+        BigInteger crtCoef = prvReader.readInt();
+        BigInteger primeExponentP = privateExp.mod(primeP.subtract(BigInteger.ONE));
+        BigInteger primeExponentQ = privateExp.mod(primeQ.subtract(BigInteger.ONE));
+        RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec(
+                modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef);
+        PrivateKey prvKey = kf.generatePrivate(prvSpec);
+        return Collections.singletonList(new KeyPair(pubKey, prvKey));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java b/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java
new file mode 100644
index 0000000..aea094f
--- /dev/null
+++ b/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.putty;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.sshd.common.cipher.BuiltinCiphers;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.Assume;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+public class PuttyKeyUtilsTest extends BaseTestSupport {
+    public static final String PASSWORD = "super secret passphrase";
+
+    private final String keyType;
+    private final String regularFile;
+    private final String encryptedFile;
+    private final PuttyKeyPairResourceParser parser;
+
+    public PuttyKeyUtilsTest(String keyType) {
+        this.keyType = keyType;
+        this.parser = PuttyKeyUtils.BY_KEY_TYPE.get(keyType);
+        this.regularFile = getClass().getSimpleName()
+                + "-" + keyType + "-" + KeyPair.class.getSimpleName()
+                + PuttyKeyPairResourceParser.PPK_FILE_SUFFIX;
+        this.encryptedFile = PASSWORD.replace(' ', '-') + "-AES-256-CBC"
+                + "-" + keyType + "-" + KeyPair.class.getSimpleName()
+                + PuttyKeyPairResourceParser.PPK_FILE_SUFFIX;
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return parameterize(PuttyKeyUtils.BY_KEY_TYPE.keySet());
+    }
+
+    @Test
+    public void testCanDecodePuttyKeyFile() throws IOException, GeneralSecurityException {
+        for (String resource : new String[]{regularFile, encryptedFile}) {
+            URL url = getClass().getResource(resource);
+            assertNotNull("Missing test resource: " + resource, url);
+
+            List<String> lines = IoUtils.readAllLines(url);
+            assertTrue(resource + " - can extract key pair", parser.canExtractKeyPairs(resource, lines));
+
+            for (PuttyKeyPairResourceParser other : PuttyKeyUtils.BY_KEY_TYPE.values()) {
+                if (parser == other) {
+                    continue;
+                }
+
+                assertFalse(other.getKeyType() + "/" + resource + " - unexpected extraction capability",
+                        other.canExtractKeyPairs(resource, lines));
+            }
+        }
+    }
+
+    @Test
+    public void testDecodePuttyKeyFile() throws IOException, GeneralSecurityException {
+        URL url = getClass().getResource(regularFile);
+        assertNotNull("Missing test resource: " + regularFile, url);
+
+        Collection<KeyPair> keys = parser.loadKeyPairs(url, null);
+        assertEquals("Mismatched loaded keys count from " + regularFile, 1, GenericUtils.size(keys));
+        assertLoadedKeyPair(regularFile, keys.iterator().next());
+    }
+
+    @Test
+    public void testDecodeEncryptedPuttyKeyFile() throws IOException, GeneralSecurityException {
+        Assume.assumeTrue(BuiltinCiphers.aes256cbc.getTransformation() + " N/A", BuiltinCiphers.aes256cbc.isSupported());
+        URL url = getClass().getResource(encryptedFile);
+        assertNotNull("Missing test resource: " + encryptedFile, url);
+
+        Collection<KeyPair> keys = parser.loadKeyPairs(url, r -> PASSWORD);
+        assertEquals("Mismatched loaded keys count from " + encryptedFile, 1, GenericUtils.size(keys));
+
+        assertLoadedKeyPair(encryptedFile, keys.iterator().next());
+    }
+
+    private void assertLoadedKeyPair(String prefix, KeyPair kp) {
+        assertNotNull(prefix + ": no key pair loaded", kp);
+
+        PublicKey pubKey = kp.getPublic();
+        assertNotNull(prefix + ": no public key loaded", pubKey);
+        assertEquals(prefix + ": mismatched public key type", keyType, KeyUtils.getKeyType(pubKey));
+
+        PrivateKey prvKey = kp.getPrivate();
+        assertNotNull(prefix + ": no private key loaded", prvKey);
+        assertEquals(prefix + ": mismatched private key type", keyType, KeyUtils.getKeyType(prvKey));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk
new file mode 100644
index 0000000..59a8fac
--- /dev/null
+++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-dss-KeyPair.ppk
@@ -0,0 +1,17 @@
+PuTTY-User-Key-File-2: ssh-dss
+Encryption: none
+Comment: dsa-key-20130709
+Public-Lines: 10
+AAAAB3NzaC1kc3MAAACBAMg/IxsG5BxnF5gM7IKqqR0rftxZC+n5GlbO+J4H+iIb
+/KR8NBehkxG3CrBZMF96M2K1sEGYLob+3k4r71oWaPul8n5rt9kpd+JSq4iD2ygO
+yg6Kd1/YDBHoxneizy6I/bGsLwhAAKWcRNrXmYVKGzhrhvZWN12AJDq2mGdj3szL
+AAAAFQD7a2MltdUSF7FU3//SpW4WGjZbeQAAAIBf0nNsfKQL/TEMo7IpTrEMg5V0
+RnSigCX0+yUERS42GW/ZeCZBJw7oL2XZbuBtu63vMjDgVpnb92BdrcPgjJ7EFW6D
+lcyeuywStmg1ygXmDR2AQCxv0eX2CQgrdUczmRa155SDVUTvTQlO1IyKx0vwKAh1
+H7E3yJUfkTAJstbGYQAAAIEAtv+cdRfNevYFkp55jVqazc8zRLvfb64jzgc5oSJV
+c64kFs4yx+abYpGX9WxNxDlG6g2WiY8voDBB0YnUJsn0kVRjBKX9OceROxrfT4K4
+dVbQZsdt+SLaXWL4lGJFrFZL3LZqvySvq6xfhJfakQDDivW4hUOhFPXPHrE5/Ia3
+T7A=
+Private-Lines: 1
+AAAAFQCE6flGnmVCAbzo9YsbdJWBnxMnBA==
+Private-MAC: 6ace0a5e5bf23649c1375e91dcd71d1def6c6aa1

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk
new file mode 100644
index 0000000..da2868e
--- /dev/null
+++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ssh-rsa-KeyPair.ppk
@@ -0,0 +1,18 @@
+PuTTY-User-Key-File-2: ssh-rsa
+Encryption: none
+Comment: rsa-key-20130709
+Public-Lines: 4
+AAAAB3NzaC1yc2EAAAABJQAAAIBLSj7+SllQdWjkD8EN4sA/fUv/jhc+ssGmCYx3
+uoiPMxjKH3xUPWu4zxJzhdlgFy4gchzjU51fDS4Oel6xGoWbGKGe4ZLQdE/t8N8l
+jAfOm/5lGp5tFhHs9UHoSm/h3RsErWNjKPjTGIlID35IcOXVhfhp9fX0RU6y/ZBI
+PhM20w==
+Private-Lines: 8
+AAAAgBx89T2fl2qNSkh0qemUEWQhkmCyTfwMSUW+bIBUawXATpGrDHLmztBOWgIy
+pUb0A52S9iyAgLwugCEnYhl/qCxvoARH7ZyTdYAL4KjJDySxVuqeo/ZhLscYcMAz
+MOyn8g5cR4dRgEwJ1/pRuK8r4+Z96zJG4NlxlHsUjHuj7t1dAAAAQQCTrj48XKIX
+M3dxWLSsSXbUCOpmAOTviuz9LD0H1ik7a6ebr0P6GTl9z7iscBgzdjBIHMFcdvar
+ophUJ5iRanCvAAAAQQCCg1VU1H5FHMipRvvw8b/zRqDsx6GTZs03ffhyKrTl1Dcd
+0oKy5/U3kQwdXPOSlVZeyX8nUVE2o7DOh7INsX0dAAAAQHkPjxivrN0SQuVAx+tK
+uRJ8vGL7sBOZ9gdTS25T3BVEkhRt37aDcshrodzDCzd515cwhmbLSsOsgyxcTwcX
+7SA=
+Private-MAC: 2416438f1a7ebdd33d519f6102d843b5f2c565d4

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk
new file mode 100644
index 0000000..9da31f1
--- /dev/null
+++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-dss-KeyPair.ppk
@@ -0,0 +1,17 @@
+PuTTY-User-Key-File-2: ssh-dss
+Encryption: aes256-cbc
+Comment: dsa-key-20130909
+Public-Lines: 10
+AAAAB3NzaC1kc3MAAACBALVdprWfZ7+FiITCILnDkeMZ2ntkV2WjW5RcyiQvJvBO
+jCNiVtK87xATEOfBb20YvNZ/CibBjGS1TL5TBqRV5XleucPHMJZ5rXdJ2FH5oZnL
+kna3Et+L1/O/GQMmp2vfSFrO3n3+mI1Jozx3FoQO8jr1zIerJ5Mc4LKqsIQB9hvR
+AAAAFQD+z1y1/4ll4ax3rri8mkYgGDhqIQAAAIBVU4VJ7V7GoEQJ5WBMbpDEcLIZ
+KUgSHsJMQzWnLOi/DcsPjVMDX6FWGPLtrjd7fgInlPMCC/SPAhXdaXMvHZSkvBHV
+DfNjpsDgsxBnK1FKqRGtD49rETFGDl92EOsyBhv+9ymdOPX6R0hCqS+ulZheQPXI
+iHXdIvQK2Ev5Dy3xNgAAAIA8qhumHZcKss+Puuw+mY5J5Qt7Omv16MuDsYiVqrBq
+1V2C9gutx3Tu+n5QYi0xPlkkP/knMtkUZS+Wt3Dr8zPcEzNBc/Tm2EdYp11jZNx4
+4PM4ing+aCU5oGcg/7RS5CrY5Fn/rvgHqK22XiC8/U55iti44bWKvI6HCejExeZX
+iA==
+Private-Lines: 1
+64IdcIX48CNGjhcxsVN0vSNpT7S72e3FVdQ3t4ENAvI=
+Private-MAC: 8e62b44b5c080965e361936520be4feaa50285b1

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk
new file mode 100644
index 0000000..2a11d04
--- /dev/null
+++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ssh-rsa-KeyPair.ppk
@@ -0,0 +1,18 @@
+PuTTY-User-Key-File-2: ssh-rsa
+Encryption: aes256-cbc
+Comment: rsa-key-20130909
+Public-Lines: 4
+AAAAB3NzaC1yc2EAAAABJQAAAIEAhY5eZJrX0a3+rtEZCq0nu2zvAHp16nk93jhi
+p7c9tTDlGm9QEAgqzmuilEeUQ4BssxAvhCFEo/7Qbg4M7PwcA5cFkjXE4gj0YDJM
+ay7l2mb5aIoS/hACgNz54p/w/UgfQC1Vygt6QtvXXAW8Lh/YCN4Zw4ViROUhoYuy
+3K5SBYs=
+Private-Lines: 8
+mqcGPnrv9d1tYkJZSGaCy5REslPZ2xh8m7qAbN+bD1m7iQ77pLxlKyzs82rbRaC9
+KSnKwsbFl7o92NT+9yYKJ7ehXyWyrUXkn9KcPk7MzNVwMuWVDXwvHodGLCyVCLYq
+PNipvg2USHvnCjnnvtMysBRNJiHTMOaf/gSZLyaEuznYo3FEClMPzggY9b2nrxnV
+O1ttk1FJatkRflwFjn3A/R/GpowmBnkDyCkVlTvR+uBAg8iIy1Vzj5zIV9zmzfgx
+DxPot+Y81y+Xe3ohVh2s1FVvLw+KQbYbCQam5j0V/dTQ+oVWjCJBlibD3aVTGK0M
+Jswz8wPwXFo5N0yX/6ZTrshbvTzoO1bg0+HUu581ZSAeqttk9C1RLmWFS8YDm0Hn
+GhDXrjuAvKJ3cjeVJsumgVw45NYGARuzV24TlHUtU+eze8Y/0NsPJXoCfVoYjTjb
+fjlMh9rYbRdyNHXwYTzwbw==
+Private-MAC: f4c50b3da0b73c34e8989411fc48c884c09e20a0

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/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 6c1f508..e3336ad 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
@@ -68,7 +68,8 @@ public interface KeyPairResourceParser extends KeyPairResourceLoader {
      * @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;
+    boolean canExtractKeyPairs(String resourceKey, List<String> lines)
+            throws IOException, GeneralSecurityException;
 
     /**
      * Converts the lines assumed to contain BASE-64 encoded data into
@@ -77,14 +78,19 @@ public interface KeyPairResourceParser extends KeyPairResourceLoader {
      * @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();
-
-        Base64.Decoder decoder = Base64.getDecoder();
-        return decoder.decode(data);
+        return data;
     }
 
     static boolean containsMarkerLine(List<String> lines, String marker) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
index 1e43f40..0c357af 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
@@ -26,9 +26,10 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -69,11 +70,7 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
     public Collection<KeyPair> extractKeyPairs(
             String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
                     throws IOException, GeneralSecurityException {
-        DSAPrivateKeySpec keySpec = decodeDSSPrivateKeySpec(stream, false);
-        KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM);
-        DSAPrivateKey prvKey = (DSAPrivateKey) kf.generatePrivate(keySpec);
-        DSAPublicKey pubKey = KeyUtils.recoverDSAPublicKey(prvKey);
-        KeyPair kp = new KeyPair(pubKey, prvKey);
+        KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
         return Collections.singletonList(kp);
     }
 
@@ -89,13 +86,16 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
      *      x       INTEGER
      * }
      * </code></pre>
+     * @param kf The {@link KeyFactory} To use to generate the keys
      * @param s The {@link InputStream} containing the encoded bytes
      * @param okToClose <code>true</code> if the method may close the input
      * stream regardless of success or failure
-     * @return The recovered {@link DSAPrivateKeySpec}
+     * @return The recovered {@link KeyPair}
      * @throws IOException If failed to read or decode the bytes
+     * @throws GeneralSecurityException If failed to generate the keys
      */
-    public static final DSAPrivateKeySpec decodeDSSPrivateKeySpec(InputStream s, boolean okToClose) throws IOException {
+    public static KeyPair decodeDSSKeyPair(KeyFactory kf, InputStream s, boolean okToClose)
+            throws IOException, GeneralSecurityException {
         ASN1Object sequence;
         try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) {
             sequence = parser.readObject();
@@ -116,12 +116,11 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
             BigInteger p = parser.readObject().asInteger();
             BigInteger q = parser.readObject().asInteger();
             BigInteger g = parser.readObject().asInteger();
-            // don't need it, but have to read it to get to x
-            @SuppressWarnings("unused")
             BigInteger y = parser.readObject().asInteger();
             BigInteger x = parser.readObject().asInteger();
-
-            return new DSAPrivateKeySpec(x, p, q, g);
+            PublicKey pubKey = kf.generatePublic(new DSAPublicKeySpec(y, p, q, g));
+            PrivateKey prvKey = kf.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
+            return new KeyPair(pubKey, prvKey);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/168adf20/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
index 0aea567..d760aaf 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
@@ -26,9 +26,11 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -69,11 +71,7 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
     public Collection<KeyPair> extractKeyPairs(
             String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream)
                     throws IOException, GeneralSecurityException {
-        RSAPrivateCrtKeySpec prvSpec = decodeRSAPrivateKeySpec(stream, false);
-        KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM);
-        RSAPrivateKey prvKey = (RSAPrivateKey) kf.generatePrivate(prvSpec);
-        RSAPublicKey pubKey = KeyUtils.recoverRSAPublicKey(prvSpec.getPrimeP(), prvSpec.getPrimeQ(), prvSpec.getPublicExponent());
-        KeyPair kp = new KeyPair(pubKey, prvKey);
+        KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
         return Collections.singletonList(kp);
     }
 
@@ -93,13 +91,16 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
      *   otherPrimeInfos   OtherPrimeInfos OPTIONAL
      * }
      * </code></pre>
+     * @param kf The {@link KeyFactory} To use to generate the keys
      * @param s The {@link InputStream} containing the encoded bytes
      * @param okToClose <code>true</code> if the method may close the input
      * stream regardless of success or failure
-     * @return The recovered {@link RSAPrivateCrtKeySpec}
+     * @return The recovered {@link KeyPair}
      * @throws IOException If failed to read or decode the bytes
+     * @throws GeneralSecurityException If failed to generate the keys
      */
-    public static RSAPrivateCrtKeySpec decodeRSAPrivateKeySpec(InputStream s, boolean okToClose) throws IOException {
+    public static KeyPair decodeRSAKeyPair(KeyFactory kf, InputStream s, boolean okToClose)
+            throws IOException, GeneralSecurityException {
         ASN1Object sequence;
         try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(s, okToClose))) {
             sequence = parser.readObject();
@@ -124,14 +125,18 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
 
             BigInteger modulus = parser.readObject().asInteger();
             BigInteger publicExp = parser.readObject().asInteger();
+            PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, publicExp));
+
             BigInteger privateExp = parser.readObject().asInteger();
             BigInteger primeP = parser.readObject().asInteger();
             BigInteger primeQ = parser.readObject().asInteger();
             BigInteger primeExponentP = parser.readObject().asInteger();
             BigInteger primeExponentQ = parser.readObject().asInteger();
             BigInteger crtCoef = parser.readObject().asInteger();
-
-            return new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef);
+            RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec(
+                    modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef);
+            PrivateKey prvKey = kf.generatePrivate(prvSpec);
+            return new KeyPair(pubKey, prvKey);
         }
     }
 }