LIMB
Вход Регистрация

тема

AES шифрование

DESTR0Y · · 2 ответов · 672 просмотров

Внимание! Данный материал предназначен исключительно для образовательных целей в области криптографии. Любое использование описанных техник для создания вредоносного ПО является нарушением законодательства. Автор не несет ответственности за ваши действия.

Приветствую всех пользователей 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.

У 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.git
cd 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
ОтветDESTR0Y

Внимание! Данный материал предназначен исключительно для образовательных целей в области криптографии. Любое использование описанных техник для создания вредоносного ПО является нарушением законодательства. Автор не…

и что тут нового уникального кучу этих локеров уже перенаписали.
👍1
ОтветBegemotik

и что тут нового уникального кучу этих локеров уже перенаписали.

Вообще не понял причем тут локер)
👍1

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