convert openSSH rsa key to javax.crypto.Cipher compatible format
Asked Answered
C

1

3

Is there a way to programmatically convert the Jsch generated SSH RSA-keys to a format javax.crypto.Cipher can use for encryption? I have mostly seen answers similar to this:

openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key_file -nocrypt > pkcs8_key

But I don't have acces to openSSL or shell commands. BTW: I'm only using JDK6.

Thanks to @erickson for his help I can convert the Public Key from RFC4716 to a Java public key using the exponent and the modulus as BigInteger types passed to a KeyFactory with the RSAPublicKeySpec. His solution is below.

Now I am trying to convert the private key. Here's an example privateJsch.key generated using writePrivateKey(str filename):

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC0ouLgTjmKjHU6UjNSL8HyTIdFM1UdVpgU81paWKreN8L36YoT
goZQHeyyUCCHmq3r3cKaySyu93mHBY0l76qSAIRZgE1IAFkBhNWBdlJ9UYA9HXm/
MqTQHbpqz0EYGE9TsFHS8dn1/utsJxKSWZ4xPNYjfS4Ps6G84iRwfdrIbQIDAQAB
AoGAKv3xnY1AqLcRV5Yk3NS9Blwsfc3f3iG0BJh+0q3zzPvcjYCp+kbAjOTyZuYn
N98asd6P6KMk3WfNJtOtanAGWl46bmtzNsQtSr5rVQEgs2w8i2yJcwVAYf2Td4qX
m3dH+roJA/CEFRSDat4sUfjOVmsYQXIBa0W2XTpp+7T1U4ECQQD1wSR6iTz7Bja0
MPcizDbRTRQHALBf7E8j8YOLpN/IGSox9pT+ktjsI2vMaD+b3SM4s0FD8quBlppE
o5FAguHxAkEAvCrCK7eZU3H+Ul1iw9Kd3WPHjDvQcdT5rEL+NSYEZyHgU7ipXEih
UHvK47Bkte/PVIu3jBFBnMujA0XiT0gSPQJBAI3+8j/nChgU6AjHfhRaIJZgzeCZ
8k8KcFPZWWOXeUHZ4HqL+lz5pmMSuFecKJy7cn1xfZVwIs62oR5l0CiRN1ECQCui
CqaSi3ZjH6M/znA0PbEhuxsUn7BVv5OncUUnzKuRmnAviO5CVU3Rdum3dJMPydcE
Ewri0YEnY2SV5vWVc80CQH43uBbshz7ju3DdVykHFrRElQB+f0YMK3Ad7eu+us0w
dLrOOoXP0T60B/bMTo8rdMa6XU/0w/w8FsOqoxNY23U=
-----END RSA PRIVATE KEY-----

using openssl asn1parse -in privateJsch.key -out privateJsch.der yeild:

$ openssl asn1parse -in privateJsch.key -out privateJsch.der
    0:d=0  hl=4 l= 604 cons: SEQUENCE
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=3 l= 129 prim: INTEGER           :B4A2E2E04E398A8C753A5233522FC1F24C874533551D569814F35A5A58AADE37C2F7E98A138286501DECB25020879AADEBDDC29AC92CAEF77987058D25EFAA92008459804D4800590184D58176527D51803D1D79BF32A4D01DBA6ACF4118184F53B051D2F1D9F5FEEB6C271292599E313CD6237D2E0FB3A1BCE224707DDAC86D
  139:d=1  hl=2 l=   3 prim: INTEGER           :010001
  144:d=1  hl=3 l= 128 prim: INTEGER           :2AFDF19D8D40A8B711579624DCD4BD065C2C7DCDDFDE21B404987ED2ADF3CCFBDC8D80A9FA46C08CE4F266E62737DF1AB1DE8FE8A324DD67CD26D3AD6A70065A5E3A6E6B7336C42D4ABE6B550120B36C3C8B6C8973054061FD93778A979B7747FABA0903F0841514836ADE2C51F8CE566B184172016B45B65D3A69FBB4F55381
  275:d=1  hl=2 l=  65 prim: INTEGER           :F5C1247A893CFB0636B430F722CC36D14D140700B05FEC4F23F1838BA4DFC8192A31F694FE92D8EC236BCC683F9BDD2338B34143F2AB81969A44A3914082E1F1
  342:d=1  hl=2 l=  65 prim: INTEGER           :BC2AC22BB7995371FE525D62C3D29DDD63C78C3BD071D4F9AC42FE3526046721E053B8A95C48A1507BCAE3B064B5EFCF548BB78C11419CCBA30345E24F48123D
  409:d=1  hl=2 l=  65 prim: INTEGER           :8DFEF23FE70A1814E808C77E145A209660CDE099F24F0A7053D95963977941D9E07A8BFA5CF9A66312B8579C289CBB727D717D957022CEB6A11E65D028913751
  476:d=1  hl=2 l=  64 prim: INTEGER           :2BA20AA6928B76631FA33FCE70343DB121BB1B149FB055BF93A7714527CCAB919A702F88EE42554DD176E9B774930FC9D704130AE2D18127636495E6F59573CD
  542:d=1  hl=2 l=  64 prim: INTEGER           :7E37B816EC873EE3BB70DD57290716B44495007E7F460C2B701DEDEBBEBACD3074BACE3A85CFD13EB407F6CC4E8F2B74C6BA5D4FF4C3FC3C16C3AAA31358DB75

