You have to decode the OpenSSH key and the result will be a KeyPair
try {
String PEMPrivateKey = "...";
String password = "";
java.security.KeyPair key = PEMDecoder.decodeKeyPairOpenSSH(PEMPrivateKey, password);
java.security.interfaces.RSAPublicKey pub = (java.security.interfaces.RSAPublicKey)key.getPublic();
} catch (IOException e) {
// key not in new OpenSSH key format
}
Below is the PEM decoder from the trilead SSH project.
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import com.trilead.ssh2.crypto.cipher.AES;
import com.trilead.ssh2.crypto.cipher.BlockCipher;
import com.trilead.ssh2.crypto.cipher.CBCMode;
import com.trilead.ssh2.crypto.cipher.DES;
import com.trilead.ssh2.crypto.cipher.DESede;
import com.trilead.ssh2.crypto.digest.MD5;
import com.trilead.ssh2.signature.DSAPrivateKey;
import com.trilead.ssh2.signature.DidECKeyPair;
import com.trilead.ssh2.signature.ECDSAKeyAlgorithm;
import com.trilead.ssh2.signature.KeyAlgorithm;
import com.trilead.ssh2.signature.KeyAlgorithmManager;
import com.trilead.ssh2.signature.RSAPrivateKey;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
/**
* PEM Support.
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
*/
public class PEMDecoder
{
private static final int PEM_RSA_PRIVATE_KEY = 1;
private static final int PEM_DSA_PRIVATE_KEY = 2;
private static final int PEM_EC_PRIVATE_KEY = 3;
private static final int hexToInt(char c)
{
if ((c >= 'a') && (c <= 'f'))
{
return (c - 'a') + 10;
}
if ((c >= 'A') && (c <= 'F'))
{
return (c - 'A') + 10;
}
if ((c >= '0') && (c <= '9'))
{
return (c - '0');
}
throw new IllegalArgumentException("Need hex char");
}
private static byte[] hexToByteArray(String hex)
{
if (hex == null)
throw new IllegalArgumentException("null argument");
if ((hex.length() % 2) != 0)
throw new IllegalArgumentException("Uneven string length in hex encoding.");
byte decoded[] = new byte[hex.length() / 2];
for (int i = 0; i < decoded.length; i++)
{
int hi = hexToInt(hex.charAt(i * 2));
int lo = hexToInt(hex.charAt((i * 2) + 1));
decoded[i] = (byte) (hi * 16 + lo);
}
return decoded;
}
private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen)
throws IOException
{
if (salt.length < 8)
throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation.");
MD5 md5 = new MD5();
byte[] key = new byte[keyLen];
byte[] tmp = new byte[md5.getDigestLength()];
while (true)
{
md5.update(password, 0, password.length);
md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the
// salt in this step.
// This took me two hours until I got AES-xxx running.
int copy = (keyLen < tmp.length) ? keyLen : tmp.length;
md5.digest(tmp, 0);
System.arraycopy(tmp, 0, key, key.length - keyLen, copy);
keyLen -= copy;
if (keyLen == 0)
return key;
md5.update(tmp, 0, tmp.length);
}
}
private static byte[] removePadding(byte[] buff, int blockSize) throws IOException
{
/* Removes RFC 1423/PKCS #7 padding */
int rfc_1423_padding = buff[buff.length - 1] & 0xff;
if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize))
throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?");
for (int i = 2; i <= rfc_1423_padding; i++)
{
if (buff[buff.length - i] != rfc_1423_padding)
throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?");
}
byte[] tmp = new byte[buff.length - rfc_1423_padding];
System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding);
return tmp;
}
private static final PEMStructure parsePEM(char[] pem) throws IOException
{
PEMStructure ps = new PEMStructure();
String line = null;
BufferedReader br = new BufferedReader(new CharArrayReader(pem));
String endLine = null;
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, '-----BEGIN...' missing");
line = line.trim();
if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----"))
{
endLine = "-----END DSA PRIVATE KEY-----";
ps.pemType = PEM_DSA_PRIVATE_KEY;
break;
}
if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----"))
{
endLine = "-----END RSA PRIVATE KEY-----";
ps.pemType = PEM_RSA_PRIVATE_KEY;
break;
}
if (line.startsWith("-----BEGIN EC PRIVATE KEY-----"))
{
endLine = "-----END EC PRIVATE KEY-----";
ps.pemType = PEM_EC_PRIVATE_KEY;
break;
}
}
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, " + endLine + " missing");
line = line.trim();
int sem_idx = line.indexOf(':');
if (sem_idx == -1)
break;
String name = line.substring(0, sem_idx + 1);
String value = line.substring(sem_idx + 1);
String values[] = value.split(",");
for (int i = 0; i < values.length; i++)
values[i] = values[i].trim();
// Proc-Type: 4,ENCRYPTED
// DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483
if ("Proc-Type:".equals(name))
{
ps.procType = values;
continue;
}
if ("DEK-Info:".equals(name))
{
ps.dekInfo = values;
continue;
}
/* Ignore line */
}
StringBuffer keyData = new StringBuffer();
while (true)
{
if (line == null)
throw new IOException("Invalid PEM structure , " + new String(pem) + endLine + " missing");
line = line.trim();
if (line.startsWith(endLine))
break;
keyData.append(line);
line = br.readLine();
}
char[] pem_chars = new char[keyData.length()];
keyData.getChars(0, pem_chars.length, pem_chars, 0);
ps.data = Base64.decode(pem_chars);
if (ps.data.length == 0)
throw new IOException("Invalid PEM structure, no data available");
return ps;
}
private static PEMStructure parsePEM(char[] pem, CertificateDecoder certificateDecoder) throws IOException
{
PEMStructure ps = new PEMStructure();
String line;
BufferedReader br = new BufferedReader(new CharArrayReader(pem));
String endLine;
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, '-----BEGIN...' missing");
line = line.trim();
if (line.startsWith(certificateDecoder.getStartLine()))
{
endLine = certificateDecoder.getEndLine();
break;
}
}
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, " + endLine + " missing");
line = line.trim();
int sem_idx = line.indexOf(':');
if (sem_idx == -1)
break;
String name = line.substring(0, sem_idx + 1);
String value = line.substring(sem_idx + 1);
String values[] = value.split(",");
for (int i = 0; i < values.length; i++)
values[i] = values[i].trim();
// Proc-Type: 4,ENCRYPTED
// DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483
if ("Proc-Type:".equals(name))
{
ps.procType = values;
continue;
}
if ("DEK-Info:".equals(name))
{
ps.dekInfo = values;
continue;
}
/* Ignore line */
}
StringBuilder keyData = new StringBuilder();
while (true)
{
if (line == null)
throw new IOException("Invalid PEM structure, " + endLine + " missing");
line = line.trim();
if (line.startsWith(endLine))
break;
keyData.append(line);
line = br.readLine();
}
char[] pem_chars = new char[keyData.length()];
keyData.getChars(0, pem_chars.length, pem_chars, 0);
ps.data = Base64.decode(pem_chars);
if (ps.data.length == 0)
throw new IOException("Invalid PEM structure, no data available");
return ps;
}
private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException
{
if (ps.dekInfo == null)
throw new IOException("Broken PEM, no mode and salt given, but encryption enabled");
if (ps.dekInfo.length != 2)
throw new IOException("Broken PEM, DEK-Info is incomplete!");
String algo = ps.dekInfo[0];
byte[] salt = hexToByteArray(ps.dekInfo[1]);
BlockCipher bc = null;
if (algo.equals("DES-EDE3-CBC"))
{
DESede des3 = new DESede();
des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24));
bc = new CBCMode(des3, salt, false);
}
else if (algo.equals("DES-CBC"))
{
DES des = new DES();
des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8));
bc = new CBCMode(des, salt, false);
}
else if (algo.equals("AES-128-CBC"))
{
AES aes = new AES();
aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16));
bc = new CBCMode(aes, salt, false);
}
else if (algo.equals("AES-192-CBC"))
{
AES aes = new AES();
aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24));
bc = new CBCMode(aes, salt, false);
}
else if (algo.equals("AES-256-CBC"))
{
AES aes = new AES();
aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32));
bc = new CBCMode(aes, salt, false);
}
else
{
throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo);
}
if ((ps.data.length % bc.getBlockSize()) != 0)
throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of "
+ bc.getBlockSize());
/* Now decrypt the content */
byte[] dz = new byte[ps.data.length];
for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++)
{
bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize());
}
/* Now check and remove RFC 1423/PKCS #7 padding */
dz = removePadding(dz, bc.getBlockSize());
ps.data = dz;
ps.dekInfo = null;
ps.procType = null;
}
public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException
{
if (ps.procType == null)
return false;
if (ps.procType.length != 2)
throw new IOException("Unknown Proc-Type field.");
if ("4".equals(ps.procType[0]) == false)
throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")");
if ("ENCRYPTED".equals(ps.procType[1]))
return true;
return false;
}
public static Object decode(char[] pem, String password) throws IOException
{
PEMStructure ps = parsePEM(pem);
if (isPEMEncrypted(ps))
{
if (password == null)
throw new IOException("PEM is encrypted, but no password was specified");
decryptPEM(ps, password.getBytes("ISO-8859-1"));
}
if (ps.pemType == PEM_DSA_PRIVATE_KEY)
{
SimpleDERReader dr = new SimpleDERReader(ps.data);
byte[] seq = dr.readSequenceAsByteArray();
if (dr.available() != 0)
throw new IOException("Padding in DSA PRIVATE KEY DER stream.");
dr.resetInput(seq);
BigInteger version = dr.readInt();
if (version.compareTo(BigInteger.ZERO) != 0)
throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream.");
BigInteger p = dr.readInt();
BigInteger q = dr.readInt();
BigInteger g = dr.readInt();
BigInteger y = dr.readInt();
BigInteger x = dr.readInt();
if (dr.available() != 0)
throw new IOException("Padding in DSA PRIVATE KEY DER stream.");
return new DSAPrivateKey(p, q, g, y, x);
}
if (ps.pemType == PEM_RSA_PRIVATE_KEY)
{
SimpleDERReader dr = new SimpleDERReader(ps.data);
byte[] seq = dr.readSequenceAsByteArray();
if (dr.available() != 0)
throw new IOException("Padding in RSA PRIVATE KEY DER stream.");
dr.resetInput(seq);
BigInteger version = dr.readInt();
if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0))
throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream.");
BigInteger n = dr.readInt();
BigInteger e = dr.readInt();
BigInteger d = dr.readInt();
return new RSAPrivateKey(d, e, n);
}
if (ps.pemType == PEM_EC_PRIVATE_KEY)
{
SimpleDERReader DERderReader = new SimpleDERReader(ps.data);
byte[] sequence = DERderReader.readSequenceAsByteArray();
if (DERderReader.available() != 0) {
throw new IOException("Unexpected padding in EC private key");
}
SimpleDERReader sequenceReader = new SimpleDERReader(sequence);
BigInteger version = sequenceReader.readInt();
if ((version.compareTo(BigInteger.ONE) != 0)) {
throw new IOException("Unexpected version number in EC private key: " + version);
}
byte[] privateBytes = sequenceReader.readOctetString();
String curveOid = null;
byte[] publicBytes = null;
while (sequenceReader.available() > 0) {
int type = sequenceReader.readConstructedType();
SimpleDERReader fieldReader = sequenceReader.readConstructed();
switch (type) {
case 0:
curveOid = fieldReader.readOid();
break;
case 1:
publicBytes = fieldReader.readOctetString();
break;
}
}
ASN1ObjectIdentifier asn1 = new ASN1ObjectIdentifier(curveOid);
X9ECParameters x9 = ECNamedCurveTable.getByOID(asn1);
BigInteger s = new BigInteger(1, privateBytes);
byte[] publicBytesSlice = new byte[publicBytes.length - 1];
System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length);
ECPoint w = ECDSAKeyAlgorithm.decodePoint(publicBytesSlice, x9.getCurve());
didisoft.sftp.bouncycastle.math.ec.ECCurve curve = x9.getCurve();
ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, new didisoft.sftp.bouncycastle.jce.spec.ECParameterSpec(curve, x9.getG(), x9.getN()));
ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, new didisoft.sftp.bouncycastle.jce.spec.ECParameterSpec(curve, x9.getG(), x9.getN()));
try {
// BC hack because jce-impl KeyFactorySpi.generatePublic doesn't support java.security.spec.ECPublicKeySpec !
// KeyFactory factory = KeyFactory.getInstance("EC");
KeyFactorySpi.ECDSA factory = new KeyFactorySpi.ECDSA();
PublicKey ecPublicKey = factory.engineGeneratePublic(pubSpec);
PrivateKey ecPrivateKey = factory.engineGeneratePrivate(privSpec);
return new DidECKeyPair(ECNamedCurveTable.getName(asn1), ecPublicKey, ecPrivateKey);
} catch (InvalidKeySpecException ex) {
throw new IOException("Could not generate EC key pair: " + ex.getMessage());
}
}
throw new IOException("PEM problem: it is of unknown type");
}
public static Object decodeKeyPairOpenSSH(char[] pem, String password) throws IOException
{
KeyPair key = decodeKeyPair(pem, password);
if (key.getPrivate() instanceof java.security.interfaces.RSAPrivateKey) {
java.security.interfaces.RSAPrivateCrtKey rsa = (java.security.interfaces.RSAPrivateCrtKey)key.getPrivate();
return new RSAPrivateKey(rsa.getPrivateExponent(), rsa.getPublicExponent(), rsa.getModulus());
} else if (key.getPrivate() instanceof java.security.interfaces.DSAPrivateKey) {
java.security.interfaces.DSAPublicKey dsaPub = (java.security.interfaces.DSAPublicKey)key.getPublic();
java.security.interfaces.DSAPrivateKey dsaPriv = (java.security.interfaces.DSAPrivateKey)key.getPrivate();
return new DSAPrivateKey(dsaPub.getParams().getP(),
dsaPub.getParams().getQ(),
dsaPub.getParams().getG(),
dsaPub.getY(),
dsaPriv.getX());
} else {
return null;
}
}
/**
* Decodes keys in the new OpenSSH format
* @param pem
* @param password
* @return
* @throws IOException
*/
public static KeyPair decodeKeyPair(char[] pem, String password) throws IOException
{
for (KeyAlgorithm<PublicKey, PrivateKey> algorithm : KeyAlgorithmManager.getSupportedAlgorithms()) {
for (CertificateDecoder decoder : algorithm.getCertificateDecoders()) {
try {
PEMStructure ps = parsePEM(pem, decoder);
if (isPEMEncrypted(ps)) {
if (password == null)
throw new IOException("PEM is encrypted, but no password was specified");
decryptPEM(ps, password.getBytes("ISO-8859-1"));
}
return decoder.createKeyPair(ps, password);
} catch (IOException ex) {
//LOGGER.log(Level.FINE, "Could not decode PEM Key using current decoder: " + decoder.getClass().getName(), ex);
// we couldn't decode the input, try another decoder
}
}
}
throw new IOException("PEM problem: it is of unknown type");
}
}