- Шифрование и генерация случайных чисел в Android приложениях. Тестовые примеры
- Шифрование данных
- Генерация случайных чисел
- Шифрование данных с помощью алгоритма AES-256 на Java
- Как зашифровать и дешифровать с помощью AES-256 средствами Java
- Реализация AES-256 на Java с помощью фреймворка Spring Security
- AES шифрование и Android клиент
- Немного теории об AES шифровании
- Вектор инициализации
- Математическая основа
- Параметры шифрования
- Ларчик просто открывался
- Результат
Шифрование и генерация случайных чисел в Android приложениях. Тестовые примеры
Шифрование данных
Шифрование имеет важное значение, поскольку позволяет скрыть от посторонних глаз то, что им не следует видеть. Мобильные устройства хранят все больше и больше значимой информации, и защитить ее – прямая обязанность каждого разработчика.
Существует два варианта шифрования данных под Android: с использованием Java Crypto API и OpenSSL API (нативный код). Мы рассмотрим оба.
Java Crypto API
Использовать Java Crypto API под Android очень просто. Сначала вам необходимо сгенерировать ключ шифрования. За это отвечает класс KeyGenerator в пэкедже javax.crypto.
Теперь вы можете использовать сгенерированный ключ для шифрования файлов с данными. Для этого зашифруем блоки байтов по алгоритму AES с помощью javax.crypto.
OpenSSL API
Шифрование данных через OpenSSL под Android требует написания нативного кода С, который доступен в Java через вызовы JNI. Это отнимает больше времени, зато быстродействие в результате будет выше.
Для начала сгенерируем ключ и iv.
Теперь мы можем использовать сгенерированный ключ (cKeyBuffer) для шифрования файла. Инициализируем EVP с помощью вашего ключа и iv. Теперь подаем блоки байтов на вход функции EVP_EncryptUpdate. Последняя порция байтов из вашего файла должна быть скормлена функции EVP_EncryptFinal_ex.
Генерация случайных чисел
Генератор случайных чисел (RNG) – это программа или устройство для производства случайной последовательности чисел на определенном промежутке. RNG является жизненно важным для безопасности приложения. В реальности криптографический протокол может быть очень надежным, но при этом подверженным разнообразным атакам из-за того, что в своей основе использует слабые методы генерации ключа. Для усиления ключа и повышения надежности всей системы в целом может использоваться аппаратная поддержка RNG.
Существует целых 4 способа сгенерировать случайные числа в Android:
- java.util.random
- java.security.SecureRandom
- /dev/urandom
- OpenSSL API
Однако, если вы используете RNG для генерации ключа, защищающего ваши данные, использовать обычный класс Random не рекомендуется, так его легче всего взломать. Остальные 3 метода обеспечивают более надежную защиту.
java.util.random
Использовать Java Random Number API очень просто. Вызов Random.nextInt() возвратит 4-байтное случайное значение (общее количество возможных значений – 2 32 ). Это API вполне годится для случаев, когда не требуется полагаться на действительно случайные числа.
java.security.SecureRandom
SecureRandom похож на java.util.Random в том смысле, что также возвращает 4-байтовое значение. SecureRandom криптографически более надежен, однако разработчики должны ознакомиться с недавней рекомендацией генерировать затравочную величину с помощью /dev/urandom для SecureRandom перед генерацией случайных чисел. В примере ниже /dev/urandom не используется.
/dev/urandom
Во всех операционных системах семейства Linux, включая Android, имеется специальный файл, созданный ядром, с помощью которого можно предоставить случайные числа приложениям. Среди всех 4 способов этот самый медленный, он генерирует криптографически безопасные значения с высокой энтропией путем объединения шумовых величин из различных частей операционной системы (например, драйверов устройств) для RNG. Мы можем получить случайное число непосредственно из ядра, прочитав файл /dev/urandom. /dev/urandom имеет доступ к аппаратному RNG, если таковой имеется.
OpenSSL API
Мы также можем использовать OpenSSL API для получения случайных чисел в нативном коде С. В OpenSSL возможно использование затравочных байт из /dev/urandom для генерации криптографически безопасных случайных чисел. OpenSSL API обратится к аппаратному RNG, если таковой имеется.
Источник
Шифрование данных с помощью алгоритма AES-256 на Java
В данной статье говорится о том, как осуществить шифрование с помощью алгоритма AES-256 на Java.
Как уже говорилось ранее, AES-256 представляет собой алгоритм шифрования с симметричным ключом. Это означает, что для шифрования и дешифрования используется один и тот же криптографический ключ.
Алгоритм используется в военных целях, используется для шифрования передачи сообщений по открытым каналам, для шифрования файлов. Так же с помощью AES-256 злоумышленники шифруют файлы на компьютерах «жертв», используя его в целях вымогательства денег (программы-вымогатели). Расшифровать данные возможно только лишь зная ключ.
Как зашифровать и дешифровать с помощью AES-256 средствами Java
Класс для шифрования и дешифрования потока байт на Java может выглядеть следующим образом:
В конструкторе Aes256Class ( ) инициализируется объект keyGenerator , которому задается размер ключа 256 бит. Далее с помощью функции generateKey() генерируется случайный ключ с заданным размером (256 бит).
В функцию makeAes передается исходный массив байт и режим работы объекта класса Cipher:
Cipher.ENCRYPT_MODE либо Cipher.DECRYPT_MODE.
В классе с функцией main можем вызвать описанные выше методы следующим образом:
Получаем следующий результат:
Объясняю, зачем я зациклил выполнение функции makeAes.
Благодаря шифрованию и дешифрованию одного и того же текста несколько раз, можно увидеть, что шифр получается один и тот же, что не есть хорошо.
Это уже может дать злоумышленнику какую-то информацию. Представьте себе, что ваша система отправляет в канал сообщение, например, «Опасность». И отправляет его 10 раз подряд.
Если каким-то образом сеть будет прослушиваться, перехваченное сообщение будет иметь 10 раз подряд один и тот же шифр.
Для того, чтобы одно и то же сообщение имело разный шифр, можно использовать соль.
Не смотря на то, что AES-256 не поддерживает концепцию соли, ничего не мешает сгенерировать случайный массив байт из, например, 8 элементов (получим 8 байтовую соль) и добавить к сообщению.
При дешифрации сообщения можно просто выбросить эти самые последние 8 байт.
Функция приобретет следующий вид:
Вызываем функцию makeAes в методе main:
В этом примере шифруем и дешифруем 10 раз сообщение «Опасность». Получаем следующий результат:
Как видим, одно и то же сообщение, но шифр разный. Однако здесь есть нюанс, если обратите внимание, то увидите, что первая часть сообщения всегда одна и та же. Это и имелось ввиду, когда говорилось, что AES не поддерживает концепцию соли.
Можете попробовать прикреплять соль к началу сообщения, к середине, или использовать несколько солей. Однако Принцип Керкгоффса подразумевает, что злоумышленнику известен алгоритм шифрования и какую-то закономерность он все же найдет.
Нужна очень «хитрая» реализация алгоритма, задача которой максимально запутать злоумышленника.
Когда я писал статью о реализации данного алгоритма на C# я положился на реализацию AES от Microsoft.
Реализация AES-256 на Java с помощью фреймворка Spring Security
В данной статье приведу реализацию AES с помощью Java фреймворка Spring Security:
Результат работы кода:
Как видите, реализация AES-256 от Spring Security не подводит. Как знать, что это именно AES? В официальной документации есть такое упоминание:
The «standard» encryption method is 256-bit AES (Spring Crypto Module)
P.S. Расшифровать подобные сообщения относительно разумными средствами (взломать шифр), не имея ключа в настоящее время не представляется возможным.
Можете попробовать перебором паролей, если у вас в запасе есть сотни миллиардов лет.
Источник
AES шифрование и Android клиент
Как говорится, ничего не предвещало беды. Мобильный клиент потихоньку пилился, кофе стыл, задачки закрывались одна за другой, пока вдруг внезапно не пришло письмо на корпоративную почту:
Срочно внедряем новый функционал. Все необходимые параметры для построения бизнес модели, в целях безопасности, будут передаваться в зашифрованном виде AES/CBC/PKCS5Padding с вектором инициализации AAACCCDDDYYUURRS и ключом шифрования ZZHHYYTTUUHHGGRR. Пример зашифрованных данных:
p+oJjsGEULNSptP5Sj1BM5w65hMjkqzahORd8ybIkqyJD0V/608c1tYuKIvDLUIa
RQ9jQ6+EwbyMFjlMa6xuEnxOx4sez001hd3NsLO7p00XoTqAvi9zwUBII+
nPphP6Zr0P4icvODpmhlmRILgSBsUf1H/3VN1lNXjo4LTa
GxLqW3VSg9iV9yFq4VMWqsRF
Попытки быстрого поиска решения выдали кучу неработающих примеров показали, что задача выходит за рамки привычной верстки layout’ов и написания Presenter’ов и требует изучения доков и чтения мануалов. Отличная возможность изучить что-то новое и обогатить свой опыт.
Но для начала, давайте разберемся, что же это такое — шифрование и зачем оно вообще нужно.
Немного теории об AES шифровании
Advanced Encryption Standard (AES) — симметричный алгоритм блочного шифрования, принятый правительством США на основе результатов проведенного конкурса в качестве стандарта шифрования и заменивший собой менее надежный алгоритм Data Encryption Standard (DES). Утвержденный алгоритм в качестве единого стандарта шифрования стал повсеместно применяться для защиты электронных данных.
Основу алгоритма составляют замены, подстановки и линейные преобразования, каждое из которых выполняется блоками по 128 бит (цифры со значениями от 0 или 1), являющихся основой структуры входных и выходных данных, поэтому он и носит называние блочного шифра. Повторение операций происходит неоднократно и в процессе каждой итерации (раунда) вычисляется уникальный ключ на основе ключа шифрования и встраивается в дальнейшие вычисления.
Криптографический ключ для алгоритма AES представляет собой последовательность из 128, 192 или 256 бит. Другие параметры входных и выходных данных и криптографического ключа не допускается стандартом AES.
Надежность шифрования обеспечивается тем, что изменение даже одного блока влечет за собой изменение последующих блоков и полное изменение конечных данных на выходе.
Данный подход обеспечивает высокую надежность алгоритма, в которой можно убедиться, рассмотрев следующий несложный пример:
Таблица 1: Зависимость количества комбинаций от длины ключа
Размер ключа | Возможное количество комбинаций |
1 бит | 2 |
2 бита | 4 |
4 бита | 16 |
8 бит | 256 |
16 бит | 65536 |
32 бита | 4.2 * 10^9 |
56 бит (DES Алгоритм) | 7.2 * 10^16 |
64 бита | 1.8 * 10^19 |
128 бит (AES алогритм) | 3.4 * 10^38 |
192 бита (AES алогритм) | 6.2 * 10^57 |
256 бит (AES алогритм) | 1.1 * 10^77 |
Самый быстрый суперкомпьютер: 10,51 ПетаФлопс = 10,51 х 10^15 Флопс (операций с плавающей точкой в секунду)
Пусть примерное количество операций в секунду, необходимых для проверки комбинации оптимистично будет: 1000
Количество проверок комбинации в секунду = (10,51 х 10^15) / 1000 = 10,51 х 10^12
Количество секунд в течение одного года = 365 х 24 х 60 х 60 = 31536000
Количество лет, чтобы взломать AES с 128-битным ключом = (3,4 х 10^38) / [(10,51 х 1012) х 31536000] = (0,323 х 10^26) / 31536000 = 1,02 х 10^18 = 1 миллиард миллиардов лет.
Подробное описание алгоритма на английском языке: ADVANCED ENCRYPTION STANDARD
Также можно почитать эту замечательную статью: Как устроен AES
Вектор инициализации
Initialization vector (IV) — вектор инициализации, представляет собой произвольное число, которое может быть использовано вместе с секретным ключом для шифрования данных.
Использование IV предотвращает повторение шифрования данных, что делает процесс взлома более трудным для хакера с помощью атаки по словарю, в попытках найти шаблоны и сломать шифр. Например, последовательность может появиться два раза и более в теле сообщения. Если повторяются последовательности в зашифрованных данных, злоумышленник может предположить, что соответствующие последовательности в сообщении также были идентичны. IV предотвращает появление соответствующих повторяющихся последовательностей символов в зашифрованном тексте.
Математическая основа
Для вспоминания изучения математической основы, воспользуемся материалом из документации к алгоритму ADVANCED ENCRYPTION STANDARD, а также этим хорошим материалом на русском языке: Общее описание криптоалгоритма AES
Соответственно, для описания алгоритма используется конечное поле Галуа GF(2^8), построенное как расширение поля GF(2) = <0,1>по модулю неприводимого многочлена m(x) = x^8 + x^4 + x^3 + x + 1. Элементами поля GF(2^8) являются многочлены вида
b_7 · x^7 + b_6 · x^6 + b_5 · x^5 + b_4 · x^4 + b_3 · x^3 + b_2 · x^2 + b_1 · x + b_0
Операции в поле выполняются по модулю m(x). Всего в поле GF(2^8) насчитывается 2^8 = 256 многочленов.
Основные математические операции в поле GF(2^8)
- Сложение байт можно выполнить любым из трех способов:
- представить байты битовыми многочленами и сложить их по обычному правилу суммирования многочленов с последующим приведением коэффициентов суммы по модулю 2 (операция XOR над коэффициентами);
- суммировать по модулю 2 соответствующие биты в байтах;
- сложить байты в шестнадцатеричной системе исчисления.
- Умножение байт выполняется с помощью представления их
многочленами и перемножения по обычным алгебраическим правилам.
Полученное произведение необходимо привести по модулю многочлена m(x) = x^8 + x^4 + x^3 + x + 1 (результат приведения равен остатку от деления произведения на m(x)) - Для любого ненулевого битового многочлена b(x) в поле GF(2^8)
существует многочлен b^-1(x), обратный к нему по
умножению, т.е. b(x) · b^-1(x) = 1 mod m(x)
Многочлены с коэффициентами, принадлежащими полю GF(2^8)
Многочлены третьей степени с коэффициентами из конечного
поля a_i ∈ GF(2^8) имеют вид: a(x) = a_3 · x^3 + a_2 · x^2 + a_1 · x + a_0 (1)
Таким образом, в этих многочленах в роли коэффициентов при неизвестных задействованы байты вместо бит. Далее эти многочлены будем представлять в форме слова [a_0, a_1, a_2, a_3]. В стандарте AES при умножении многочленов вида (1) используется приведение по модулю другого многочлена x^4 + 1.
Для изучения арифметики рассматриваемых многочленов введем дополнительно многочлен b(x) = b_3 · x^3 + b_2 · x^2 + b_1 · x + b_0, где b_i ∈ GF(2^8). Тогда
- Сложение
a(x) + b(x) = (a_3 ⊕ b_3) x^3 + (a_2 ⊕ b_2) x^2 + (a_1 ⊕ b_1) x + (a_0 ⊕ b_0) - Умножение
Для представления результата четырехбайтовым словом, берется результат по модулю многочлена степени не более 4. Авторы шифра выбрали для этой цели многочлен x^4+1, для которого справедливо x_i mod(x^4 + 1) ≡ x_i mod 4. Дальнейшее приведение по модулю x^4+1 позволяет получить результат в виде:
d(x) = a(x) · b(x) = d_3 · x^3 + d_2 · x^2 + d_1 · x + d_0
Параметры шифрования
Ну что есть AES и вектор инициализации стало понятно. Теперь попытаемся понять и остальные слова в строке AES/CBC/PKCS5Padding
Cipher block chaining (CBC) — режим сцепления блоков шифротекста — один из режимов шифрования для симметричного блочного шифра с использованием механизма обратной связи. Каждый блок открытого текста (кроме первого) побитово складывается по модулю 2 с предыдущим результатом. Одна ошибка в бите блока шифротекста влияет на расшифровку всех последующих блоков. Перестройка порядка блоков зашифрованного текста вызывает повреждения результата дешифрования.
Другой параметр PKCS5Padding, указывает на то, каким образом должны обрабатываться неполные блоки. При использовании одного из общих алгоритмов заполнения, нужно включить размер блока в зашифрованные данные, гарантируя то, что когда вы попытаетесь расшифровать зашифрованное сообщение, вы получите нужное количество байт.
Для работоспособности всех параметров шифрования AES, каждая реализация платформы Java должна поддерживать следующие стандартные алгоритмы шифрования с ключевыми размерами (в скобках):
- AES/CBC/NoPadding (128)
- AES/CBC/PKCS5Padding (128)
- AES/ECB/NoPadding (128)
- AES/ECB/PKCS5Padding (128)
- DES/CBC/NoPadding (56)
- DES/CBC/PKCS5Padding (56)
- DES/ECB/NoPadding (56)
- DES/ECB/PKCS5Padding (56)
- DESede/CBC/NoPadding (168)
- DESede/CBC/PKCS5Padding (168)
- DESede/ECB/NoPadding (168)
- DESede/ECB/PKCS5Padding (168)
- RSA/ECB/PKCS1Padding (1024, 2048)
- RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
- RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
Ларчик просто открывался
Разобравшись с теорией, можно приступать к реализации самого алгоритма расшифровки серверного сообщения.
В отличии от стандартного набора JDK, для работы нам потребуется android.util.Base64 для преобразования строки:
Стоит также принимать во внимание, что размер вектора инициализации должен быть 16 байт (128 — бит). Это связано с тем, что стандарт шифрования AES включает в себя три типа блочных шифров: AES — 128, AES — 192 и AES — 256. Каждый из этих шифров имеет 128 — битный размер блока, с размерами ключа 128, 192 и 256 бит соответственно и принимая во внимание то, что для всех типов блочного шифра, вектор инициализации такого же размера, как и размер блока шифра мы получаем, что вектор инициализации всегда имеет 128 — битный размер.
В противном случае, даже если мы попытаемся использовать вектор другого размера, то шифротекст не будет расшифрован и мы получим следующее исключение:
Результат
Как видно из реализации, решение оказалось достаточно простым и тривиальным в контексте задач подобного рода. Но тем не менее, иногда бывает очень полезно покопаться в доках и реализовать то, что встречается не так уж и часто в трудовых буднях Android разработчика.
Для самых любознательных — под спойлером то, что было зашифровано в сообщении:
Источник