410013796724260
• Webmoney
R335386147728
Z369087728698
Симметричное шифрованиеСимметричные криптосистемы (симметричное шифрование) — это способ шифрования, в котором один и тот же криптографический ключ применяется для шифрования и дешифровывания. При асимметричном шифровании криптографические ключи шифрования и дешифрирования отличаются; шифрование осуществляется с помощью открытого ключа, а дешифрование с помощью закрытого ключа. Определение шифраРассмотрим реализацию симметричного шифрования в java на примере алгоритма AES. В первую очередь нам понадобится определяющий алгоритм шифрования класс javax.crypto.Cipher, реализующий базовые функции популярных криптографических алгоритомов. Для получения экземпляра данного класса используется статистический метод Cipher.getInstance(), которому в качестве параметра передается наименование криптографического алгоритма шифрования. В простейшем случае пример создания экземпляра Java Cipher мог бы выглядеть следующим образом :
Cipher cipher = Cipher.getInstance("AES");
Однако, согласно документации JCA (Java Cryptography Architecture), в разделе "Creating a Cipher Object" указано, что для того, чтобы получить экземпляр Cipher нужно указать не просто алгоритм шифрования, а «трансформацию». Формат описания «трансформации» выглядит следующим образом: "algorithm/mode/padding" :
Параметр «padding» определяет, какой объём данных составляет 1 блок. Каждый блок данных шифруется отдельно. Так, например, padding, равный PKCS5Padding, определяет размер одного блока в 2 байта (16 бит). Режимы шифрованияРежим шифрования определяет детали шифрования данных, которые косвенно влияют на алгоритм шифрования. Режимы шифрования могут использоваться в нескольких различных алгоритмах шифрования как метод, добавляемый к основному алгоритму. Поэтому режимы шифрования рассматриваются не отдельно от самих алгоритмов шифрования, а скорее как их «дополнения». Наиболее известные режимы шифрования :
Отдельные алгоритмы шифрования могут работать в разных режимах. В примере, рассмотренном ниже, при создании экземпляра Cipher будем использовать трансформацию "AES/ECB/PKCS5Padding". То есть, алгоритм шифрования – AES, режим шифрования – ECB, размер блока – 2 байта (PKCS5Padding). Инициализация шифраПрежде чем использовать Cipher для шифрования или дешифрирования текста, его необходимо инициализировать. Инициализация Cipher выполняется вызовом его метода init(int opmode, Key key), принимающего два параметра :
Следующий код формирует экземпляр cipher для шифрования текста. Для дешифрирования текста необходимо определить режим Cipher.DECRYPT_MODE.
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// Экземпляр cipher для шифрования текста
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
Создание секретного ключаРассмотрим метод создания секретного ключа createSecretKey(), листинг которого представлен ниже. В методе сначала формируется массив bytes с использованием генератора псевдослучайныйх значений SecureRandom. После этого для массива bytes создается дайджест сообщения MessageDigest. Размер дайджеста составляет 20 байт, из которых выделяются первые 16 байт методом копирования в новый массив (copyOf) утилитой Arrays. Экземпляр секретного ключа SecretKeySpec создается для алгоритма "AES". Закомментированный в методе код позволяет вывести в консоль сгенерированный массив псевдослучайных значений.
private SecretKeySpec createSecretKey()
{
SecretKeySpec sks = null;
byte[] bytes = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(bytes);
// System.out.println("random : " +
// DatatypeConverter.printHexBinary(bytes));
try {
MessageDigest md;
byte[] key;
md = MessageDigest.getInstance("SHA-1");
key = md.digest(bytes);
key = Arrays.copyOf(key, 16);
sks = new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException e) { }
return sks;
}
Ключ алгоритма симметричного шифрования должен храниться в тайне обеими участниками. Алгоритм шифрования выбирается сторонами до начала обмена криптованными сообщениями. В методе createSecretKey таким ключом является переменная key. Ключ используется для создания SecretKeySpec, который необходим для инициализации Cipher. Шифрование и дешифрированиеДля шифрования и дешифрования данных с помощью экземпляра Cipher, используется один из методов update() или doFinal(). Класс Cipher имеет несколько переопределенных методов update() и doFinal(), которые принимают разные параметры. Следующий пример демонстрирует использование метода doFinal() для шифрования текста :
byte[] plainText = "Hello, World!".getBytes("UTF-8");
byte[] cipherText = cipher.doFinal(plainText);
System.out.println("encrypted text : " +
DatatypeConverter.printHexBinary(cipherText));
Для дешифрирования текст будет выглядеть примерно также, за исключением того, что экземпляр Cipher должен быть инициализирован соответствующим образом : byte[] plainText = cipher.doFinal(cipherText); Листинг примера CryptoExampleВ примере CryptoExample представлены 2 метода : encrypt и decrypt. Исходный код метода createSecretKey рассмотрен выше. Алгоритм функционирования данных методов понятен из представленного выше описания. Дополнительно в методах encrypt и decrypt представлен код шифрования и дешифрирования с использованием утилиты java.util.Base64, которая появилась в Java 8. Base64 — стандарт кодирования двоичных данных при помощи только 64 символов ASCII. Алфавит кодирования содержит текстово-цифровые латинские символы A-Z, a-z и 0-9 (62 знака) и 2 дополнительных символа, зависящих от системы реализации. Т.е. весь диапазон закодированных символов укладывается в английский алфавит, цифры и двух спецсимволов. Таким образом, из каждых 3 исходных байтов утилита Base64 формирует 4 символа, увеличивая размер текста на ⅓. В примере для криптования использован текст «decrypt!decrypt!decrypt!decrypt!», представляющий 4 повторяющихся размером 8 символов/байт слов «decrypt!». Т.е., кодируемый текст из 32 байт включает 2 повторяющихся выражения «decrypt!decrypt!» по 16 байт. А, как мы помним, режим PKCS5Padding определяет размер кодируемого блока в 2 байта.
public class CryptoExample
{
Cipher cipher;
String transformation = "AES/ECB/PKCS5Padding";
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public CryptoExample() {}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public byte[] encrypt(SecretKeySpec secretKey,
byte[] plainText) {
try {
cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
// return Base64.getEncoder()
// .encode(cipher.doFinal(plainText));
} catch (Exception e) { }
return null;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public byte[] decrypt(SecretKeySpec secretKey,
byte[] encryptedText) {
try {
cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(encryptedText);
// return cipher.doFinal(Base64.getDecoder()
// .decode(encryptedText));
} catch (Exception e) {}
return null;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private SecretKeySpec createSecretKey() {
// исходный код представлен выше
. . .
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public static void main(String[] args) throws Exception
{
CryptoExample cryptoEx = new CryptoExample();
SecretKeySpec key = cryptoEx.createSecretKey();
String text = "decrypt!decrypt!decrypt!decrypt!";
byte[] enc = cryptoEx.encrypt(key, text.getBytes());
System.out.println("Original text: '" + text + "'");
System.out.println("Encrypted text :\n" +
DatatypeConverter.printHexBinary(enc));
byte[] bytes = cryptoEx.decrypt(key, enc);
if (bytes != null) {
String plainAfter = new String(bytes);
System.out.println("Text after decryption: '" +
plainAfter + "'");
}
}
}
Ну, и теперь смотрим на результат выполнения примера. В консоль были выведены следующие сообщения : random : 992DE79A44047FCD641EC2F9F1A2056D Original text: 'decrypt!decrypt!decrypt!decrypt!' Encrypted text : CF2949AFAA65F0E2E8926364D56FBBFA \ CF2949AFAA65F0E2E8926364D56FBBFA \ 6C1647F049941345172F0083EF997201 Text after decryption: 'decrypt!decrypt!decrypt!decrypt!' Кодированный текст (Encrypted text) для наглядности, разделен на 3 части. После первых 2-х частей из 32 символов вставлены символы пробела и переносом '\', далее остальная часть кодированного текста. В принципе, кодированный текст был восстановлен правильно. Но вот сам кодированный текст имеет 2 повторения (блоков также 2; остался неприятный осадок). Как это можно исправить? Можно использовать утилиту Base64, которая преобразует исходный текст и повторений не будет. Для этого можно снять комментарии в методах encrypt, decrypt. Но мы должны быть уверены, что у пользователя используется Java 8. Проще изменить режим шифрования – CBC (Cipher Block Chaining). Режим шифрования CBC вводит понятие Initialization Vector, представленный классом IvParameterSpec. В данном режиме результат генерации предыдущего блока используется для генерации следующего. Но чтобы использовать режим шифрования CBC требуется несложная доработка кода, связанная с инициализацией шифра Cipher. Для этого определяем массив из 16 байт sec_bytes, которые будем заполнять псевдослучайными значениями в конструкторе класса. При нициализации шифра будем использовать этот массив для формирования IvParameterSpec. Ниже представлены изменения кода примера для использования режима CBC.
// String transformation = "AES/ECB/PKCS5Padding";
String transformation = "AES/CBC/PKCS5Padding";
byte[] sec_bytes = new byte[16];
. . .
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public CryptoUtil() {
SecureRandom random = new SecureRandom();
random.nextBytes(sec_bytes);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public byte[] encrypt(SecretKeySpec secretKey,
byte[] plainText) {
try {
cipher = Cipher.getInstance(transformation);
if (transformation.contains("ECB"))
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
else {
IvParameterSpec ivSpec;
ivSpec = new IvParameterSpec(sec_bytes);
cipher.init(Cipher.ENCRYPT_MODE, secretKey,
ivSpec);
}
// return Base64.getEncoder()
// .encode(cipher.doFinal(plainText));
return cipher.doFinal(plainText);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public byte[] decrypt(SecretKeySpec secretKey,
byte[] encryptedText) {
try {
cipher = Cipher.getInstance(transformation);
if (transformation.contains("ECB"))
cipher.init(Cipher.DECRYPT_MODE, secretKey);
else {
IvParameterSpec ivSpec;
ivSpec = new IvParameterSpec(sec_bytes);
cipher.init(Cipher.DECRYPT_MODE, secretKey,
ivSpec);
}
// return cipher.doFinal(Base64.getDecoder()
// .decode(encryptedText));
return cipher.doFinal(encryptedText);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
После доработки повторения в кодированном тексте отсутствуют, результат шифрования и дешифрирования радует. random : 5B1E804043C2068B9602285172800C3E Original text: 'decrypt!decrypt!decrypt!decrypt!' Encrypted text : F0802F50C5E41CFEE3F1F5EA71224771 \ 8F1BCDF65519472EDCD6DE11FB0E14A1 \ D3A493CF59D7F2BB197CA90025E05203 Text after decryption: 'decrypt!decrypt!decrypt!decrypt!' Полагаю, что данная статья помогла Вам решить задачу симметричного шифрования и дешифрирования. Ну, а вопрос передачи секретного ключа каждый решает самостоятельно. |
