Криптография В Java

Привет, Хабр! Представляю вашему вниманию перевод статьи «Яванская криптография» Якоб Йенков.

Данная публикация представляет собой перевод первой статьи.

Java-криптография часть серии статей для новичков, желающих освоить основы криптографии на Java.



Оглавление:

  1. Java-криптография
  2. Шифр
  3. Дайджест сообщения
  4. Мак
  5. Подпись
  6. Ключевая пара
  7. Генератор ключей
  8. Генератор пары ключей
  9. хранилище ключей
  10. Ключевой инструмент
  11. Сертификат
  12. СертификатЗавод
  13. СертПат
Java-криптография API криптографии Java предоставить возможность шифровать и расшифровывать данные в Java, а также управлять ключами, подписями и аутентифицировать сообщения, вычислять криптографические хеши и многое другое.

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

В этой статье не объясняются основы теории криптографии.

Вам придется поискать эту информацию в другом месте.



Расширение криптографии Java

API криптографии Java предоставляется так называемым расширением Расширение криптографии Java (JCE).

JCE уже давно является частью платформы Java. JCE изначально был отделен от Java из-за ограничений на экспорт технологий шифрования в США.

Поэтому самые сильные алгоритмы шифрования не были включены в стандартную платформу Java. Вы можете использовать более сильные алгоритмы шифрования, если ваша компания находится в США, но в противном случае вам придется использовать более слабые алгоритмы или реализовать свои собственные алгоритмы шифрования и подключить их к JCE. С 2017 года правила экспорта алгоритмов шифрования в США были значительно смягчены, и в большинстве стран мира можно использовать международные стандарты шифрования через Java JCE. Криптографическая архитектура Java Криптографическая архитектура Java (JCA) — это название внутреннего дизайна криптографического API в Java. JCA структурирован вокруг нескольких основных классов и интерфейсов общего назначения.

Фактическая функциональность этих интерфейсов обеспечивается поставщиками.

Таким образом, вы можете использовать класс Cipher для шифрования и дешифрования некоторых данных, но конкретная реализация шифра (алгоритма шифрования) зависит от конкретного используемого поставщика.

Также возможно внедрение и подключение собственных провайдеров, но с этим надо быть осторожным.

Правильно реализовать шифрование без дыр в безопасности сложно! Если вы не знаете, что делаете, вам, вероятно, лучше использовать встроенный поставщик Java или надежный поставщик, такой как Bouncy Castle.

Основные классы и интерфейсы

API криптографии Java состоит из следующих пакетов Java:
  • java.security
  • java.security.cert
  • java.security.spec
  • java.security.interfaces
  • javax.crypto
  • javax.crypto.spec
  • javax.crypto.interfaces
Основные классы и интерфейсы этих пакетов:
  • Поставщик
  • SecureRandom
  • Шифр
  • Дайджест сообщения
  • Подпись
  • Мак
  • Параметры Алгоритма
  • АлгоритмПараметрГенератор
  • KeyFactory
  • Секретный КлючФабрика
  • Генератор пары ключей
  • Генератор ключей
  • Ключевое соглашение
  • хранилище ключей
  • СертификатЗавод
  • СертПатБилдер
  • СертПатВалидатор
  • CertStore


Поставщик

Класс Provider (java.security.Provider) является центральным классом API шифрования Java. Чтобы использовать API шифрования Java, вам необходимо установить поставщика криптографии.

Java SDK поставляется с собственным поставщиком криптографии.

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

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

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

Один из самых популярных поставщиков криптографии для API шифрования Java называется Bouncy Castle. Вот пример, где BouncyCastleProvider установлен в качестве поставщика криптографии:

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class ProviderExample { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); } }



Шифр

Класс Cipher (javax.crypto.Cipher) представляет собой криптографический алгоритм.

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

Класс Cipher более подробно объясняется в следующих разделах с кратким описанием ниже.

Создайте экземпляр класса шифрования, который использует алгоритм шифрования AES для внутреннего использования:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

Метод Cipher.getInstance(.

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

В приведенном выше примере:

  • AES — алгоритм шифрования
  • CBC — это режим, в котором может работать алгоритм AES.
  • PKCS5Padding — это то, как алгоритм AES должен обрабатывать последние байты данных для шифрования.

    Что именно это означает, смотрите в руководстве по криптографии в целом, а не в этой статье.



Инициализация шифрования

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

?Экземпляр шифрования инициализируется путем вызова метода в этом() .

Метод в этом() принимает два параметра:

  • Режим - Шифрование/Дешифрование
  • Ключ
Первый параметр определяет режим работы экземпляра шифра: шифровать или расшифровывать данные.

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

Пример:

byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); cipher.init(Cipher.ENCRYPT_MODE, key);

