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

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

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

1. Обзор

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

В этом руководстве мы узнаем, как реализовать шифрование и дешифрование AES с использованием архитектуры криптографии Java (JCA) в JDK.

2. Алгоритм AES

Алгоритм AES — это итеративный блочный шифр с симметричным ключом, который поддерживает криптографические ключи (секретные ключи) длиной 128, 192 и 256 бит для шифрования и дешифрования данных блоками по 128 бит . На рисунке ниже показан высокоуровневый алгоритм AES:

./b9fa0cccc77d45cee31c8b7d4aa77620.png

Если данные, которые нужно зашифровать, не соответствуют требованиям к размеру блока в 128 бит, они должны быть дополнены. Заполнение — это процесс заполнения последнего блока до 128 бит.

3. Варианты AES

Алгоритм AES имеет шесть режимов работы:

  1. ECB (Электронная кодовая книга)
  2. CBC (цепочка блоков шифрования)
  3. CFB (обратная связь по шифру)
  4. OFB (Выходная обратная связь)
  5. CTR (счетчик)
  6. GCM (режим Галуа/счетчик)

Мы можем применить режим работы, чтобы усилить эффект алгоритма шифрования. Более того, режим работы может преобразовывать блочный шифр в поточный шифр. Каждый режим имеет свои сильные и слабые стороны. Давайте быстро рассмотрим каждый из них.

3.1. ЕЦБ

Этот режим работы самый простой из всех. Открытый текст разбивается на блоки размером 128 бит. Затем каждый блок шифруется одним и тем же ключом и алгоритмом. Следовательно, он дает тот же результат для одного и того же блока. Это основная слабость этого режима, и его не рекомендуется использовать для шифрования . Это требует заполнения данных.

3.2. Си-Би-Си

Чтобы преодолеть слабость ECB, режим CBC использует вектор инициализации (IV) для усиления шифрования. Во-первых, CBC использует блок открытого текста xor с IV. Затем он шифрует результат в блок зашифрованного текста. В следующем блоке он использует результат шифрования для операции xor с блоком открытого текста до последнего блока.

В этом режиме шифрование нельзя распараллелить, но можно распараллелить расшифровку. Он также требует заполнения данных.

3.3. CFB

Этот режим можно использовать как потоковый шифр. Сначала он шифрует IV, затем выполняет операцию xor с блоком открытого текста для получения зашифрованного текста. Затем CFB шифрует результат шифрования, чтобы исключить открытый текст. Нужна капельница.

В этом режиме можно распараллелить расшифровку, но нельзя распараллелить шифрование.

3.4. ОФБ

Этот режим можно использовать как потоковый шифр. Во-первых, он шифрует IV. Затем он использует результаты шифрования для удаления открытого текста, чтобы получить зашифрованный текст.

Он не требует заполнения данных и не будет затронут зашумленным блоком.

3.5. CTR

Этот режим использует значение счетчика в качестве IV. Он очень похож на OFB, но каждый раз использует счетчик для шифрования вместо IV.

Этот режим имеет две сильные стороны, в том числе распараллеливание шифрования/дешифрования, и шум в одном блоке не влияет на другие блоки.

3.6. ГКМ

Этот режим является расширением режима CTR. GCM привлек значительное внимание и рекомендован NIST. Модель GCM выводит зашифрованный текст и тег аутентификации. Основным преимуществом этого режима по сравнению с другими режимами работы алгоритма является его эффективность.

В этом руководстве мы будем использовать алгоритм AES/CBC/PKCS5Padding , так как он широко используется во многих проектах.

3.7. Размер данных после шифрования

Как упоминалось ранее, AES имеет размер блока 128 бит или 16 байт. AES не изменяет размер, а размер зашифрованного текста равен размеру открытого текста. Кроме того, в режимах ECB и CBC мы должны использовать алгоритм заполнения, такой как PKCS 5. Таким образом, размер данных после шифрования составляет:

ciphertext_size (bytes) = cleartext_size + (16 - (cleartext_size % 16))

Для хранения IV с зашифрованным текстом нам нужно добавить еще 16 байт.

4. Параметры АЭС

В алгоритме AES нам нужны три параметра: входные данные, секретный ключ и IV. IV не используется в режиме ECB.

4.1. Входные данные

Входными данными для AES могут быть строки, файлы, объекты и пароли.

4.2. Секретный ключ

Существует два способа генерации секретного ключа в AES: генерация из случайного числа или получение из заданного пароля.

При первом подходе секретный ключ должен быть сгенерирован с помощью криптографически безопасного (псевдо) генератора случайных чисел, такого как класс SecureRandom .

Для генерации секретного ключа мы можем использовать класс KeyGenerator . Определим метод генерации ключа AES размером n (128, 192 и 256) бит:

public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(n);
SecretKey key = keyGenerator.generateKey();
return key;
}

При втором подходе секретный ключ AES может быть получен из заданного пароля с помощью функции получения ключа на основе пароля, такой как PBKDF2 . Нам также нужно солт-значение для превращения пароля в секретный ключ. Соль также является случайным значением.

Мы можем использовать класс SecretKeyFactory с алгоритмом PBKDF2WithHmacSHA256 для генерации ключа из заданного пароля.

Давайте определим метод генерации ключа AES из заданного пароля с 65 536 итерациями и длиной ключа 256 бит:

public static SecretKey getKeyFromPassword(String password, String salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {

SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return secret;
}

4.3. Вектор инициализации (IV)

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

Давайте определим метод для генерации IV:

public static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}

5. Шифрование и дешифрование

5.1. Нить

Чтобы реализовать шифрование входной строки, нам сначала нужно сгенерировать секретный ключ и IV в соответствии с предыдущим разделом. На следующем этапе мы создаем экземпляр класса Cipher с помощью метода getInstance() .

Кроме того, мы настраиваем экземпляр шифра, используя метод init() с секретным ключом, IV и режимом шифрования. Наконец, мы шифруем входную строку, вызывая метод doFinal() . Этот метод получает байты ввода и возвращает зашифрованный текст в байтах:

public static String encrypt(String algorithm, String input, SecretKey key,
IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {

Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder()
.encodeToString(cipherText);
}

Для расшифровки входной строки мы можем инициализировать наш шифр, используя DECRYPT_MODE для расшифровки содержимого:

public static String decrypt(String algorithm, String cipherText, SecretKey key,
IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {

Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] plainText = cipher.doFinal(Base64.getDecoder()
.decode(cipherText));
return new String(plainText);
}

Давайте напишем тестовый метод для шифрования и дешифрования строкового ввода:

@Test
void givenString_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {

String input = "foreach";
SecretKey key = AESUtil.generateKey(128);
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
String algorithm = "AES/CBC/PKCS5Padding";
String cipherText = AESUtil.encrypt(algorithm, input, key, ivParameterSpec);
String plainText = AESUtil.decrypt(algorithm, cipherText, key, ivParameterSpec);
Assertions.assertEquals(input, plainText);
}

5.2. Файл

Теперь давайте зашифруем файл с помощью алгоритма AES. Шаги те же, но нам нужны некоторые классы ввода -вывода для работы с файлами. Давайте зашифруем текстовый файл:

public static void encryptFile(String algorithm, SecretKey key, IvParameterSpec iv,
File inputFile, File outputFile) throws IOException, NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {

Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
FileInputStream inputStream = new FileInputStream(inputFile);
FileOutputStream outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[64];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byte[] output = cipher.update(buffer, 0, bytesRead);
if (output != null) {
outputStream.write(output);
}
}
byte[] outputBytes = cipher.doFinal();
if (outputBytes != null) {
outputStream.write(outputBytes);
}
inputStream.close();
outputStream.close();
}

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

Для расшифровки файла мы используем аналогичные шаги и инициализируем наш шифр, используя DECRYPT_MODE , как мы видели ранее.

