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

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

1. обзор

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

2. Написание теста первым

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

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

Во-первых,we’ll encrypt the content using a newly generated secret key (в этом примере мы используем AES,Advanced Encryption Standard в качестве алгоритма симметричного шифрования).

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

Предположим, чтоour FileEncrypterDecrypter class will write the output to a file called baz.enc. После этогоwe decrypt this file using the same secret key и убедитесь, что расшифрованный контент равен исходному:

@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, используя указанное преобразованиеString.

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

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

Тогда мы можемuse the instantiated cipher and the provided secret key to perform the encryption:

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 позволяет намleverage the convenient CipherOutputStream class for writing the encrypted content into another OutputStream.

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

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

4. Дешифрирование

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

Снова мы можем использовать специальный класс Java,CipherInputStream, which transparently takes care of the actual decryption:

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 repository.

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

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