base64 decoding the key portion yields the following in hex broken out into ASN.1 PKCS#1 components (see RFC3447) for this example only, others will follow pattern with different numbers of bytes:

30:82:    x3082 == ASN.1 Sequence
02:5C:    Key Length == 604 bytes
02:01:    x02 == ASN.1 integer, Value Length == 1 byte
00:       Version == 0
02:81:81: x02 == ASN.1 integer, Modulus Length == 129 bytes
00:B4:A2:E2:E0:4E:39:8A:8C:75:3A:52:33:52:2F:C1:
F2:4C:87:45:33:55:1D:56:98:14:F3:5A:5A:58:AA:DE:
37:C2:F7:E9:8A:13:82:86:50:1D:EC:B2:50:20:87:9A:
AD:EB:DD:C2:9A:C9:2C:AE:F7:79:87:05:8D:25:EF:AA:
92:00:84:59:80:4D:48:00:59:01:84:D5:81:76:52:7D:
51:80:3D:1D:79:BF:32:A4:D0:1D:BA:6A:CF:41:18:18:
4F:53:B0:51:D2:F1:D9:F5:FE:EB:6C:27:12:92:59:9E:
31:3C:D6:23:7D:2E:0F:B3:A1:BC:E2:24:70:7D:DA:C8:
6D:       Modulus
02:03:    x02 == ASN.1 integer, Value Length == 3 bytes
01:00:01: Public Exponent
02:81:80: x02 == ASN.1 integer, Value Length == 128 bytes
2A:FD:F1:9D:8D:40:A8:B7:11:57:96:24:DC:D4:BD:06:
5C:2C:7D:CD:DF:DE:21:B4:04:98:7E:D2:AD:F3:CC:FB:
DC:8D:80:A9:FA:46:C0:8C:E4:F2:66:E6:27:37:DF:1A:
B1:DE:8F:E8:A3:24:DD:67:CD:26:D3:AD:6A:70:06:5A:
5E:3A:6E:6B:73:36:C4:2D:4A:BE:6B:55:01:20:B3:6C:
3C:8B:6C:89:73:05:40:61:FD:93:77:8A:97:9B:77:47:
FA:BA:09:03:F0:84:15:14:83:6A:DE:2C:51:F8:CE:56:
6B:18:41:72:01:6B:45:B6:5D:3A:69:FB:B4:F5:53:81:
          Private Exponent 
02:41:    x02 == ASN.1 integer, Value Length == 65 bytes
00:F5:C1:24:7A:89:3C:FB:06:36:B4:30:F7:22:CC:36:
D1:4D:14:07:00:B0:5F:EC:4F:23:F1:83:8B:A4:DF:C8:
19:2A:31:F6:94:FE:92:D8:EC:23:6B:CC:68:3F:9B:DD:
23:38:B3:41:43:F2:AB:81:96:9A:44:A3:91:40:82:E1:
F1:       Prime P
02:41:    x02 == ASN.1 integer, Value Length == 65 bytes
00:BC:2A:C2:2B:B7:99:53:71:FE:52:5D:62:C3:D2:9D:
DD:63:C7:8C:3B:D0:71:D4:F9:AC:42:FE:35:26:04:67:
21:E0:53:B8:A9:5C:48:A1:50:7B:CA:E3:B0:64:B5:EF:
CF:54:8B:B7:8C:11:41:9C:CB:A3:03:45:E2:4F:48:12:
3D:       Prime Q
02:41:    x02 == ASN.1 integer, Value Length == 65 bytes
00:8D:FE:F2:3F:E7:0A:18:14:E8:08:C7:7E:14:5A:20:
96:60:CD:E0:99:F2:4F:0A:70:53:D9:59:63:97:79:41:
D9:E0:7A:8B:FA:5C:F9:A6:63:12:B8:57:9C:28:9C:BB:
72:7D:71:7D:95:70:22:CE:B6:A1:1E:65:D0:28:91:37:
51:       Prime P Exponent
02:40:    x02 == ASN.1 integer, Value Length == 64 bytes
2B:A2:0A:A6:92:8B:76:63:1F:A3:3F:CE:70:34:3D:B1:
21:BB:1B:14:9F:B0:55:BF:93:A7:71:45:27:CC:AB:91:
9A:70:2F:88:EE:42:55:4D:D1:76:E9:B7:74:93:0F:C9:
D7:04:13:0A:E2:D1:81:27:63:64:95:E6:F5:95:73:CD:
          Prime Q Exponent
