Внимание! Данный материал предназначен исключительно для образовательных целей в области криптографии. Любое использование описанных техник для создания вредоносного ПО является нарушением законодательства. Автор не несет ответственности за ваши действия.
Приветствую всех пользователей LIMB.cc
В статье я опишу, как работает AES шифрование, разберу его режимы, также напишу собственный криптер, покажу особенности и фичи данного алгоритма.
AES появился около 25 лет назад и до сих пор остается одним из самых лучших алгоритмов мира в плане устойчивости, логики построения математических операций в раундах, размера ключа, устойчивости к криптоанализу и т. д. В общем и целом AES, по моему мнению, считается золотым стандартом для множества целей, где применяется криптография. Статья рассчитана на всех пользователей, попытаюсь понятным языком изложить принцип работы алгоритма и его особенности.
Предисловие.
Advanced Encryption Standard (AES) - Используется повсеместно в нашей жизни, например тот же WI-FI, протокол безопасности WPA2/WPA3 использует AES, для шифрование трафика. Тот же HTTPS, например использует AES-GCM/CCM. Но понятное дело, наибольший интерес представляет популярность данного алгоритма среди Ransomware групп если ранняя версия LockBit(ABCD) использовала AES для шифрования файлов, то и новый LockBit 5.0 не отступил от этой традиции.
Части статьи:
1. Особенности AES/Характеристики
2. Теория
3. Режимы AES
4. Практика/Написание Криптера
5. Декриптор/Расшифровка
Особенности AES/Характеристики.
Ключ шифрования.
1. В алгоритме используется симметричный ключ, это означает что он применяется как при шифрования так и при расшифровки. Должен быть всегда секретным.
2. Длинна ключа зависит от того какой вариант шифрования вы выберите: AES-128, AES-192, AES-256. То есть размер ключа может быть как 128 бит, так и 256 бит.
Варианты алгоритма.
Как я упомянул в пункте выше, у AES есть 3 основных варианта реализации:
1. AES-128, Размер ключа 128 бит, кол-во раундов 10
2. AES-192, Размер ключа 192 бит, кол-во раундов 12
3. AES-256, Размер ключа 256 бит, кол-во раундов 14
Соответственно чем больше ключ и раундов, тем дольше происходит процесс шифрования
Теория.
Для начала давайте представим 1 блок в AES(inital state) как матрицу 4х4 для того что бы можно было понять механизм операций. Размер inital state(блок в AES) - 16 байт.
Сам принцип алгоритма очень прост, прогоняем 128 битный блок через 14 раундов которые состоят из логических операций и получаем чистый зашифрованный блок. Ниже предоставлена схема(на ней вариант AES-128)
Логические операции.
В общем и целом, я имею ввиду про математические преобразования/операции алгоритма, просто сформулировать все аспекты в одном понятии сложно, поэтому пусть пункт будет иметь такое название.
SubBytes.
Простая операция подстановки байтов по таблице S-box
Входной байт меняется на байт из таблицы по X и Y, например у нас есть байт 8B, по таблице он превратится 3D, ниже представлен пример:
ShiftRows.
Тоже элементарная операция в которой строки сдвигаются вперед в той же матрице 4х4
Циклический сдвиг:
1. Строка, остается без изменений
2. Строка, 1 левый байт сдвигается в правую часть
3. Строка, 2 байта с лева сдвигаются в правую часть
4. Строка, 3 байта слева сдвигаются в правую часть
MixColumns.
Вот тут нужно очень точно и аккуратно понять как действует mixcolumns, здесь мы работаем с столбцом, в столбце естественно у нас есть 4 байта, эти 4 байта проходят через умножения и XOR, вот матрица умножений
Естественно при обычном сложении в HEX мы можем получить 2 байта обычным образом, но так как в AES все происходит в поле Галуа, "+" мы заменяем XOR, при таком раскалде мы естественно можем получить только 1 байт тем самым не выйдя за лимит 255, вот более точная формула
Умножение так же происходит в рамках поля Галуа, Пример:
Умножение на 02. Возьмем байт 6D, у него старший бит 0, (01101101), который слева, значит просто сдвигаем на 1 бит влево.
Итого: 6D * 02 = DA(11011010)
Умножение на 02 при условии что старший бит 1. Возьмем к примеру байт D4, старший бит 1, (11010100) опять же сдвигаем его в влево, но теперь у нас выходит за границы, и получается 9 битов, понятно дело что так не пойдет по этому обрезаем последний бит, выходит такой байт A8((-1)10101000), далее нужно сделать редукцию с байтом 1B, что бы не потерять байт у которого вылетел бит, производим обычный XOR: A8 ⊕ 1B = B3.
Итого: D4 * 02 = B3(10110011)
Умножение на 03. Тоже просто действие, Формула такая:
x*03 = (x*02) ⊕ x
Или же с примером
D4*03 = (D4*02) ⊕ D4 = B3 ⊕ D4 = 67
Простыми словами, нам нужно байт перемножить на 02, после чего сделать XOR с неперемноженным тем же байтом.
AddRoundKey.
Одна из самых важных и простых операций которая и делает initial state уникальным под ваш ключ.
XOR вашего блока который прошел через 3 операции, данный этап последний в цикле. Ниже пояснение как создается AddRoundKey.
Закрепим ясность
0 Раунд - XOR 1 чистого initial state с AddRoundKey
1-Nr-1 Раундов все 4 операции - SubBytes -> ShiftRows -> MixColumns -> AddRoundKey
Последний Nr 3 операции - SubBytes -> ShiftRows -> AddRoundKey
:bangbang:Хочу заметить что в финальном раунде MixColumns не используется
Доп материалы:
Режимы AES.
У AES есть довольно множество режимов, которые влияют на его работу. Вообще дело в том что без режима вообще AES не может работать, так как чистый алгоритм может зашифровать только 16 байт, а что бы зашифровать более 1 блока последовательно нужен конечно же режим, в котором и укладывается логика которая сохраняет цепочку и целостность.
Вообще режимов довольно много, но мы отметим 5 основных:
ECB - каждый блок шифруется независимо, самый примитивный режим, плох что одинаковые блоки дают одинаковый шифротекст
CBC - перед шифрованием блока делается XOR с предыдущим шифртекстом (первый блок с IV), за счёт чего пропадают паттерны, но шифрование становится последовательным и требует паддинга
CFB, OFB - делают из блочного шифра потоковый: шифруется предыдущий шифртекст или внутреннее состояние, результат XOR-ится с данными
CTR - самый лучший вариант для скорости, на вход идет nonce и counter, как в ChaCha20, его мы и будем использовать.
Практика.
Будем использовать библиотеку OpenSSL
Через Git клонируем vcpkg
Устанавливаем OpenSSL
Интегрируем в VS
Начнем с того что будем использовать EVP, это высокоуровневый API OpenSSL, от нас не потребуются высокие знания в алгоритме AES, естественно если бы мы писали свою реализацию, то на это ушло бы не мало времени)
Главным нашим преимуществом по скорости является AES-NI, это аппаратное ускорение, это означает что 1 раунд AES физически находится на процессоре, что позволяет достигать невероятной скорости шифрования, вы можете проверить есть ли оно у вас на процессоре через __cpuid, это механизм который запрашивает у процессора набор поддерживаемых возможностей, давайте проверим есть ли у вас поддержка AES-NI
Минимальный код для проверки есть ли у вас AES-NI, дело в том что ECX сообщает нам находится ли аппаратное ускорение на процессоре или нет, через 25 бит
Но нам не понадобится данная функция так как OpenSSL EVP и так умеет определять AES-NI и оптимизироваться под него.
(Источник:https://www.felixcloutier.com/x86/cpuid)
Я приложу код в конце статьи, мы можем шифровать и расшифровывать указанный файл в одной директории, начнем с основной логики
Во первых так как у нас режим ctr, юзаем IV для шифрования, открываем файл, читаем, создаем openssl контекст и ключем и IV, чтение происходит по 8мб, шифрует/дешифрует, после выполненной работы, закрывает контекст и файл
Далее парсер для ключей + IV
Простая проверка длинны, парс hex, что бы ключ не сломался и выводился в консоль нормально
Это были 3 основные функции, далее все соединяем в main и делаем простую систему шифровки, расшифровки.
Шифрование.
Компилируем, PE у меня получился 106КБ размером, я создал для пробы файл 25гб, далее вводим команды для крипта
Нам выдается KEY и IV, понадобится нам для расшифровки
Далее ждем и нам показывает конечную инфу по шифрованию
1.5 гб/c неплохой результат, но стоит понимать что у нас AES-256, + большой размер файла, кончено же скорость может разнится, на 4гб файле мне показывало 2.5гб/c. Если поиграться с буферами, кастомными режимами по скипу чанков, и другими параметрами, будет другой результат.
Расшифровка.
Так же указываем файл и параметр "dec"
Далее вводим 2 параметра по очереди, и расшифровка начнется
После успешного декрипта видим что скорость такая же, оно и не удивительно)
И конечно же стоит убедится что шифровка и расшифровка работает, чекаем первые 200 байт и сравниваем результат
1. Вывод исходный файл
2. Вывод зашифрованный файл
3. Вывод расшифрованный файл
Как видите все идеально работает, код ниже:
Спасибо за внимание!
Приветствую всех пользователей LIMB.cc
В статье я опишу, как работает AES шифрование, разберу его режимы, также напишу собственный криптер, покажу особенности и фичи данного алгоритма.
AES появился около 25 лет назад и до сих пор остается одним из самых лучших алгоритмов мира в плане устойчивости, логики построения математических операций в раундах, размера ключа, устойчивости к криптоанализу и т. д. В общем и целом AES, по моему мнению, считается золотым стандартом для множества целей, где применяется криптография. Статья рассчитана на всех пользователей, попытаюсь понятным языком изложить принцип работы алгоритма и его особенности.
Предисловие.
Advanced Encryption Standard (AES) - Используется повсеместно в нашей жизни, например тот же WI-FI, протокол безопасности WPA2/WPA3 использует AES, для шифрование трафика. Тот же HTTPS, например использует AES-GCM/CCM. Но понятное дело, наибольший интерес представляет популярность данного алгоритма среди Ransomware групп если ранняя версия LockBit(ABCD) использовала AES для шифрования файлов, то и новый LockBit 5.0 не отступил от этой традиции.
Части статьи:
1. Особенности AES/Характеристики
2. Теория
3. Режимы AES
4. Практика/Написание Криптера
5. Декриптор/Расшифровка
Особенности AES/Характеристики.
Ключ шифрования.
1. В алгоритме используется симметричный ключ, это означает что он применяется как при шифрования так и при расшифровки. Должен быть всегда секретным.
2. Длинна ключа зависит от того какой вариант шифрования вы выберите: AES-128, AES-192, AES-256. То есть размер ключа может быть как 128 бит, так и 256 бит.
Варианты алгоритма.
Как я упомянул в пункте выше, у AES есть 3 основных варианта реализации:
1. AES-128, Размер ключа 128 бит, кол-во раундов 10
2. AES-192, Размер ключа 192 бит, кол-во раундов 12
3. AES-256, Размер ключа 256 бит, кол-во раундов 14
Соответственно чем больше ключ и раундов, тем дольше происходит процесс шифрования
Теория.
Для начала давайте представим 1 блок в AES(inital state) как матрицу 4х4 для того что бы можно было понять механизм операций. Размер inital state(блок в AES) - 16 байт.
Сам принцип алгоритма очень прост, прогоняем 128 битный блок через 14 раундов которые состоят из логических операций и получаем чистый зашифрованный блок. Ниже предоставлена схема(на ней вариант AES-128)
Не удалось прогрузить медиа
Логические операции.
В общем и целом, я имею ввиду про математические преобразования/операции алгоритма, просто сформулировать все аспекты в одном понятии сложно, поэтому пусть пункт будет иметь такое название.
SubBytes.
Простая операция подстановки байтов по таблице S-box
Не удалось прогрузить медиа
Входной байт меняется на байт из таблицы по X и Y, например у нас есть байт 8B, по таблице он превратится 3D, ниже представлен пример:
Не удалось прогрузить медиа
ShiftRows.
Тоже элементарная операция в которой строки сдвигаются вперед в той же матрице 4х4
Не удалось прогрузить медиа
Циклический сдвиг:
1. Строка, остается без изменений
2. Строка, 1 левый байт сдвигается в правую часть
3. Строка, 2 байта с лева сдвигаются в правую часть
4. Строка, 3 байта слева сдвигаются в правую часть
MixColumns.
Вот тут нужно очень точно и аккуратно понять как действует mixcolumns, здесь мы работаем с столбцом, в столбце естественно у нас есть 4 байта, эти 4 байта проходят через умножения и XOR, вот матрица умножений
Не удалось прогрузить медиа
Естественно при обычном сложении в HEX мы можем получить 2 байта обычным образом, но так как в AES все происходит в поле Галуа, "+" мы заменяем XOR, при таком раскалде мы естественно можем получить только 1 байт тем самым не выйдя за лимит 255, вот более точная формула
Не удалось прогрузить медиа
Умножение так же происходит в рамках поля Галуа, Пример:
Умножение на 02. Возьмем байт 6D, у него старший бит 0, (01101101), который слева, значит просто сдвигаем на 1 бит влево.
Итого: 6D * 02 = DA(11011010)
Умножение на 02 при условии что старший бит 1. Возьмем к примеру байт D4, старший бит 1, (11010100) опять же сдвигаем его в влево, но теперь у нас выходит за границы, и получается 9 битов, понятно дело что так не пойдет по этому обрезаем последний бит, выходит такой байт A8((-1)10101000), далее нужно сделать редукцию с байтом 1B, что бы не потерять байт у которого вылетел бит, производим обычный XOR: A8 ⊕ 1B = B3.
Итого: D4 * 02 = B3(10110011)
Умножение на 03. Тоже просто действие, Формула такая:
x*03 = (x*02) ⊕ x
Или же с примером
D4*03 = (D4*02) ⊕ D4 = B3 ⊕ D4 = 67
Простыми словами, нам нужно байт перемножить на 02, после чего сделать XOR с неперемноженным тем же байтом.
AddRoundKey.
Одна из самых важных и простых операций которая и делает initial state уникальным под ваш ключ.
XOR вашего блока который прошел через 3 операции, данный этап последний в цикле. Ниже пояснение как создается AddRoundKey.
Спойлер: Описание
Разложу по полкам. Допустим у нас будет 14 раундов, то есть AES-256, нужно прибегнуть к формуле
Nb⋅(Nr+1)
Nb - Кол-во слов(1 слово[w] = 4 байта)
Nr - Кол-во раундов(AES-256 имеет 14 раундов)
Но так же стоит заметить что у нас Nr = 14 + 1, и того 15 раз применяется операций.
1 Операция с AddRoundKey в самом начале, обычный XOR как раз таки нашего чистого мастер ключа w[0], w[1], w[2], w[3], С первым чистым блоком входного текста. Это можно назвать 0 раундом, После чего проходят все 14 раундов с операциями.
Как создается AddRoundKey? - есть такое понятие как key shedule.
Мы должны иметь такое же кол-во уникальных addroundkey операций как и 14 + 1, 240 байт в общем, но не забивайте себе этим голову.
По нашей формуле у нас Nb умножается на кол-во раундов, то есть 4 * 15 = 60, слово то 4 байта. В чем самый главный смысл, что мы расширяем наш ключ до 60 слов, изначально то у нас всего 8 слов так как ключ 256 бит / 32 бит, 32/4 = 1 слово, тут и выходит главный смысл, что ДАЖЕ для каждого раунда в 14 циклах применяется уникальный модифицированный ключ, что и делает анализ, вообще невозможным.
Как нам расширить ключ?
Возникает логический вопрос, а откуда появляются новые слова? ну то есть 8 мы уже имеем, а 52 из чего именно генерируются. Есть такая простая формула:
w = w[i-8] XOR temp
w - слово которое мы хотим преобразовать
w[i-8] - слово которое получилось если бы мы отняли 8 от w. Например w[17-8] = w9
temp/w[i-1] - предыдущее слово, то есть если мы генерируем w32 то берем в temp w31
Тем самым и получается генерация новых слов, но это простые слова А есть так же генерация слов кратных 8 и кратных 4, ну и конечно же AES был бы не AES-ом, если бы и тут не внес головную боль.
Ниже продемонстрированы 3 вариации при генерации новых слов которые существуют для AddRoundKey:
1. i % 8 == 0: temp = SubWord(RotWord(temp)) XOR Rcon[i/8]
Это все слова кратные 8
w[8], w[16], w[24], w[32], w[40], w[48], w[56]
2. i % 8 == 4: temp = SubWord(temp)
Это все слова кратные 4
w[12], w[20], w[28], w[36], w[44], w[52]
3. w = w[i-8] XOR temp
Во всех остальных случаях
Для раундов где числа кратны 8 или 4 используется отельные логические операции почти идентичные как в обычных раундах AES, такие как:
RotWord - Циклическое смещение байта в лево(означает что байт переместится в правую часть слова)
SubWord - Простая подстановка байтов по S-box
Rcon - Используется только для слов кратных 8, имеет свою таблицу
{0x00, 0x00, 0x00, 0x00}, {0x01, 0x00, 0x00, 0x00}, {0x02, 0x00, 0x00, 0x00}, {0x04, 0x00, 0x00, 0x00}, {0x08, 0x00, 0x00, 0x00}, {0x10, 0x00, 0x00, 0x00}, {0x20, 0x00, 0x00, 0x00}, {0x40, 0x00, 0x00, 0x00}, {0x80, 0x00, 0x00, 0x00}, {0x1b, 0x00, 0x00, 0x00}, {0x36, 0x00, 0x00, 0x00}
Ну и после чего идет обычный XOR с temp
В общем и целом AddRoundKey сложноватая операция но без нее никакЗакрепим ясность
0 Раунд - XOR 1 чистого initial state с AddRoundKey
1-Nr-1 Раундов все 4 операции - SubBytes -> ShiftRows -> MixColumns -> AddRoundKey
Последний Nr 3 операции - SubBytes -> ShiftRows -> AddRoundKey
:bangbang:Хочу заметить что в финальном раунде MixColumns не используется
Доп материалы:
Не удалось прогрузить медиа
AES: Keeping Your Data Secure with Advance Encryption Standard
learn.microsoft.com
Не удалось прогрузить медиа
Режимы AES.
У AES есть довольно множество режимов, которые влияют на его работу. Вообще дело в том что без режима вообще AES не может работать, так как чистый алгоритм может зашифровать только 16 байт, а что бы зашифровать более 1 блока последовательно нужен конечно же режим, в котором и укладывается логика которая сохраняет цепочку и целостность.
Вообще режимов довольно много, но мы отметим 5 основных:
ECB - каждый блок шифруется независимо, самый примитивный режим, плох что одинаковые блоки дают одинаковый шифротекст
CBC - перед шифрованием блока делается XOR с предыдущим шифртекстом (первый блок с IV), за счёт чего пропадают паттерны, но шифрование становится последовательным и требует паддинга
CFB, OFB - делают из блочного шифра потоковый: шифруется предыдущий шифртекст или внутреннее состояние, результат XOR-ится с данными
CTR - самый лучший вариант для скорости, на вход идет nonce и counter, как в ChaCha20, его мы и будем использовать.
Практика.
Будем использовать библиотеку OpenSSL
Через Git клонируем vcpkg
git clone https://github.com/microsoft/vcpkg.gitcd vcpkg.\bootstrap-vcpkg.batУстанавливаем OpenSSL
.\vcpkg.exe install openssl:x64-windowsИнтегрируем в VS
.\vcpkg.exe integrate installНачнем с того что будем использовать EVP, это высокоуровневый API OpenSSL, от нас не потребуются высокие знания в алгоритме AES, естественно если бы мы писали свою реализацию, то на это ушло бы не мало времени)
Главным нашим преимуществом по скорости является AES-NI, это аппаратное ускорение, это означает что 1 раунд AES физически находится на процессоре, что позволяет достигать невероятной скорости шифрования, вы можете проверить есть ли оно у вас на процессоре через __cpuid, это механизм который запрашивает у процессора набор поддерживаемых возможностей, давайте проверим есть ли у вас поддержка AES-NI
C++:
#include <intrin.h>
#include <stdio.h>
int main() {
int cpuInfo[4];
__cpuid(cpuInfo, 1);
puts((cpuInfo[2] & (1 << 25)) ? "AES-NI +" : "AES-NI -");
}
Минимальный код для проверки есть ли у вас AES-NI, дело в том что ECX сообщает нам находится ли аппаратное ускорение на процессоре или нет, через 25 бит
Не удалось прогрузить медиа
Но нам не понадобится данная функция так как OpenSSL EVP и так умеет определять AES-NI и оптимизироваться под него.
(Источник:https://www.felixcloutier.com/x86/cpuid)
Я приложу код в конце статьи, мы можем шифровать и расшифровывать указанный файл в одной директории, начнем с основной логики
C++:
static bool crypt_file_ctr(bool enc, const char* in_path, const char* out_path, const unsigned char* key, const unsigned char* iv) {
FILE* fi = std::fopen(in_path, "rb");
FILE* fo = std::fopen(out_path, "wb");
if (!fi || !fo) return false;
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return false;
int ok = enc ? EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, key, iv)
: EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, key, iv);
if (ok != 1) { EVP_CIPHER_CTX_free(ctx); return false; }
std::vector<unsigned char> in(8 << 20), out(in.size() + 16);
for (;;) {
size_t n = std::fread(in.data(), 1, in.size(), fi);
if (n == 0) break;
int outlen = 0;
ok = enc ? EVP_EncryptUpdate(ctx, out.data(), &outlen, in.data(), (int)n)
: EVP_DecryptUpdate(ctx, out.data(), &outlen, in.data(), (int)n);
if (ok != 1) { EVP_CIPHER_CTX_free(ctx); return false; }
std::fwrite(out.data(), 1, outlen, fo);
}
int finlen = 0;
ok = enc ? EVP_EncryptFinal_ex(ctx, out.data(), &finlen)
: EVP_DecryptFinal_ex(ctx, out.data(), &finlen);
if (ok != 1) { EVP_CIPHER_CTX_free(ctx); return false; }
std::fwrite(out.data(), 1, finlen, fo);
EVP_CIPHER_CTX_free(ctx);
std::fclose(fi); std::fclose(fo);
return true;
}
Во первых так как у нас режим ctr, юзаем IV для шифрования, открываем файл, читаем, создаем openssl контекст и ключем и IV, чтение происходит по 8мб, шифрует/дешифрует, после выполненной работы, закрывает контекст и файл
Далее парсер для ключей + IV
C++:
static bool hex2bytes(const char* hex, unsigned char* out, size_t outlen) {
auto hexval = [](char c)->int {
if ('0' <= c && c <= '9') return c - '0';
if ('a' <= c && c <= 'f') return c - 'a' + 10;
if ('A' <= c && c <= 'F') return c - 'A' + 10;
return -1;
};
if (std::strlen(hex) != outlen * 2) return false;
for (size_t i = 0; i < outlen; i++) {
int hi = hexval(hex[i * 2]), lo = hexval(hex[i * 2 + 1]);
if (hi < 0 || lo < 0) return false;
out[i] = (unsigned char)((hi << 4) | lo);
}
return true;
}
static void print_hex(const char* label, const unsigned char* p, size_t n) {
std::printf("%s", label);
for (size_t i = 0; i < n; i++) std::printf("%02X", p[i]);
std::printf("\n");
}
Простая проверка длинны, парс hex, что бы ключ не сломался и выводился в консоль нормально
Это были 3 основные функции, далее все соединяем в main и делаем простую систему шифровки, расшифровки.
Шифрование.
Компилируем, PE у меня получился 106КБ размером, я создал для пробы файл 25гб, далее вводим команды для крипта
cryptor.exe enc rand25gb.binНам выдается KEY и IV, понадобится нам для расшифровки
Не удалось прогрузить медиа
Далее ждем и нам показывает конечную инфу по шифрованию
Не удалось прогрузить медиа
1.5 гб/c неплохой результат, но стоит понимать что у нас AES-256, + большой размер файла, кончено же скорость может разнится, на 4гб файле мне показывало 2.5гб/c. Если поиграться с буферами, кастомными режимами по скипу чанков, и другими параметрами, будет другой результат.
Расшифровка.
Так же указываем файл и параметр "dec"
cryptor.exe dec rand25gb.bin.decryptДалее вводим 2 параметра по очереди, и расшифровка начнется
Не удалось прогрузить медиа
После успешного декрипта видим что скорость такая же, оно и не удивительно)
Не удалось прогрузить медиа
И конечно же стоит убедится что шифровка и расшифровка работает, чекаем первые 200 байт и сравниваем результат
Не удалось прогрузить медиа
1. Вывод исходный файл
2. Вывод зашифрованный файл
3. Вывод расшифрованный файл
Как видите все идеально работает, код ниже:
Не удалось прогрузить медиа
AES duty - PasteCode.dev
PasteCode.dev is a website where you can store any code online for quick sharing. You can paste code and share code online for free.
Не удалось прогрузить медиа
pastecode.dev
Спасибо за внимание!
👍53
❤️11
😮7
👏11
🤔7
😢6
LIMB