Рубрики
Без рубрики

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

Узнайте, как реализовать шифрование и расшифровку AES с помощью архитектуры криптографии Java.

Автор оригинала: baeldung.

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

1. Обзор

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

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

2. Алгоритм AES

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

Алгоритм высокого уровня AES

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

3. Вариации AES

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

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

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

3.1. ЕЦБ

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

3.2. CBC

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

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

3.3. CFB

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

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

3.4. OFB

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

Он не требует данных обивки и не будет зависеть от шумного блока.

3.5. CTR

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

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

3.6. ГКМ

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

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

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

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

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

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

4. Параметры AES

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

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

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

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

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

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

Для создания секретного ключа мы можем использовать Ключевой генерал класс. Давайте определим метод генерации ключа 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 является псевдо-случайным значением и имеет тот же размер, что и зашифрованный блок. Мы можем использовать БезопасныйРандом класса для создания случайного IV.

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

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

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

5.1. Струна

Для реализации шифрования строки ввода нам сначала необходимо создать секретный ключ и IV в соответствии с предыдущим разделом. В качестве следующего шага мы создаем экземпляр из Шифровая класса с помощью 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 = "baeldung";
    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. Шаги те же, но нам нужно некоторое IO классы для работы с файлами. Давайте зашифруем текстовый файл:

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 как мы видели раньше.

Опять же, давайте определим метод тестирования для шифрования и расшифровки текстового файла. В этом методе мы читаем Baeldung.txt файл из каталога тестовых ресурсов, шифровать его в файл под названием baeldung.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/baeldung.txt");
    File inputFile = resource.getFile();
    File encryptedFile = new File("classpath:baeldung.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.baeldung.com";
    String password = "baeldung";
    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-объекта необходимо использовать ЗапечатанныйОбъект класс. Объект должен быть Серийный . Начнем с определения Студенческие класс:

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

    // standard setters and getters
}

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

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("Baeldung", 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 .