1. Введение
RSA, или, другими словами, Ривест-Шамир-Адлеман , представляет собой асимметричный криптографический алгоритм. Он отличается от симметричных алгоритмов, таких как DES или AES , наличием двух ключей. Открытый ключ, которым мы можем поделиться с кем угодно, используется для шифрования данных. И частный, который мы храним только для себя и он используется для расшифровки данных.
В этом руководстве мы узнаем, как генерировать, хранить и использовать ключи RSA в Java.
2. Создайте пару ключей RSA
Прежде чем мы начнем фактическое шифрование, нам нужно сгенерировать нашу пару ключей RSA. Мы можем легко сделать это, используя KeyPairGenerator
из пакета java.security
:
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();
Сгенерированный ключ будет иметь размер 2048 бит.
Далее мы можем извлечь закрытый и открытый ключи:
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
Мы будем использовать открытый ключ для шифрования данных и закрытый для их расшифровки.
3. Хранение ключей в файлах
Хранение пары ключей в памяти не всегда является хорошим вариантом. В основном ключи остаются неизменными в течение длительного времени. В таких случаях их удобнее хранить в файлах.
Чтобы сохранить ключ в файле, мы можем использовать метод getEncoded
, который возвращает содержимое ключа в его основном формате кодировки:
try (FileOutputStream fos = new FileOutputStream("public.key")) {
fos.write(publicKey.getEncoded());
}
Чтобы прочитать ключ из файла, нам сначала нужно загрузить содержимое в виде массива байтов:
File publicKeyFile = new File("public.key");
byte[] publicKeyBytes = Files.readAllBytes(publicKeyFile.toPath());
а затем используйте KeyFactory
для воссоздания фактического экземпляра:
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
keyFactory.generatePublic(publicKeySpec);
Содержимое байта ключа должно быть заключено в класс EncodedKeySpec
. Здесь мы используем X509EncodedKeySpec,
который представляет алгоритм по умолчанию для метода Key::getEncoded
, который мы использовали для сохранения файла.
В этом примере мы сохранили и прочитали только файл открытого ключа. Те же шаги можно использовать для обработки закрытого ключа.
Помните, храните файл с закрытым ключом в максимально возможной безопасности с максимально ограниченным доступом. Несанкционированный доступ может вызвать проблемы с безопасностью.
4. Работа со строками
Теперь давайте посмотрим, как мы можем шифровать и расшифровывать простые строки. Во-первых, нам понадобятся некоторые данные для работы:
String secretMessage = "ForEach secret message";
Во- вторых, нам понадобится объект Cipher
, инициализированный для шифрования открытым ключом, который мы сгенерировали ранее:
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
Подготовив это, мы можем вызвать метод doFinal
для шифрования нашего сообщения. Обратите внимание, что он принимает только аргументы массива байтов, поэтому нам нужно преобразовать нашу строку до:
byte[] secretMessageBytes = secretMessage.getBytes(StandardCharsets.UTF_8);)
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);
Теперь наше сообщение успешно закодировано. Если мы хотим сохранить его в базе данных или отправить через REST API , было бы удобнее закодировать его с помощью алфавита Base64 :
String encodedMessage = Base64.getEncoder().encodeToString(encryptedMessageBytes);
Таким образом, сообщение будет более читабельным и с ним будет легче работать.
Теперь давайте посмотрим, как мы можем расшифровать сообщение в его исходную форму. Для этого нам понадобится еще один экземпляр Cipher .
На этот раз мы инициализируем его режимом расшифровки и закрытым ключом:
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
Мы вызовем шифр, как и раньше, с помощью метода doFinal
:
byte[] decryptedMessageBytes = decryptCipher.doFinal(encryptedMessageBytes);
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);
Наконец, давайте проверим, правильно ли прошел процесс шифрования-дешифрования:
assertEquals(secretMessage, decryptedMessage);
5. Работа с файлами
Также возможно зашифровать целые файлы. В качестве примера создадим временный файл с текстовым содержимым:
Path tempFile = Files.createTempFile("temp", "txt");
Files.writeString(tempFile, "some secret message");
Прежде чем мы начнем шифрование, нам нужно преобразовать его содержимое в массив байтов:
byte[] fileBytes = Files.readAllBytes(tempFile);
Теперь мы можем использовать шифрование:
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedFileBytes = encryptCipher.doFinal(fileBytes);
И, наконец, мы можем перезаписать его новым зашифрованным содержимым:
try (FileOutputStream stream = new FileOutputStream(tempFile.toFile())) {
stream.write(encryptedFileBytes);
}
Процесс расшифровки очень похож. Единственное отличие состоит в том, что шифр инициализируется в режиме расшифровки с закрытым ключом:
byte[] encryptedFileBytes = Files.readAllBytes(tempFile);
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedFileBytes = decryptCipher.doFinal(encryptedFileBytes);
try (FileOutputStream stream = new FileOutputStream(tempFile.toFile())) {
stream.write(decryptedFileBytes);
}
В качестве последнего шага мы можем проверить, соответствует ли содержимое файла исходному значению:
String fileContent = Files.readString(tempFile);
Assertions.assertEquals("some secret message", fileContent);
6. Резюме
В этой статье мы узнали, как создавать ключи RSA в Java и как использовать их для шифрования и расшифровки сообщений и файлов. Как всегда, весь исходный код доступен на GitHub .