Last active
December 26, 2015 03:39
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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