Given this randomly generated ECDSA private key:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR1n1SFvy7Di392GmMy8JsWEjbffTCu
nGKwZrIgq/yIy1C33ud4bxN3W4vbXCtZfyPeVbWNpW1eXSZ/3uWmcJ3SAAAAmCxLaSMsS2
kjAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHWfVIW/LsOLf3Ya
YzLwmxYSNt99MK6cYrBmsiCr/IjLULfe53hvE3dbi9tcK1l/I95VtY2lbV5dJn/e5aZwnd
IAAAAgUgu0f1JX6BTUL3UU3Xq3C8erF/W2cIgzuCHciLp55HYAAAAA
-----END OPENSSH PRIVATE KEY-----
I am using this code to parse it into a PrivateKey
:
public PrivateKey readPrivateKeyAsOpenSsh(Reader reader) throws IOException
{
try (PemReader pemReader = new PemReader(reader))
{
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
AsymmetricKeyParameter keyParameter = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(content);
if (!(keyParameter instanceof ECPrivateKeyParameters ecPrivateKeyParameters))
throw new UnsupportedOperationException("Unsupported format: " + pemObject.getType());
BigInteger d = ecPrivateKeyParameters.getD();
ECNamedCurveSpec ecNamedCurveSpec = getEcNamedCurveSpec(ecPrivateKeyParameters);
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecNamedCurveSpec);
KeyFactory keyFactory;
try
{
keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(ecPrivateKeySpec);
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e)
{
// Deployment-time decision
throw new AssertionError(e);
}
}
}
/**
* Returns the name of a key's algorithm.
*
* @param keyParameter a key's parameters
* @return the name of the key's algorithm
* @throws IOException if the algorithm is unsupported
*/
private static String getAlgorithm(AsymmetricKeyParameter keyParameter) throws IOException
{
return switch (keyParameter)
{
case RSAKeyParameters _ -> "RSA";
case DSAPublicKeyParameters _ -> "DSA";
case ECPublicKeyParameters _ -> "EC";
default -> throw new IOException("Unsupported key parameter: " +
keyParameter.getClass().getName());
};
}
/**
* @param keyParameters the private key's parameters
* @return the set of domain parameters used with elliptic curve cryptography (ECC)
* @throws IOException if the elliptical curve used is unknown
*/
private ECNamedCurveSpec getEcNamedCurveSpec(ECPrivateKeyParameters keyParameters) throws IOException
{
ECDomainParameters domainParameters = keyParameters.getParameters();
X9ECParameters x9ECParameters = getX9EcParameters(domainParameters);
if (x9ECParameters == null)
throw new IOException("Failed to convert domain parameters to X9ECParameters");
EllipticCurve ellipticCurve = new EllipticCurve(
new ECFieldFp(x9ECParameters.getCurve().getField().getCharacteristic()),
x9ECParameters.getCurve().getA().toBigInteger(),
x9ECParameters.getCurve().getB().toBigInteger());
ECPoint g = x9ECParameters.getG();
String curveName = getCurveName(domainParameters);
if (curveName == null)
throw new IOException("Failed to find the curve name");
return new ECNamedCurveSpec(curveName,
ellipticCurve,
new java.security.spec.ECPoint(
g.getAffineXCoord().toBigInteger(),
g.getAffineYCoord().toBigInteger()),
x9ECParameters.getN(),
x9ECParameters.getH());
}
/**
* Returns the X9ECParameters of the curve with the specified domain parameters.
*
* @param domainParameters domain parameters
* @return null if no match is found
*/
private X9ECParameters getX9EcParameters(ECDomainParameters domainParameters)
{
Entry<String, X9ECParameters> details = getCurveDetails(domainParameters);
if (details == null)
return null;
return details.getValue();
}
/**
* Returns the name of the curve with the specified domain parameters.
*
* @param domainParameters domain parameters
* @return null if no match is found
*/
private String getCurveName(ECDomainParameters domainParameters)
{
Entry<String, X9ECParameters> details = getCurveDetails(domainParameters);
if (details == null)
return null;
return details.getKey();
}
/**
* Returns the name and X9ECParameters of the curve with the specified domain parameters.
*
* @param domainParameters domain parameters
* @return null if no match is found
*/
private Entry<String, X9ECParameters> getCurveDetails(ECDomainParameters domainParameters)
{
for (Enumeration<?> e = ECNamedCurveTable.getNames(); e.hasMoreElements(); )
{
String name = (String) e.nextElement();
X9ECParameters x9EcParams = ECNamedCurveTable.getByName(name);
if (x9EcParams.getCurve().equals(domainParameters.getCurve()))
return new SimpleImmutableEntry<>(name, x9EcParams);
}
return null;
}
This seems to work, but I can't figure out how to convert it bck to a PEM-encoded OPENSSH PRIVATE KEY. Here is the code that isn't working:
/**
* Writes a {@code PrivateKey} as a PEM-encoded OpenSSH format stream.
*
* @param privateKey the key
* @param writer the stream to write into
* @throws NullPointerException if any of the arguments are null
* @throws IOException if an error occurs while writing into the stream
*/
public void writePrivateKeyAsOpenSsh(ECPrivateKey privateKey, Writer writer) throws IOException
{
AsymmetricKeyParameter param = getAsymmetricKeyParameter(privateKey);
try (JcaPEMWriter pemWriter = new JcaPEMWriter(writer))
{
byte[] encodedPrivateKey = OpenSSHPrivateKeyUtil.encodePrivateKey(param);
PemObject pemObject = new PemObject("OPENSSH PRIVATE KEY", encodedPrivateKey);
pemWriter.writeObject(pemObject);
pemWriter.flush();
}
}
It returns this output:
-----BEGIN OPENSSH PRIVATE KEY-----
MIIBaAIBAQQgUgu0f1JX6BTUL3UU3Xq3C8erF/W2cIgzuCHciLp55HaggfowgfcC
AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////
MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr
vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE
axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W
K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8
YyVRAgEBoUQDQgAEdZ9Uhb8uw4t/dhpjMvCbFhI2330wrpxisGayIKv8iMtQt97n
eG8Td1uL21wrWX8j3lW1jaVtXl0mf97lpnCd0g==
-----END OPENSSH PRIVATE KEY-----
which doesn't seem to be a valid key. I search discussion forums and the BouncyCastle testcases to no avail.
Any ideas?
Thank you in advance.
I got the following working using the MINA sshd library (https://github.com/apache/mina-sshd):
/**
* Writes a {@code KeyPair} as a PEM-encoded OpenSSH format stream.
*
* @param keys a key pair
* @param out the stream to write into
* @throws NullPointerException if any of the arguments are null
* @throws IOException if an error occurs while reading the stream
* @throws GeneralSecurityException if the key pair is unsupported or invalid
*/
public void writeKeyPairAsOpenSsh(Collection<KeyPair> keys, OutputStream out)
throws IOException, GeneralSecurityException
{
OpenSSHKeyPairResourceWriter openSshWriter = OpenSSHKeyPairResourceWriter.INSTANCE;
for (KeyPair key : keys)
openSshWriter.writePrivateKey(key, null, null, out);
}
Short and to the point.