Опять же, давайте определим тестовый метод для шифрования и дешифрования текстового файла. В этом методе мы читаем файл foreach.txt из каталога тестовых ресурсов, шифруем его в файл с именем foreach.encrypted , а затем расшифровываем файл в новый файл:

@Test
void givenFile_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException,
InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException,
NoSuchPaddingException {

SecretKey key = AESUtil.generateKey(128);
String algorithm = "AES/CBC/PKCS5Padding";
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
Resource resource = new ClassPathResource("inputFile/foreach.txt");
File inputFile = resource.getFile();
File encryptedFile = new File("classpath:foreach.encrypted");
File decryptedFile = new File("document.decrypted");
AESUtil.encryptFile(algorithm, key, ivParameterSpec, inputFile, encryptedFile);
AESUtil.decryptFile(
algorithm, key, ivParameterSpec, encryptedFile, decryptedFile);
assertThat(inputFile).hasSameTextualContentAs(decryptedFile);
}

5.3. на основе пароля

Мы можем выполнять шифрование и дешифрование AES, используя секретный ключ, полученный из заданного пароля.

Для генерации секретного ключа мы используем метод getKeyFromPassword() . Шаги шифрования и дешифрования такие же, как показано в разделе ввода строки. Затем мы можем использовать созданный шифр и предоставленный секретный ключ для выполнения шифрования.

Напишем тестовый метод:

@Test
void givenPassword_whenEncrypt_thenSuccess()
throws InvalidKeySpecException, NoSuchAlgorithmException,
IllegalBlockSizeException, InvalidKeyException, BadPaddingException,
InvalidAlgorithmParameterException, NoSuchPaddingException {

String plainText = "www.foreach.com";
String password = "foreach";
String salt = "12345678";
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
SecretKey key = AESUtil.getKeyFromPassword(password,salt);
String cipherText = AESUtil.encryptPasswordBased(plainText, key, ivParameterSpec);
String decryptedCipherText = AESUtil.decryptPasswordBased(
cipherText, key, ivParameterSpec);
Assertions.assertEquals(plainText, decryptedCipherText);
}

5.4. Объект

Для шифрования объекта Java нам нужно использовать класс SealedObject . Объект должен быть Serializable . Начнем с определения класса Student :

public class Student implements Serializable {
private String name;
private int age;

// standard setters and getters
}

Далее давайте зашифруем объект Student :

public static SealedObject encryptObject(String algorithm, Serializable object,
SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException,
InvalidKeyException, IOException, IllegalBlockSizeException {

Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
SealedObject sealedObject = new SealedObject(object, cipher);
return sealedObject;
}

Зашифрованный объект впоследствии можно расшифровать с помощью правильного шифра:

public static Serializable decryptObject(String algorithm, SealedObject sealedObject,
SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
ClassNotFoundException, BadPaddingException, IllegalBlockSizeException,
IOException {

Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
Serializable unsealObject = (Serializable) sealedObject.getObject(cipher);
return unsealObject;
}

Теперь давайте напишем тестовый пример:

@Test
void givenObject_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, IOException,
BadPaddingException, ClassNotFoundException {

Student student = new Student("ForEach", 20);
SecretKey key = AESUtil.generateKey(128);
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
String algorithm = "AES/CBC/PKCS5Padding";
SealedObject sealedObject = AESUtil.encryptObject(
algorithm, student, key, ivParameterSpec);
Student object = (Student) AESUtil.decryptObject(
algorithm, sealedObject, key, ivParameterSpec);
assertThat(student).isEqualToComparingFieldByField(object);
}

6. Заключение

В этой статье мы узнали, как шифровать и расшифровывать входные данные, такие как строки, файлы, объекты и данные на основе пароля, с помощью алгоритма AES в Java. Кроме того, мы обсудили варианты AES и размер данных после шифрования.

Как всегда, полный исходный код статьи доступен на GitHub .