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

Шифрование и дешифрование файлов в Java

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

1. Обзор

В этом руководстве мы рассмотрим, как зашифровать и расшифровать файл с помощью существующих API-интерфейсов JDK.

2. Сначала написание теста

Мы начнем с написания нашего теста в стиле TDD. Поскольку здесь мы будем работать с файлами, интеграционный тест кажется подходящим.

Поскольку мы просто используем существующую функциональность JDK, внешние зависимости не нужны.

Во- первых, мы зашифруем содержимое с помощью вновь сгенерированного секретного ключа (в этом примере мы используем AES, Advanced Encryption Standard , в качестве алгоритма симметричного шифрования).

Также обратите внимание, что мы определяем полную строку преобразования в конструкторе ( AES/CBC/PKCS5Padding ), которая представляет собой конкатенацию используемого шифрования, режима блочного шифрования и заполнения ( алгоритм/режим/заполнение ). Реализации JDK по умолчанию поддерживают ряд различных преобразований, но обратите внимание, что не каждая комбинация может считаться криптографически безопасной по современным стандартам.

Предположим, что наш класс FileEncrypterDecrypter будет записывать вывод в файл с именем baz.enc . После этого мы расшифровываем этот файл, используя тот же секретный ключ , и проверяем, что расшифрованное содержимое совпадает с исходным содержимым:

@Test
public void whenEncryptingIntoFile_andDecryptingFileAgain_thenOriginalStringIsReturned() {
String originalContent = "foobar";
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();

FileEncrypterDecrypter fileEncrypterDecrypter
= new FileEncrypterDecrypter(secretKey, "AES/CBC/PKCS5Padding");
fileEncrypterDecrypter.encrypt(originalContent, "baz.enc");

String decryptedContent = fileEncrypterDecrypter.decrypt("baz.enc");
assertThat(decryptedContent, is(originalContent));

new File("baz.enc").delete(); // cleanup
}

3. Шифрование

Мы инициализируем шифр в конструкторе нашего класса FileEncrypterDecrypter , используя указанную строку преобразования.

Это позволяет нам ошибиться раньше, если было указано неправильное преобразование:

FileEncrypterDecrypter(SecretKey secretKey, String transformation) {
this.secretKey = secretKey;
this.cipher = Cipher.getInstance(transformation);
}

Затем мы можем использовать созданный шифр и предоставленный секретный ключ для выполнения шифрования:

void encrypt(String content, String fileName) {
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] iv = cipher.getIV();

try (FileOutputStream fileOut = new FileOutputStream(fileName);
CipherOutputStream cipherOut = new CipherOutputStream(fileOut, cipher)) {
fileOut.write(iv);
cipherOut.write(content.getBytes());
}
}

Java позволяет нам использовать удобный класс CipherOutputStream для записи зашифрованного содержимого в другой OutputStream .

Обратите внимание, что мы записываем IV ( вектор инициализации ) в начало выходного файла. В этом примере IV автоматически генерируется при инициализации Cipher .

Использование IV обязательно при использовании режима CBC, чтобы рандомизировать зашифрованный вывод. Однако IV не считается секретом, поэтому можно написать его в начале файла.

4. Расшифровка

Для расшифровки мы также должны сначала прочитать IV. После этого мы можем инициализировать наш шифр и расшифровать содержимое.

Опять же, мы можем использовать специальный класс Java, CipherInputStream , который прозрачно заботится о фактическом расшифровывании :

String decrypt(String fileName) {
String content;

try (FileInputStream fileIn = new FileInputStream(fileName)) {
byte[] fileIv = new byte[16];
fileIn.read(fileIv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(fileIv));

try (
CipherInputStream cipherIn = new CipherInputStream(fileIn, cipher);
InputStreamReader inputReader = new InputStreamReader(cipherIn);
BufferedReader reader = new BufferedReader(inputReader)
) {

StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
content = sb.toString();
}

}
return content;
}

5. Вывод

Мы видели, что можем выполнять базовое шифрование и дешифрование, используя стандартные классы JDK, такие как Cipher , CipherOutputStream и CipherInputStream .

Как обычно, полный код этой статьи доступен в нашем репозитории GitHub .

Кроме того, вы можете найти список шифров, доступных в JDK , здесь .

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