Рубрики
Без рубрики

Как прочитать файл PEM, чтобы получить открытые и закрытые ключи

Узнайте, как читать открытые и закрытые ключи из файлов PEM.

Автор оригинала: Catalin Burcea.

1. Обзор

В криптографии с открытым ключом (также известной как асимметричная криптография) механизм шифрования опирается на два связанных ключа: открытый ключ и закрытый ключ. Открытый ключ используется для шифрования сообщения, в то время как только владелец закрытого ключа может расшифровать сообщение.

В этом уроке мы рассмотрим, как читать открытые и закрытые ключи из файла PEM.

Во-первых, мы изучим некоторые важные концепции криптографии с открытым ключом. Затем мы узнаем, как читать PEM-файлы с помощью чистой Java.

Наконец, мы исследуем Вышибала библиотека как альтернативный подход.

2. Концепции

Прежде чем мы начнем, давайте разберемся в некоторых ключевых понятиях.

X. 509-это стандарт, определяющий формат сертификатов открытого ключа. Таким образом, этот формат описывает открытый ключ среди другой информации.

DER – самый популярный формат кодирования для хранения данных, таких как сертификаты X. 509, закрытые ключи PKCS8 в файлах. Это двоичная кодировка, и результирующее содержимое не может быть просмотрено с помощью текстового редактора.

PKCS8 – это стандартный синтаксис для хранения информации о закрытом ключе. Закрытый ключ может быть дополнительно зашифрован с помощью симметричного алгоритма.

Этот стандарт может обрабатывать не только закрытые ключи RSA, но и другие алгоритмы. Закрытые ключи PKCS8 обычно обмениваются через формат кодирования PEM.

PEM – это механизм кодирования base64 сертификата DER. PEM может также кодировать другие виды данных, такие как открытые/закрытые ключи и запросы сертификатов.

Файл PEM также содержит верхний и нижний колонтитулы, описывающие тип закодированных данных:

-----BEGIN PUBLIC KEY-----
...Base64 encoding of the DER encoded certificate...
-----END PUBLIC KEY-----

3. Использование чистой Java

3.1. Считывание Данных PEM Из Файла

Давайте начнем с чтения файла PEM и сохранения его содержимого в строку:

String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

3.2. Получить Открытый Ключ Из Строки PEM

Мы собираемся создать служебный метод, который получает открытый ключ из закодированной строки PEM:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjtGIk8SxD+OEiBpP2/T
JUAF0upwuKGMk6wH8Rwov88VvzJrVm2NCticTk5FUg+UG5r8JArrV4tJPRHQyvqK
wF4NiksuvOjv3HyIf4oaOhZjT8hDne1Bfv+cFqZJ61Gk0MjANh/T5q9vxER/7TdU
NHKpoRV+NVlKN5bEU/NQ5FQjVXicfswxh6Y6fl2PIFqT2CfjD+FkBPU1iT9qyJYH
A38IRvwNtcitFgCeZwdGPoxiPPh1WHY8VxpUVBv/2JsUtrB/rAIbGqZoxAIWvijJ
Pe9o1TY3VlOzk9ASZ1AeatvOir+iDVJ5OpKmLnzc46QgGPUsjIyo6Sje9dxpGtoG
QQIDAQAB
-----END PUBLIC KEY-----

Предположим, мы получаем Файл в качестве параметра:

public static RSAPublicKey readPublicKey(File file) throws Exception {
    String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

    String publicKeyPEM = key
      .replace("-----BEGIN PUBLIC KEY-----", "")
      .replaceAll(System.lineSeparator(), "")
      .replace("-----END PUBLIC KEY-----", "");

    byte[] encoded = Base64.decodeBase64(publicKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
    return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}

Как мы видим, сначала нам нужно удалить верхний и нижний колонтитулы, а также новые строки. Затем нам нужно декодировать строку в кодировке Base64 в соответствующий двоичный формат.

Затем нам нужно загрузить результат в класс спецификации ключа, способный обрабатывать материал открытого ключа. В нашем случае мы будем использовать класс X509EncodedKeySpec .

Наконец, мы можем сгенерировать объект открытого ключа из спецификации, используя класс KeyFactory .

3.3. Получить Закрытый Ключ Из Строки PEM

Теперь, когда мы знаем, как читать открытый ключ, алгоритм чтения закрытого ключа очень похож.

Мы собираемся использовать закрытый ключ, закодированный PEM, в формате PKCS8. Давайте посмотрим, как выглядят верхний и нижний колонтитулы:

-----BEGIN PRIVATE KEY-----
...Base64 encoded key...
-----END PRIVATE KEY-----

Как мы узнали ранее, нам нужен класс, способный обрабатывать ключевой материал PKCS8. Класс PKCS8EncodedKeySpec выполняет эту роль.

Итак, давайте посмотрим на алгоритм:

public RSAPrivateKey readPrivateKey(File file) throws Exception {
    String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

    String privateKeyPEM = key
      .replace("-----BEGIN PRIVATE KEY-----", "")
      .replaceAll(System.lineSeparator(), "")
      .replace("-----END PRIVATE KEY-----", "");

    byte[] encoded = Base64.decodeBase64(privateKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
    return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}

4. Использование библиотеки BouncyCastle

4.1. Чтение Открытого Ключа

Мы собираемся изучить библиотеку BouncyCastle и посмотреть, как ее можно использовать в качестве альтернативы чистой реализации Java.

Давайте получим открытый ключ:

public RSAPublicKey readPublicKey(File file) throws Exception {
    KeyFactory factory = KeyFactory.getInstance("RSA");

    try (FileReader keyReader = new FileReader(file);
      PemReader pemReader = new PemReader(keyReader)) {

        PemObject pemObject = pemReader.readPemObject();
        byte[] content = pemObject.getContent();
        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);
        return (RSAPublicKey) factory.generatePublic(pubKeySpec);
    }
}

Есть несколько важных классов, о которых мы должны знать при использовании BouncyCastle:

  • PEMReader – принимает Reader в качестве параметра и анализирует его содержимое. Он удаляет ненужные заголовки и декодирует базовые данные Base64 PEM в двоичный формат.
  • PemObject хранит результат, сгенерированный PEMReader .

Кроме того, давайте рассмотрим другой подход, который обертывает классы Java ( X509EncodedKeySpec, фабрика ключей ) в собственный класс BouncyCastle ( JcaPEMKeyConverter ):

public RSAPublicKey readPublicKeySecondApproach(File file) throws IOException {
    try (FileReader keyReader = new FileReader(file)) {
        PEMParser pemParser = new PEMParser(keyReader);
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemParser.readObject());
        return (RSAPublicKey) converter.getPublicKey(publicKeyInfo);
    }
}

4.2. Чтение Закрытого ключа

Мы увидим два примера, которые очень похожи на те, что были показаны выше.

В первом примере нам просто нужно заменить класс X509EncodedKeySpec классом PKCS8EncodedKeySpec и вернуть объект RSAPrivateKey вместо объекта RSAPublicKey :

public RSAPrivateKey readPrivateKey(File file) throws Exception {
    KeyFactory factory = KeyFactory.getInstance("RSA");

    try (FileReader keyReader = new FileReader(file);
      PemReader pemReader = new PemReader(keyReader)) {

        PemObject pemObject = pemReader.readPemObject();
        byte[] content = pemObject.getContent();
        PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
        return (RSAPrivateKey) factory.generatePrivate(privKeySpec);
    }
}

Теперь давайте немного переработаем второй подход из предыдущего раздела, чтобы прочитать закрытый ключ:

public RSAPrivateKey readPrivateKeySecondApproach(File file) throws IOException {
    try (FileReader keyReader = new FileReader(file)) {

        PEMParser pemParser = new PEMParser(keyReader);
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(pemParser.readObject());

        return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo);
    }
}

Как мы видим, мы просто заменили SubjectPublicKeyInfo на PrivateKeyInfo и RSAPublicKey на RSAPrivateKey .

4.3. Преимущества

Есть несколько преимуществ, предоставляемых библиотекой BouncyCastle.

Одним из преимуществ является то, что нам не нужно вручную пропускать или удалять верхний и нижний колонтитулы. Другое дело, что мы не несем ответственности за декодирование Base64 любой. Таким образом, мы можем написать менее подверженный ошибкам код с помощью Bouncy Castle.

Кроме того, библиотека BouncyCastle также поддерживает формат PKCS 1 . Несмотря на то, что PKCS1 также является популярным форматом, используемым для хранения криптографических ключей (только ключей RSA), Java не поддерживает его самостоятельно.

5. Заключение

В этой статье мы узнали, как считывать открытые и закрытые ключи из файлов PEM.

Во-первых, мы изучили несколько ключевых концепций криптографии с открытым ключом. Затем мы увидели, как читать открытые и закрытые ключи с помощью чистой Java.

Наконец, мы изучили библиотеку BouncyCastle и узнали, что это хорошая альтернатива, поскольку она дает несколько преимуществ по сравнению с чистой реализацией Java.

Полный исходный код для обоих подходов Java и Bouncy Castle доступен на GitHub.