1. Обзор
В криптографии с открытым ключом, также известной как асимметричная криптография , механизм шифрования опирается на два связанных ключа: открытый ключ и закрытый ключ. Открытый ключ используется для шифрования сообщения, а расшифровать сообщение может только владелец закрытого ключа.
В этом руководстве мы узнаем, как читать открытые и закрытые ключи из файла PEM.
Сначала мы изучим некоторые важные концепции криптографии с открытым ключом. Затем мы научимся читать файлы PEM, используя чистую Java.
Наконец, мы рассмотрим библиотеку BouncyCastle как альтернативный подход.
2. Концепции
Прежде чем мы начнем, давайте обсудим некоторые ключевые понятия.
X.509 — это стандарт, определяющий формат сертификатов открытых ключей. Таким образом, этот формат описывает открытый ключ, помимо другой информации.
DER — самый популярный формат кодирования для хранения данных, таких как сертификаты X.509 и закрытые ключи PKCS8 в файлах. Это двоичная кодировка, и полученный контент нельзя просмотреть в текстовом редакторе.
PKCS8 — это стандартный синтаксис для хранения информации о секретном ключе. Закрытый ключ может быть дополнительно зашифрован с использованием симметричного алгоритма.
Этот стандарт может обрабатывать не только закрытые ключи RSA, но и другие алгоритмы. Закрытые ключи PKCS8 обычно передаются в формате кодирования PEM.
PEM — это механизм кодирования base-64 сертификата 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, KeyFactory
) в собственный класс 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 . Таким образом, с помощью BouncyCastle мы можем писать менее подверженный ошибкам код.
Более того, библиотека BouncyCastle также поддерживает формат PKCS1 . Несмотря на то, что PKCS1 также является популярным форматом, используемым для хранения криптографических ключей (только ключей RSA), Java не поддерживает его сам по себе.
5. Вывод
В этой статье мы узнали, как читать открытые и закрытые ключи из файлов PEM.
Во-первых, мы изучили несколько ключевых концепций криптографии с открытым ключом. Затем мы увидели, как читать открытые и закрытые ключи с помощью чистой Java.
Наконец, мы изучили библиотеку BouncyCastle и обнаружили, что это хорошая альтернатива, поскольку она дает несколько преимуществ по сравнению с реализацией на чистом Java.
Полный исходный код для подходов Java и BouncyCastle доступен на GitHub.