понедельник, 15 мая 2017 г.

Разбор публичного ключа в фомате ssh-rsa

Иногда приходится работать в java с фоматами ключей, для которых не написано никаких дополнительных библиотек, т возможность использовать openssl нет, либо не хочется. К примеру - публичные ssh ключи, которые генерит putty.exe Их формат описан в RFC 4253 в разделе 6.6


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;
}
потом обертки для чтения строк и BigInteger


private 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 и modulus
private 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.




Комментариев нет:

Отправить комментарий