02:40:    x02 == ASN.1 integer, Value Length == 64 bytes
7E:37:B8:16:EC:87:3E:E3:BB:70:DD:57:29:07:16:B4:
44:95:00:7E:7F:46:0C:2B:70:1D:ED:EB:BE:BA:CD:30:
74:BA:CE:3A:85:CF:D1:3E:B4:07:F6:CC:4E:8F:2B:74:
C6:BA:5D:4F:F4:C3:FC:3C:16:C3:AA:A3:13:58:DB:75
          CRT Coefficient

Similar posts:

references:

Chimera answered 14/10, 2013 at 17:40 Comment(4)
can you use the Bouncycastle library?Speechmaker
I could, but I'm trying hard to minimize additional libraries, packages & jar-files.Chimera
The Jsch contains a class KeyPairRSA that contains the writing and parsing of RSA keys. The only issue is that does not contain many getters. Otherwise you could simply convert the underlying byte array fields to positive BigInteger values and use a simple RSAPrivateKeySpec or RSAPrivateCrtKeySpec and an "RSA" KeyFactory (sorry, no time for a full answer)Surfing
com.jcraft.jsch.KeyPairRSA and KeyPair have load & getPublicKeyBlob neither of which are compatible with javax.crypto.Cipher as "keys". So the answer to my questions is basically, "no", com.jcraft.jsch does not seem to offer anyway to convert its object to a java.security.Key object, or to X.509 for public keys, or PKCS#8 for private keys.Chimera
D
6
static KeyPair demo(InputStream pub, InputStream pvt) throws IOException, GeneralSecurityException {
    KeyFactory f = KeyFactory.getInstance("RSA");

    RSAPublicKeySpec pubspec = decodeRSAPublicSSH(readAllBase64Bytes(pub));
    RSAPrivateCrtKeySpec pvtspec = decodeRSAPrivatePKCS1(readAllBase64Bytes(pvt));

    return new KeyPair(f.generatePublic(pubspec), f.generatePrivate(pvtspec));
}

static RSAPublicKeySpec decodeOpenSSH(byte[] input) {
    String[] fields = new String(input, StandardCharsets.US_ASCII).split(" ");
    if ((fields.length < 2) || (!fields[0].equals("ssh-rsa"))) throw new IllegalArgumentException("Unsupported type");
    byte[] std = Base64.getDecoder().decode(fields[1]);
    return decodeRSAPublicSSH(std);
}

static RSAPublicKeySpec decodeRSAPublicSSH(byte[] encoded) {
    ByteBuffer input = ByteBuffer.wrap(encoded);
    String type = string(input);
    if (!"ssh-rsa".equals(type)) throw new IllegalArgumentException("Unsupported type");
    BigInteger exp = sshint(input);
    BigInteger mod = sshint(input);
    if (input.hasRemaining()) throw new IllegalArgumentException("Excess data");
    return new RSAPublicKeySpec(mod, exp);
}

static RSAPrivateCrtKeySpec decodeRSAPrivatePKCS1(byte[] encoded) {
    ByteBuffer input = ByteBuffer.wrap(encoded);
    if (der(input, 0x30) != input.remaining()) throw new IllegalArgumentException("Excess data");
    if (!BigInteger.ZERO.equals(derint(input))) throw new IllegalArgumentException("Unsupported version");
    BigInteger n = derint(input);
    BigInteger e = derint(input);
    BigInteger d = derint(input);
    BigInteger p = derint(input);
    BigInteger q = derint(input);
    BigInteger ep = derint(input);
    BigInteger eq = derint(input);
    BigInteger c = derint(input);
    return new RSAPrivateCrtKeySpec(n, e, d, p, q, ep, eq, c);
}

