Skip to content

Instantly share code, notes, and snippets.

@mikofski
Last active December 26, 2015 03:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikofski/7087690 to your computer and use it in GitHub Desktop.
Save mikofski/7087690 to your computer and use it in GitHub Desktop.
Demonstration of SSH -> Java KeyPair Conversion from SO (http://stackoverflow.com/a/19435226/1020470) by @erickson
static KeyPair OpenSSH2JavaKeyPairDemo(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 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)
{
int len = buf.getInt();
byte[] copy = new byte[len];
buf.get(copy);
return copy;
}
private static BigInteger derint(ByteBuffer input)
{
byte[] value = new byte[der(input, 0x02)];
input.get(value);
return new BigInteger(+1, value);
}
// get subset of DER (Distinguished Encoding Rules)
// checks 1st byte for value type using ASN.1
// returns length of value in bytes
private static int der(ByteBuffer input, int exp)
{
// get 1st byte of `input` to determine value type
// EG: x02 == integer according to ASN.1 (Abstract Syntax Notation)
int tag = input.get() & 0xFF; // xFF == 255 = 1111 1111, eff. mod(n,256)
// throw exception if input type is not expected
if (tag != exp)
throw new IllegalArgumentException("Unexpected tag");
// get next byte, limit to 8-bits (xFF)
int n = input.get() & 0xFF;
// return value length if less than 128 bytes
if (n < 128)
return n;
// if value length is greater than or equal to 127 bytes
n &= 0x7F; // x7F == 127 = 0111 1111, eff. mod(n,128)
// if mod(n,128) not in [1, 2], throw exception
if ((n < 1) || (n > 2))
throw new IllegalArgumentException("Invalid length");
int len = 0;
// decrement n by 1 each loop
while (n-- > 0) {
len <<= 8; // shift bits 8 spaces to the left
len |= input.get() & 0xFF; // read 8 bits into length, limit to 255
}
return len; // return length of value (in bytes) to be read
}
/* example 1:
* DER 02:41
* type is x02 == INTEGER
* Value length is x41 == 65 bytes
* example 2:
* DER 02:81:81
* type is x02 == INTEGER
* x81 == 129 > 128, mod(129,128) == 1, so loop once
* x81 == 129, len = 129 bytes or 1000 0001
* example 3:
* DER 02:81:80
* type is x02 == INTEGER
* x81 == 129 > 128, mod(129,128) == 1, so loop once
* x80 == 128, len = 128 bytes or 1000 0000
* example 4:
* DER 02:82:81:80
* type is x02 == INTEGER
* x82 == 130 > 128, mod(130,128) == 2, so loop twice
* x81 == 129, len = 129 bytes or 1000 0001
* x80 == 128, len = 33152 bytes or 1000 0001 1000 0000
* is this really correct???
*/
private static byte[] readAllBase64Bytes(InputStream input)
{
StringBuilder buf = new StringBuilder();
Scanner scanner = new Scanner(input, "US-ASCII");
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (!line.startsWith("-----"))
buf.append(line);
}
return DatatypeConverter.parseBase64Binary(buf.toString());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment