Перейти к основному содержимому

RSA в Java

· 4 мин. чтения

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 .