Автор оригинала: 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.