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

Руководство по классу шифров

Узнайте, как использовать класс Cipher для выполнения криптографического шифрования и дешифрования данных в Java.

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

1. Обзор

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

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

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

2. Класс шифра

Расширение Java Cryptography Extension (JCE) – это часть архитектуры Java Cryptography Architecture (JCA) , которая предоставляет приложению криптографические шифры для шифрования и дешифрования данных, а также хэширования личных данных.

Класс Cipher , расположенный в пакете javax.crypto , формирует ядро платформы JCE, обеспечивая функциональность для шифрования и дешифрования.

2.1. Создание экземпляра Шифра

Чтобы создать экземпляр объекта Cipher , мы вызываем метод static getInstance , передавая имя запрошенного преобразования . При необходимости может быть указано имя поставщика.

Давайте напишем пример класса, иллюстрирующий создание экземпляра Шифра :

public class Encryptor {

    public byte[] encryptMessage(byte[] message, byte[] keyBytes) 
      throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        //...
    }
}

Преобразование AES/ECB/PKCS5Padding сообщает методу getInstance создать экземпляр объекта Cipher в виде AES шифра с ECB режимом работы и схемой заполнения PKCS5 |.

Мы также можем создать экземпляр объекта Cipher , указав только алгоритм в преобразовании:

Cipher cipher = Cipher.getInstance("AES");

В этом случае Java будет использовать значения по умолчанию для конкретного поставщика для схемы режима и заполнения.

Обратите внимание , что getInstance вызовет исключение NoSuchAlgorithmException , если преобразование null , пустое или в недопустимом формате, или

Он вызовет исключение NoSuchPaddingException , если преобразование содержит неподдерживаемую схему заполнения.

2.2. Безопасность резьбы

Класс Cipher является статусным без какой-либо внутренней синхронизации. На самом деле, такие методы, как init() или update () , изменят внутреннее состояние конкретного экземпляра Шифра .

Поэтому класс Cipher не является потокобезопасным. Таким образом, мы должны создать один Шифр экземпляр для каждого шифрования/дешифрования.

2.3. Ключи

Интерфейс Key представляет ключи для криптографических операций. Ключи-это непрозрачные контейнеры, которые содержат закодированный ключ, формат кодирования ключа и его криптографический алгоритм.

Ключи обычно получают с помощью генераторов ключей , сертификатов или спецификаций ключей с использованием фабрики ключей .

Давайте создадим симметричный Ключ из предоставленных байтов ключа:

SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

2.4. Инициализация шифра

Мы вызываем метод init() для инициализации C шифра объекта с помощью Ключа или Сертификата и opmode , указывающего режим работы шифра.

При желании мы можем передать источник случайности . По умолчанию используется реализация SecureRandom установленного поставщика с наивысшим приоритетом. В противном случае он будет использовать системный источник.

Мы можем дополнительно указать набор параметров, специфичных для алгоритма. Например, мы можем передать IvParameterSpec to укажите вектор инициализации .

Вот доступные режимы работы шифра:

  • ENCRYPT_MODE : инициализация шифр объект в режиме шифрования
  • DECRYPT_MODE : инициализация шифр объект в режиме дешифрования
  • WRAP_MODE : инициализировать шифр объект в обертывание ключа режим
  • UNWRAP_MODE : инициализация шифр объект ключ-разворачивание режим

Давайте инициализируем объект Cipher :

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// ...

Теперь метод init выдает исключение InvalidKeyException , если предоставленный ключ не подходит для инициализации шифра, например, когда длина/кодировка ключа недопустима.

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

Давайте рассмотрим пример использования Сертификата :

public byte[] encryptMessage(byte[] message, Certificate certificate) 
  throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
 
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.ENCRYPT_MODE, certificate);
    // ...
}

Объект Cipher получает открытый ключ для шифрования данных из сертификата, вызывая метод getPublicKey .

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

После инициализации объекта Cipher мы вызываем метод doFinal() для выполнения операции шифрования или дешифрования. Этот метод возвращает массив байтов, содержащий зашифрованное или расшифрованное сообщение.

Метод doFinal() также сбрасывает объект Cipher в состояние, в котором он был ранее инициализирован с помощью вызова метода init () , делая объект Cipher доступным для шифрования или дешифрования дополнительных сообщений.

Давайте вызовем doFinal в нашем EncryptMessage методе:

public byte[] encryptMessage(byte[] message, byte[] keyBytes)
  throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, 
    BadPaddingException, IllegalBlockSizeException {
 
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    return cipher.doFinal(message);
}

Чтобы выполнить операцию дешифрования, мы меняем opmode на DECRYPT_MODE :

public byte[] decryptMessage(byte[] encryptedMessage, byte[] keyBytes) 
  throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, 
    BadPaddingException, IllegalBlockSizeException {
 
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    return cipher.doFinal(encryptedMessage);
}

2.6. Поставщики услуг

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

Теперь давайте добавим BouncyCastle в качестве поставщика безопасности. Мы можем добавить поставщика безопасности статически или динамически.

Чтобы добавить BouncyCastle статически, мы изменяем файл java.security , расположенный в папке /jre/lib/security .

Мы добавляем строку в конце списка:

...
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider

При добавлении свойства provider ключ свойства находится в формате security.provider.N , где число N на единицу больше, чем последнее в списке.

Мы также можем добавить поставщика безопасности BouncyCastle динамически без необходимости изменять файл безопасности:

Security.addProvider(new BouncyCastleProvider());

Теперь мы можем указать поставщика во время инициализации шифра:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");

BC указывает Bouncy Castle в качестве поставщика. Мы можем получить список зарегистрированных поставщиков с помощью метода Security.getProviders () .

3. Тестирование шифрования и дешифрования

Давайте напишем пример теста, чтобы проиллюстрировать шифрование и расшифровку сообщений.

В этом тесте мы используем алгоритм шифрования AES со 128-битным ключом и утверждаем, что расшифрованный результат равен исходному тексту сообщения:

@Test
public void whenIsEncryptedAndDecrypted_thenDecryptedEqualsOriginal() 
  throws Exception {
 
    String encryptionKeyString =  "thisisa128bitkey";
    String originalMessage = "This is a secret message";
    byte[] encryptionKeyBytes = encryptionKeyString.getBytes();

    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(encryptionKeyBytes, "AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    byte[] encryptedMessageBytes = cipher.doFinal(message.getBytes());

    cipher.init(Cipher.DECRYPT_MODE, secretKey);

    byte[] decryptedMessageBytes = cipher.doFinal(encryptedMessageBytes);
    assertThat(originalMessage).isEqualTo(new String(decryptedMessageBytes));
}

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

В этой статье мы обсудили класс Cipher и представили примеры использования. Более подробную информацию о классе Cipher и фреймворке JCE можно найти в документации class и Справочном руководстве Архитектура криптографии Java (JCA)|/.

Реализацию всех этих примеров и фрагментов кода можно найти на GitHub . Это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.