Иногда приходится работать в java с фоматами ключей, для которых не написано никаких дополнительных библиотек, т возможность использовать openssl нет, либо не хочется. К примеру - публичные ssh ключи, которые генерит putty.exe Их формат описан в RFC 4253 в разделе 6.6
Выглядит довольно просто, непонятно только что такое mpint, ну и string похоже не так прост. Описание находим в RFC 4251 в разделе 5.
Теперь можно писать конвертер.
Напишем функцию для чтения байтов, которая сначала читает 4 байта длины, а затем остальное значение.
The "ssh-rsa" key format has the following specific encoding:
string "ssh-rsa"
mpint e
mpint n
Here the 'e' and 'n' parameters form the signature key blob.
Смотрим что внутри файла, предварительно декодиров его из base64Выглядит довольно просто, непонятно только что такое mpint, ну и string похоже не так прост. Описание находим в RFC 4251 в разделе 5.
string Arbitrary length binary string. Strings are allowed to contain arbitrary binary data, including null characters and 8-bit characters. They are stored as a uint32 containing its length (number of bytes that follow) and zero (= empty string) or more bytes that are the value of the string. Terminating null characters are not used. Strings are also used to store text. In that case, US-ASCII is used for internal names, and ISO-10646 UTF-8 for text that might be displayed to the user. The terminating null character SHOULD NOT normally be stored in the string. For example: the US-ASCII string "testing" is represented as 00 00 00 07 t e s t i n g. The UTF-8 mapping does not alter the encoding of US-ASCII characters. mpint Represents multiple precision integers in two's complement format, stored as a string, 8 bits per byte, MSB first. Negative numbers have the value 1 as the most significant bit of the first byte of the data partition. If the most significant bit would be set for a positive number, the number MUST be preceded by a zero byte. Unnecessary leading bytes with the value 0 or 255 MUST NOT be included. The value zero MUST be stored as a string with zero bytes of data. By convention, a number that is used in modular computations in Z_n SHOULD be represented in the range 0 <= x < n.
Examples: value (hex) representation (hex) ----------- -------------------- 0 00 00 00 00 9a378f9b2e332a7 00 00 00 08 09 a3 78 f9 b2 e3 32 a7 80 00 00 00 02 00 80 -1234 00 00 00 02 ed cc -deadbeef 00 00 00 05 ff 21 52 41 11
Теперь можно писать конвертер.
Напишем функцию для чтения байтов, которая сначала читает 4 байта длины, а затем остальное значение.
private byte[] readBytes(ByteBuffer buffer, AtomicInteger pos){
int len = buffer.getInt(pos.get());
byte buff[] = new byte[len];
for(int i = 0; i < len; i++) {
buff[i] = buffer.get(i + pos.get() + SIZEOF_INT);
}
pos.set(pos.get() + SIZEOF_INT + len);
return buff;
}
потом обертки для чтения строк и BigIntegerprivate BigInteger readMpint(ByteBuffer buffer, AtomicInteger pos){
byte[] bytes = readBytes(buffer, pos);
if(bytes.length == 0){
return BigInteger.ZERO;
}
return new BigInteger(bytes);
}
private String readString(ByteBuffer buffer, AtomicInteger pos){
byte[] bytes = readBytes(buffer, pos);
if(bytes.length == 0){
return "";
}
return new String(bytes, StandardCharsets.US_ASCII);}
Далее все тривиально, читаем и декодируем из base64 ключ, проверяем, что алгоритм ssh-rsa и считываем exponent и modulusprivate static int SIZEOF_INT = 4;
private static String key1 = "AAAAB3NzaC1yc2EAAAADAQABAAABAQClAxT5S/WuX04OXBt9R59WcL45OmaU3M5U063lfyja7ovqaVR7/2kHtLF/LoCQCXSZMny8RTCGDjoXD7G/tGsyHFDHCI//Y1VDLE06AlDzrlu69DQY91+6gkhGjH3SF6us5hXlihrbSFLfAlSdkEs8gwSrspVQyuaOf+39dnMddhEDYYg+z0ce82ta/n8xPBWCp60nDEDayNjOsRgzDJKSujNfngjQTL1x6qKJj8BW/P5lLJE1nbMm9BQD9G7glJk86qh1I/tJCnij6On0m6KcdzVz8cU3sBgNeB433kGjJtpxXXmJB6Vuu5IverhyfpiB4hP9WlKa/LSzW+ZIdvl/";
@Test
public void convertkey() throws Exception {
byte[] decoded = java.util.Base64.getDecoder().decode(key1);
try {
ByteBuffer byteBuffer = ByteBuffer.wrap(decoded);
AtomicInteger position = new AtomicInteger();
//first read algorithm, should be ssh-rsa
String algorithm = readString(byteBuffer, position);
System.out.println(algorithm);
assert "ssh-rsa".equals(algorithm);
// than read exponent
BigInteger publicExponent = readMpint(byteBuffer, position);
System.out.println("publicExponent = " + publicExponent);
// than read modulus
BigInteger modulus = readMpint(byteBuffer, position);
System.out.println("modulus = " + modulus);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey publicKey = kf.generatePublic(keySpec);
System.out.printf("%s, is RSAPublicKey: %b%n", publicKey.getClass().getName(), publicKey instanceof RSAPublicKey);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
Дальше с публичным ключом модно делать все что угодно, использую стандартные средства java.
Комментариев нет:
Отправить комментарий