private static String string(ByteBuffer buf) {
    return new String(lenval(buf), Charset.forName("US-ASCII"));
}

private static BigInteger sshint(ByteBuffer buf) {
    return new BigInteger(+1, lenval(buf));
}

private static byte[] lenval(ByteBuffer buf) {
    byte[] copy = new byte[buf.getInt()];
    buf.get(copy);
    return copy;
}

private static BigInteger derint(ByteBuffer input) {
    int len = der(input, 0x02);
    byte[] value = new byte[len];
    input.get(value);
    return new BigInteger(+1, value);
}

private static int der(ByteBuffer input, int exp) {
    int tag = input.get() & 0xFF;
    if (tag != exp) throw new IllegalArgumentException("Unexpected tag");
    int n = input.get() & 0xFF;
    if (n < 128) return n;
    n &= 0x7F;
    if ((n < 1) || (n > 2)) throw new IllegalArgumentException("Invalid length");
    int len = 0;
    while (n-- > 0) {
        len <<= 8;
        len |= input.get() & 0xFF;
    }
    return len;
}

private static byte[] readAllBase64Bytes(InputStream input) throws IOException {
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.US_ASCII));
    Decoder decoder = Base64.getDecoder();
    while (true) {
        String line = r.readLine();
        if (line == null) break;
        if (line.startsWith("-----")) continue;
        output.write(decoder.decode(line));
    }
    return output.toByteArray();
}
Discrown answered 17/10, 2013 at 19:12 Comment(15)
This seems to work, insofar as I get a "1024 bits Sun RSA public key with exponent of 65537 & 1024-bits long modulus, but I haven't checked it against the private key. I had to strip off the header/footer-lines & comments. (BTW, I can't use jdk7, only jdk6, so I had to work around some of jdk7 libs like java.nio.Files.) Also there are 13 bytes left over after the modulus, before the footer. They're not text, as base64 string they read "bRDQ0kh9j1ASyAihGA==". Do you know what these bytes are for?Chimera
@MarkMikofski There is no extra data left after the modulus when I use the sample you provided in the question. The fragment of base64-encoded data in your comment does not occur in the sample you provided. I can't guess what the problem is without a complete sample.Discrown
It works, thanks! I compared it with pem public key generated from SSH key using openssl, and the hex is the same as the Java key! But I made several mistakes along the way. (1) I decoded each line as I read it. I am using jdk6, so I used BufferedReader.readLine which does omit newline characters, but the entire string must be read first before encoding it. (2) For some reason storing the bytes from each line in a byte array, then decoding, also didn't work, I had to use StringBuilder. (3) Stoopid me, I didn't delete the last line of the RFC4716 ---- END SSH2 PUBLIC KEY ----Chimera
private key doesn't have the same format as public key, IE: doesn't start with <4-byte int> <key-type> <4-byte int> <exponent> <4-byte int> <modulus>Chimera
@MarkMikofski Is there an RFC that covers the private key format, or do you have a sample with corresponding public key?Discrown
The key is pkcs#1 asn.1 format, I put a sample of the key before base64 decoding and the der output from asn1parse in the messageChimera
the format is rfc3447 all the info is in the asn1parse output, header length, length of sequence. I think spec is RSAPrivateCrtKeySpec` not RSAPrivateKeySpec. I almost have it.Chimera
@MarkMikofski Please see my answer updated with private and public keys.Discrown
And note that my der() method is not a general-purpose ASN.1 DER parser. There's a lot more to it, but this suffices for this case.Discrown
Can't someone please add the missing closing double-quotation marks on line 17 after "Unsupported type?Chimera
this is a big piece of code: Thank you!!! I think it's worth to create a github project for it with a proper license file etc...Northwestwards
huh, the private key part works like a charm, but public throws exception: negative array length in lenval :(Northwestwards
@Northwestwards I'm guessing that your public key is not in the RFC 4716 format that Jsch uses. Is it an OpenSSH format? Instead of a header like "---- BEGIN SSH2 PUBLIC KEY ----", it starts with something like "ssh-rsa AAAAB3NzaC1yc2EAAAAD... ." Is that right?Discrown
@Discrown yes, indeed: I assumed it expects openSSH format ;) thanks again!! so does this code originally comes from or is based on JSch?Northwestwards
@Northwestwards If you read the comment history, it comes out that the OP actually had an RFC 4716 format file. Apparently that’s actually what Jsch uses? I’m not sure, and not sure why OP’s title says OpenSSH. I will look for other answers for OpenSSH and post one if I can’t find one.Discrown

© 2022 - 2024 — McMap. All rights reserved.