Обратите внимание, что способ создания ключа в этом примере небезопасен и не должен использоваться на практике.

В следующих разделах этой статьи будет описано, как более безопасно создавать ключи.

Чтобы инициализировать экземпляр шифрования для расшифровки данных, вы должны использовать Cipher.DECRYPT_MODE, например:

cipher.init(Cipher.DECRYPT_MODE, key);



Зашифровать или расшифровать данные

После инициализации шифра вы можете приступить к шифрованию или расшифровке данных, вызвав методы обновлять() или доФинал() .

Метод обновлять() используется, если вы шифруете или расшифровываете часть данных.

Метод доФинал() вызывается, когда вы шифруете последний фрагмент данных или если блок данных, который вы передаете доФинал() , представляет собой единый набор данных, подлежащих шифрованию.

Пример шифрования данных методом доФинал() :

byte[] plainText = "abcdefghijklmnopqrstuvwxyz".

getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText);

Чтобы расшифровать данные, вам необходимо передать зашифрованный текст (данные) методу доФинал() или doUpdate() .



Ключи

Чтобы зашифровать или расшифровать данные, вам нужен ключ.

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

  • Симметричные клавиши
  • Асимметричные клавиши
Симметричные ключи используются для симметричных алгоритмов шифрования.

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

Асимметричные ключи используются для асимметричных алгоритмов шифрования.

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

Алгоритмы шифрования с открытым и закрытым ключами являются примерами алгоритмов асимметричного шифрования.

Каким-то образом сторона, которой необходимо расшифровать данные, должна знать ключ, необходимый для расшифровки данных.

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

Это называется обменом ключами.



Ключ безопасности

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

В примере из предыдущего раздела, посвященном классу Cipher, использовался очень простой, жестко запрограммированный ключ.

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

Важно сделать ключ, который сложно угадать.

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

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



Генерация ключей

Для генерации случайных ключей шифрования вы можете использовать класс Java KeyGenerator. Подробнее KeyGenerator будет описан в следующих главах, вот небольшой пример его использования здесь:

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = new SecureRandom(); int keyBitSize = 256; keyGenerator.init(keyBitSize, secureRandom); SecretKey secretKey = keyGenerator.generateKey();

Полученный экземпляр SecretKey можно передать методу Шифр.

инит() , например так:

cipher.init(Cipher.ENCRYPT_MODE, secretKey);



Генерация пары ключей

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

Чтобы создать пару асимметричных ключей, вы можете использовать KeyPairGenerator (java.security.KeyPairGenerator).

KeyPairGenerator будет описан более подробно в следующих главах, ниже приведен простой пример использования Java KeyPairGenerator:

SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair();



Хранилище ключей

Java KeyStore — это база данных, которая может содержать ключи.

Java KeyStore представлен классом KeyStore (java.security.KeyStore).

Хранилище ключей может содержать следующие типы ключей:

  • Закрытые ключи
  • Открытые ключи + сертификаты
  • Секретные ключи
Частные и открытые ключи используются в асимметричном шифровании.

Открытый ключ может иметь связанный сертификат. Сертификат — это документ, удостоверяющий личность человека, организации или устройства, утверждающего, что он владеет открытым ключом.

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

Секретные ключи используются в симметричном шифровании.

Класс KeyStore довольно сложен, поэтому более подробно он описан позже в отдельной главе, посвященной Java KeyStore.

Инструмент управления ключами (Keytool)

Java Keytool — это инструмент командной строки, который может работать с файлами Java KeyStore. Keytool может генерировать пары ключей в файл KeyStore, экспортировать и импортировать сертификаты в KeyStore и выполнять некоторые другие функции.

Keytool поставляется с установленной Java. Keytool описывается более подробно позже в отдельной главе, посвященной Java Keytool.

Дайджест сообщения

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

Дайджест сообщения — это хеш-значение, рассчитанное на основе данных сообщения.

Если хотя бы один байт в зашифрованных данных изменится, дайджест сообщения, рассчитанный на основе данных, также изменится.

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

Если два дайджеста сообщения одинаковы, существует высокая вероятность (но не 100%) того, что данные не были изменены.

Java MessageDigest (java.security.MessageDigest) можно использовать для расчета дайджестов сообщений.

Чтобы создать экземпляр MessageDigest, вызывается метод MessageDigest.getInstance() .

Существует несколько различных алгоритмов дайджеста сообщений.

Вам необходимо указать, какой алгоритм вы хотите использовать при создании экземпляра MessageDigest. Подробнее работа с MessageDigest будет описана в главе, посвященной Java MessageDigest.

Краткое введение в класс MessageDigest:



MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");

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

Чтобы вычислить дайджест сообщения некоторых данных, вы вызываете метод обновлять() или дайджест() .

Метод обновлять() может вызываться несколько раз, и дайджест сообщения обновляется внутри объекта.

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

Пример звонка обновлять() несколько раз, после чего последовал звонок дайджест() :

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".

getBytes("UTF-8"); byte[] data2 = "abcdefghijklmnopqrstuvxyz".

getBytes("UTF-8"); messageDigest.update(data1); messageDigest.update(data2); byte[] digest = messageDigest.digest();

Вы также можете позвонить дайджест() один раз, передавая все данные для расчета дайджеста сообщения.

Пример:

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".

getBytes("UTF-8"); byte[] digest = messageDigest.digest(data1);



Код аутентификации сообщения (MAC)

Класс Java Mac используется для генерации MAC (кода аутентификации сообщения) из сообщения.

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

Только имея как исходные данные, так и ключ, вы сможете проверить MAC. Таким образом, MAC является более безопасным способом защиты блока данных от модификации, чем дайджест сообщения.

Класс Mac описан более подробно в главе Java Mac с кратким введением ниже.

?Экземпляр Java Mac создается путем вызова метода Mac.getInstance() , передавая имя алгоритма, используемого в качестве параметра.

Вот как это выглядит:

Mac mac = Mac.getInstance("HmacSHA256");

Прежде чем вы сможете создать MAC из данных, вы должны инициализировать экземпляр Mac с помощью ключа.

Вот пример инициализации экземпляра Mac с помощью ключа:

byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8 ,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); mac.init(key);

После инициализации экземпляра Mac вы можете вычислить MAC на основе данных, вызвав методы обновлять() И доФинал() .

Если у вас есть все данные для расчета МАК, вы можете сразу вызвать метод доФинал() .

Вот как это выглядит:

byte[] data = "abcdefghijklmnopqrstuvxyz".

getBytes("UTF-8"); byte[] data2 = "0123456789".

getBytes("UTF-8"); mac.update(data); mac.update(data2); byte[] macBytes = mac.doFinal();



Подпись

Класс Signature (java.security.Signature) используется для цифровой подписи данных.

Когда данные подписываются, на основе этих данных создается цифровая подпись.

Таким образом подпись отделяется от данных.

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

Дайджест зашифрованного сообщения называется цифровой подписью.

Чтобы создать экземпляр Signature, вызывается метод Подпись.

getInstance(.

) :

Signature signature = Signature.getInstance("SHA256WithDSA");



Подпись данных

Чтобы подписать данные, вы должны инициализировать экземпляр подписи в режиме подписи, вызвав метод initSign(.

) и передав закрытый ключ для подписи данных.

Пример инициализации экземпляра подписи в режиме подписи:

signature.initSign(keyPair.getPrivate(), secureRandom);

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

Это делается путем вызова метода update(), передавая данные подписи в качестве параметра.

Вы можете вызвать метод update() несколько раз, чтобы завершить данные для создания подписи.

После передачи всех данных в метод update() вызывается метод Sign() для получения цифровой подписи.

Вот как это выглядит:

byte[] data = "abcdefghijklmnopqrstuvxyz".

getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign();



Проверка подписи

Для проверки подписи необходимо инициализировать экземпляр подписи в режиме проверки, вызвав метод initVerify(.

) , передав в качестве параметра открытый ключ, используемый для проверки подписи.

Пример инициализации экземпляра подписи в режиме проверки выглядит следующим образом:

Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initVerify(keyPair.getPublic());

После инициализации в режиме проверки в метод обновлять() передаются данные, подписанные подписью.

Вызов метода проверять() , возвращает значение истинный или ЛОЖЬ в зависимости от того, можно ли проверить подпись или нет. Вот как выглядит проверка подписи:

byte[] data2 = "abcdefghijklmnopqrstuvxyz".

getBytes("UTF-8"); signature2.update(data2); boolean verified = signature2.verify(digitalSignature);



Полный пример подписи и проверки



SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initSign(keyPair.getPrivate(), secureRandom); byte[] data = "abcdefghijklmnopqrstuvxyz".

getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); Signature signature2 = Signature.getInstance("SHA256WithDSA"); signature2.initVerify(keyPair.getPublic()); signature2.update(data); boolean verified = signature2.verify(digitalSignature); System.out.println("verified = " + verified);

Теги: #программирование #java #криптография #BouncyCastle #jca #jce #jce #java crypto api #java crypto api #java crypto api

Вместе с данным постом часто просматривают: