44import com .trilead .ssh2 .crypto .cipher .BlockCipher ;
55import com .trilead .ssh2 .crypto .cipher .DES ;
66import com .trilead .ssh2 .crypto .cipher .DESede ;
7+ import com .trilead .ssh2 .crypto .keys .Ed25519PrivateKey ;
78import com .trilead .ssh2 .signature .ECDSASHA2Verify ;
89
910import java .io .IOException ;
1516import java .security .NoSuchAlgorithmException ;
1617import java .security .PrivateKey ;
1718import java .security .SecureRandom ;
19+ import java .security .KeyFactory ;
1820import java .security .interfaces .DSAPrivateKey ;
1921import java .security .interfaces .ECPrivateKey ;
2022import java .security .interfaces .RSAPrivateCrtKey ;
23+ import java .security .interfaces .RSAPrivateKey ;
2124import java .security .spec .ECFieldFp ;
25+ import java .security .spec .InvalidKeySpecException ;
26+ import java .security .spec .RSAPrivateCrtKeySpec ;
2227import java .security .spec .ECParameterSpec ;
2328import java .security .spec .ECPoint ;
2429import java .util .Locale ;
@@ -203,7 +208,7 @@ public static String encodePrivateKey(PrivateKey privateKey, String password)
203208 /**
204209 * Encode private key to PEM format with auto-detected key type and specified algorithm.
205210 *
206- * @param privateKey private key (RSA, DSA, or EC )
211+ * @param privateKey private key (RSA, DSA, EC, or Ed25519 )
207212 * @param password password for encryption, or null for unencrypted
208213 * @param algorithm encryption algorithm
209214 * @return PEM-encoded private key
@@ -214,15 +219,100 @@ public static String encodePrivateKey(PrivateKey privateKey, String password, St
214219 throws IOException , InvalidKeyException {
215220 if (privateKey instanceof RSAPrivateCrtKey ) {
216221 return encodeRSAPrivateKey ((RSAPrivateCrtKey ) privateKey , password , algorithm );
222+ } else if (privateKey instanceof RSAPrivateKey ) {
223+ // Handle non-CRT RSA keys (e.g., from Conscrypt's OpenSSLRSAPrivateKey)
224+ try {
225+ RSAPrivateCrtKey crtKey = convertToRSAPrivateCrtKey ((RSAPrivateKey ) privateKey );
226+ return encodeRSAPrivateKey (crtKey , password , algorithm );
227+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e ) {
228+ throw new InvalidKeyException ("Failed to convert RSA key to CRT format" , e );
229+ }
217230 } else if (privateKey instanceof DSAPrivateKey ) {
218231 return encodeDSAPrivateKey ((DSAPrivateKey ) privateKey , password , algorithm );
219232 } else if (privateKey instanceof ECPrivateKey ) {
220233 return encodeECPrivateKey ((ECPrivateKey ) privateKey , password , algorithm );
234+ } else if (privateKey instanceof Ed25519PrivateKey ) {
235+ return encodeEd25519PrivateKey ((Ed25519PrivateKey ) privateKey , password , algorithm );
221236 } else {
222237 throw new InvalidKeyException ("Unsupported key type: " + privateKey .getClass ().getName ());
223238 }
224239 }
225240
241+ /**
242+ * Encode Ed25519 private key to PEM format (PKCS#8).
243+ *
244+ * @param privateKey Ed25519 private key
245+ * @param password password for encryption, or null for unencrypted
246+ * @return PEM-encoded private key
247+ * @throws IOException if encoding fails
248+ */
249+ public static String encodeEd25519PrivateKey (Ed25519PrivateKey privateKey , String password ) throws IOException {
250+ return encodeEd25519PrivateKey (privateKey , password , DEFAULT_ENCRYPTION );
251+ }
252+
253+ /**
254+ * Encode Ed25519 private key to PEM format (PKCS#8) with specified encryption algorithm.
255+ *
256+ * @param privateKey Ed25519 private key
257+ * @param password password for encryption, or null for unencrypted
258+ * @param algorithm encryption algorithm
259+ * @return PEM-encoded private key
260+ * @throws IOException if encoding fails
261+ */
262+ public static String encodeEd25519PrivateKey (Ed25519PrivateKey privateKey , String password , String algorithm )
263+ throws IOException {
264+ // Ed25519 uses PKCS#8 format (BEGIN PRIVATE KEY)
265+ byte [] encoded = privateKey .getEncoded ();
266+ return formatPEM ("PRIVATE KEY" , encoded , password , algorithm );
267+ }
268+
269+ /**
270+ * Converts an RSAPrivateKey to RSAPrivateCrtKey by parsing the PKCS#8 encoded form.
271+ * This is needed for keys from providers like Conscrypt that don't implement RSAPrivateCrtKey.
272+ *
273+ * @param privateKey The RSA private key to convert
274+ * @return The RSAPrivateCrtKey with CRT parameters
275+ * @throws InvalidKeySpecException if the key cannot be parsed
276+ * @throws NoSuchAlgorithmException if RSA algorithm is not available
277+ */
278+ static RSAPrivateCrtKey convertToRSAPrivateCrtKey (RSAPrivateKey privateKey )
279+ throws InvalidKeySpecException , NoSuchAlgorithmException {
280+ byte [] encoded = privateKey .getEncoded ();
281+ try {
282+ SimpleDERReader reader = new SimpleDERReader (encoded );
283+ reader .resetInput (reader .readSequenceAsByteArray ());
284+ if (!reader .readInt ().equals (BigInteger .ZERO )) {
285+ throw new InvalidKeySpecException ("PKCS#8 is not version 0" );
286+ }
287+
288+ reader .readSequenceAsByteArray (); // OID sequence
289+ reader .resetInput (reader .readOctetString ()); // RSA key bytes
290+ reader .resetInput (reader .readSequenceAsByteArray ()); // RSA key sequence
291+
292+ if (!reader .readInt ().equals (BigInteger .ZERO )) {
293+ throw new InvalidKeySpecException ("RSA key is not version 0" );
294+ }
295+
296+ BigInteger modulus = reader .readInt ();
297+ BigInteger publicExponent = reader .readInt ();
298+ BigInteger privateExponent = reader .readInt ();
299+ BigInteger primeP = reader .readInt ();
300+ BigInteger primeQ = reader .readInt ();
301+ BigInteger primeExponentP = reader .readInt ();
302+ BigInteger primeExponentQ = reader .readInt ();
303+ BigInteger crtCoefficient = reader .readInt ();
304+
305+ RSAPrivateCrtKeySpec spec = new RSAPrivateCrtKeySpec (
306+ modulus , publicExponent , privateExponent ,
307+ primeP , primeQ , primeExponentP , primeExponentQ , crtCoefficient );
308+
309+ KeyFactory kf = KeyFactory .getInstance ("RSA" );
310+ return (RSAPrivateCrtKey ) kf .generatePrivate (spec );
311+ } catch (IOException e ) {
312+ throw new InvalidKeySpecException ("Could not parse RSA key" , e );
313+ }
314+ }
315+
226316 private static String formatPEM (String type , byte [] data , String password , String algorithm ) throws IOException {
227317 byte [] encodedData = data ;
228318
0 commit comments