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!' Полагаю, что данная статья помогла Вам решить задачу симметричного шифрования и дешифрирования. Ну, а вопрос передачи секретного ключа каждый решает самостоятельно. |