1. Обзор
В этом руководстве мы рассмотрим, как зашифровать и расшифровать файл с помощью существующих API JDK.
2. Сначала напишите тест
Мы начнем с написания нашего теста в стиле TDD. Поскольку мы собираемся работать с файлами здесь, интеграционный тест кажется подходящим.
Поскольку мы просто используем существующую функциональность JDK, никаких внешних зависимостей не требуется.
Во-первых, мы зашифруем содержимое с помощью недавно сгенерированного секретного ключа (мы используем AES, Advanced Encryption Standard , в качестве алгоритма симметричного шифрования в этом примере).
Также обратите внимание, что мы определяем полную строку преобразования в конструкторе ( AES/CBC/PKCS5Padding ), который представляет собой объединение используемого шифрования, режима блочного шифрования и заполнения ( алгоритм/режим/заполнение ). Реализации JDK по умолчанию поддерживают ряд различных преобразований, но обратите внимание, что не каждая комбинация все еще может считаться криптографически безопасной по сегодняшним стандартам.
Мы предположим, что ваш Файловый шифратор-дешифратор класс запишет выходные данные в файл с именем 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. Шифрование
Мы инициализируем шифр в конструкторе нашего класса File Encrypter Decrypter , используя указанное преобразование String.
Это позволяет нам рано завершать работу в случае, если было указано неправильное преобразование:
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 автоматически генерируется при инициализации шифра .
Использование 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 здесь .
Наконец, обратите внимание, что приведенные здесь примеры кода не предназначены для производственного кода, и при их использовании необходимо тщательно учитывать специфику вашей системы.