• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья ASM – CNG (часть 2). Шифрование AES-256 в режиме GCM

Это вторая часть разговора об инфраструктуре шифрования нового поколения "Crypt Next Generation". В первой мы рассмотрели базовые сведения о шифровании данных, и режимы работы алгоритма AES. В том-что это действительно мотор поколения Next можно убедиться на сайте MSDN, где на невинный запрос описания какой-либо из библиотеки CAPI, мелкософт встречает нас грозным предупреждением типа: -"Этот API устарел! Новое программное обеспечение должно использовать CNG, т.к. Microsoft может удалить этот API в будущих выпусках". Таким образом нас просто ставят перед фактом, подталкивая на изучение современных методов. Так не будем-же противиться этому..


Содержание:


1. Хеширование информации;
2. AAD – дополнительные данные для хеша;
3. Алгоритм AES в режиме GCM;
4. Практика – шифрование с аутентификацией;
5. Использование AEAD в браузерах;
6. Выводы.
-------------------------------------------------------

1. Хеширование данных

Как правило, информацию хешируют для проверки её целостности. Отправитель пересчитывает хеш со-своей инфы, и вкладывает его значение в конверт с данными. Получатель на другом конце извлекает из конверта значение полученного хеша, и при помощи всё той-же функции повторно снимает хеш с принятых данных. Если при сравнении оба значения совпадут, значит не один бит информации не потерялся в каналах связи, и полученная инфа валидна.

Здесь нужно понимать, что расчёт хеша – это не шифрование данных, а своеобразная сумма всех байт, на которую накладываются сложные математические операции. По этой причине, хеширование является односторонней функцией, без возможности восстановления исходных данных.

Вне зависимости от размера информации на входе, мы всегда получаем хеш фиксированной длины. К примеру алгоритмы MD2[4,5] генерируют на выходе 16-байтные значения, а разрядность хеша SHA (Secure Hash Algorithm) указывается в его названии, т.е. для SHA-512 будет соответственно 512-бит или 64 байта (особняком стоит SHA1 с размером хеша 160-бит = 20 байт).

Возьмём к примеру пароль из четырёх символов "1234". Как его хранить в программе? Ясно, что не в открытом виде, а если зашифровать обычным ксором, то подбор его брутфорсом займёт пару минут. Поэтому мы снимаем с пароля хеш например SHA-256 (sha2) и на выходе получаем уже 32-байтное значение этого пароля. Так решаются сразу две проблемы: во-первых автоматически отваливается брутфорс любого рода, а во-вторых – скрывается от посторонних глаз пароль. Получить коллизию при 32-байтном значении нереально, а потому можно быть уверенным, что хеши двух паролей не совпадут.

Для программного его расчёта нам понадобятся аж шесть функций из библиотеки CNG Bcrypt.dll, т.к. в нашем распоряжении не законченные функции, а их примитивы. Конечно удобней было-бы собрать все в один флакон и обозвать набор элементарно BCryptHash() (кстати подобная функция есть и добавлена она только в Win10), но мы имеем дело с библиотекой низкого уровня, где каждую единицу приходится обрабатывать отдельно – это раскрепощает нас, хотя и требует взамен больше телодвижений.

Функция BCryptOpenAlgorithmProvider() возвращает дескриптор выбранного алгоритма. Поскольку мы собираемся хешировать данные, то передаём этой функции во-втором параметре указатель на Unicode-строку "SHA256". Подготавливает окружение для операции следующий примитив BCryptCreateHash(). При вызове, она запросит у нас память для временного своего объекта, так-что перед ней нужно будет узнать у провайдера размер этого объекта для данного алгоритма, при помощи BCryptGetProperty() с аргументом "OBJECT_LENGTH".


C-подобный:
;//
;//  Common algorithm Unicode identifiers.
;//  (du = Define Unicode)
;//
 BCRYPT_3DES_112_ALGORITHM            du  '3DES_112',0
 BCRYPT_3DES_ALGORITHM                du  '3DES',0
 BCRYPT_AES_ALGORITHM                 du  'AES',0
 BCRYPT_AES_CMAC_ALGORITHM            du  'AES-CMAC',0
 BCRYPT_AES_GMAC_ALGORITHM            du  'AES-GMAC',0
 BCRYPT_AES_XTS_ALGORITHM             du  'XTS-AES',0
 BCRYPT_CAPI_KDF_ALGORITHM            du  'CAPI_KDF',0
 BCRYPT_DES_ALGORITHM                 du  'DES',0
 BCRYPT_DESX_ALGORITHM                du  'DESX',0
 BCRYPT_DH_ALGORITHM                  du  'DH',0
 BCRYPT_DSA_ALGORITHM                 du  'DSA',0
 BCRYPT_ECDH_ALGORITHM                du  'ECDH',0
 BCRYPT_ECDH_P256_ALGORITHM           du  'ECDH_P256',0
 BCRYPT_ECDH_P384_ALGORITHM           du  'ECDH_P384',0
 BCRYPT_ECDH_P521_ALGORITHM           du  'ECDH_P521',0
 BCRYPT_ECDSA_ALGORITHM               du  'ECDSA',0
 BCRYPT_ECDSA_P256_ALGORITHM          du  'ECDSA_P256',0
 BCRYPT_ECDSA_P384_ALGORITHM          du  'ECDSA_P384',0
 BCRYPT_ECDSA_P521_ALGORITHM          du  'ECDSA_P521',0
 BCRYPT_MD2_ALGORITHM                 du  'MD2',0
 BCRYPT_MD4_ALGORITHM                 du  'MD4',0
 BCRYPT_MD5_ALGORITHM                 du  'MD5',0
 BCRYPT_PBKDF2_ALGORITHM              du  'PBKDF2',0
 BCRYPT_RC2_ALGORITHM                 du  'RC2',0
 BCRYPT_RC4_ALGORITHM                 du  'RC4',0
 BCRYPT_RNG_ALGORITHM                 du  'RNG',0
 BCRYPT_RNG_DUAL_EC_ALGORITHM         du  'DUALECRNG',0
 BCRYPT_RNG_FIPS186_DSA_ALGORITHM     du  'FIPS186DSARNG',0
 BCRYPT_RSA_ALGORITHM                 du  'RSA',0
 BCRYPT_RSA_SIGN_ALGORITHM            du  'RSA_SIGN',0
 BCRYPT_SHA1_ALGORITHM                du  'SHA1',0
 BCRYPT_SHA256_ALGORITHM              du  'SHA256',0
 BCRYPT_SHA384_ALGORITHM              du  'SHA384',0
 BCRYPT_SHA512_ALGORITHM              du  'SHA512',0
 BCRYPT_SP800108_CTR_HMAC_ALGORITHM   du  'SP800_108_CTR_HMAC',0
 BCRYPT_SP80056A_CONCAT_ALGORITHM     du  'SP800_56A_CONCAT',0
 BCRYPT_TLS1_1_KDF_ALGORITHM          du  'TLS1_1_KDF',0
 BCRYPT_TLS1_2_KDF_ALGORITHM          du  'TLS1_2_KDF',0

Теперь можно приступать непосредственно к процессу хеширования BCryptHashData() – функция ожидает адрес исходных данных и их размер. Финализирует операцию BCryptFinishHash(), которая сохраняет готовый к употреблению хеш в указанный нами буфер. По окончании, временный объект и всё окружение подлежит уничтожению функцией BCryptDestroyHash(), но если это последнее, что делает наша программа, ExitProcess() на выходе сама подчистит все выделенные системой ресурсы.

В своей демке, я посчитаю хеш введённого юзером пароля сразу двумя алгоритмами SHA-256 и MD5, чтобы можно было сравнить их значения:


C-подобный:
format pe console
entry  start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\bcrypt.inc'
;//------------
.data
pcbResult     dd  0
passLen       dd  0
hashBuff      rb  64      ;//<---- готовый продукт хеша
hashObject    rb  1024    ;//<---- буф под временный объект

align 16
;//<----- данные SHA-256
shaAlgHndl    dd  0
shaHndl       dd  0
shaLen        dd  0
shaObjLen     dd  0
;//<----- данные MD5
md5AlgHndl    dd  0
md5Hndl       dd  0
md5Len        dd  0
md5ObjLen     dd  0

buff          db  0
;//------------
.code
start:   invoke  SetConsoleTitle,<'*** CNG Hash example ***',0>

        cinvoke  printf,<10,' SHA-256 and MD5 hash generate',\
                         10,' =======================================',10,0>

;// Получить дескриптор алгоритма SHA-512, и собрать о нём инфу
         invoke  BCryptOpenAlgorithmProvider,shaAlgHndl,BCRYPT_SHA256_ALGORITHM,0,0
         invoke  BCryptGetProperty,[shaAlgHndl],BCRYPT_HASH_LENGTH, shaLen, 4, pcbResult,0
         invoke  BCryptGetProperty,[shaAlgHndl],BCRYPT_OBJECT_LENGTH, shaObjLen, 4, pcbResult,0

;// Получить дескриптор MD5, и собрать о нём инфу
         invoke  BCryptOpenAlgorithmProvider,md5AlgHndl,BCRYPT_MD5_ALGORITHM,0,0
         invoke  BCryptGetProperty,[md5AlgHndl],BCRYPT_HASH_LENGTH, md5Len,4, pcbResult,0
         invoke  BCryptGetProperty,[md5AlgHndl],BCRYPT_OBJECT_LENGTH, md5ObjLen,4, pcbResult,0

         mov     eax,[shaLen]    ;// длина хеша SHA в байтах
         mov     ebx,eax
         shl     ebx,3           ;// ..и в битах (х8)
        cinvoke  printf,<10,' SHA-256 information ************',\
                         10,'   Hash len.........:  %03d byte  (%d bit)',\
                         10,'   Temp object len..:  %03d byte',10,0>,eax,ebx,[shaObjLen]
         mov     eax,[md5Len]
         mov     ebx,eax
         shl     ebx,3
        cinvoke  printf,<10,' MD5 information ****************',\
                         10,'   Hash len.........:  %03d byte  (%d bit)',\
                         10,'   Temp object len..:  %03d byte',10,0>,eax,ebx,[md5ObjLen]

;//***********************************************
;// Запрашиваем у юзера пасс в свой буфер
        cinvoke  printf,<10,' =======================================',\
                         10,' Type pass..........:  ',0>
        cinvoke  scanf,<'%s',0>,buff

         invoke  lstrlen,buff    ;//<---- вычислить его длину
         mov     [passLen],eax

;//***** Хеширование данных из буфера ***************
         invoke  BCryptCreateHash,[shaAlgHndl],shaHndl,hashObject,[shaObjLen],0,0,0
         invoke  BCryptHashData,[shaHndl],buff,[passLen],0       ;//<---- исходные данные в буфере "buff"
         invoke  BCryptFinishHash,[shaHndl],hashBuff,[shaLen],0  ;//<---- результат в буфере "hashBuff"
         invoke  BCryptDestroyHash,[shaHndl]

        cinvoke  printf,<10,' Pass len...........:  %02d byte',\
                         10,' Pass SHA-256 hash..:  ',0>,[passLen]
         mov     ecx,[shaLen]
         mov     esi,hashBuff
         call    PrintHexString

;//********************
         invoke  BCryptCreateHash,[md5AlgHndl],md5Hndl,hashObject,[md5ObjLen],0,0,0
         invoke  BCryptHashData,[md5Hndl],buff,[passLen],0
         invoke  BCryptFinishHash,[md5Hndl],hashBuff,[md5Len],0
         invoke  BCryptDestroyHash,[md5Hndl]

        cinvoke  printf,<10,' Pass MD5 hash......:  ',0>
         mov     ecx,[md5Len]
         mov     esi,hashBuff
         call    PrintHexString

@exit:  cinvoke  _getch
        cinvoke  exit,0
;//------------
;//----- Процедура вывода Hex-строки на консоль --------------
;//----- на входе: ESI = указатель на данные, ECX = длина
proc PrintHexString
@@:      xor     eax,eax
         lodsb
         push    ecx esi
        cinvoke  printf,<'%02x',0>,eax
         pop     esi ecx
         loop    @b
         ret
endp
;//------------
section '.idata' import data readable
library  kernel32,'kernel32.dll',msvcrt,'msvcrt.dll',bcrypt,'bcrypt.dll'

include  'api\kernel32.inc'
include  'api\msvcrt.inc'
include  'api\bcrypt.inc'

SHA256.png


Хешам находят разное применение на практике. К примеру аверы проверяют файлы на наличие блох только один раз, после чего сравнивают их хеши. Если файл заражён, он моментально всплывёт наружу – это быстрее, чем каждый раз повторять одну и ту-же процедуру по всей длине файлов.


2. AAD – дополнительные данные для хеша

Хеширование информации в чистом виде просуществовало довольно долго. Но однажды кому-то понадобилось не только проверять целость данных, но и идентифицировать их отправителя. К примеру получили мы конверт с вложенным хешем, но как узнать, что это хеш конкретно принятых данных, ведь отправитель (или третье лицо) может подставить вместо него любое значение от фонаря?

Одним из разумных решений стало добавление запроса на "ввод пароля" от отправителя. Теперь, на этапе хеширования информации, отправитель должен предоставить функции не только сами данные, но и дополнительную текстовую строку, чтобы однозначно идентифицировать себя – эту строку назвали AAD, или "Additional Authenticated Data". AAD можно рассматривать как внешнюю соль к хешу, и чтобы значения двух хешей совпали на передающей и принимающей стороне, получатель должен подтвердить оригинальный AAD (т.е. ввести пароль).

Так появились хеши с аутентификацией. Их тут-же взяли на вооружения сразу несколько режимов шифрования AES, известных под общим именем AEAD – "Authenticated Encryption with Associated Data" (аутентифицированное шифрование с присоединёнными данными). В этих режимах, алгоритм сначала шифрует исходные данные, после чего сразу-же вычисляет с них хеш. В контексте шифрования, такие хеши назвали МАС, что в развёрнутом виде означает "Message Authentification Code", или код аутентификации сообщения.

Таким образом, AEAD-режимы сочетают в себе две операции: непосредственно шифрование для конфиденциальности, и вычисление MAC для проверки целостности данных, с аутентификацией юзера. Это приводит к более низким вычислительным затратам, по сравнению с использованием отдельных функций шифрование + аутентификация. Здесь, первая часть сообщения шифруется, вторая часть в виде AAD остаётся открытой, и всё сообщение целиком накрыто аутентификацией. В настоящее время имеются несколько AEAD-режимов AES, но в тройке лидеров: OCB2 (Offset codebook), CCM (CBC+MAC), и герой данного чтива GCM (Galois Counter Mode, CTR+GMAC) как наиболее популярный.

Один из примеров использования AAD – это когда наше приложение служит прокси-сервером для развёртывания интерфейса с одним симметричным ключом и неограниченным кол-вом клиентов (причём каждый клиент находится в своём сегменте безопасности). Например, приложение может быть базой, в которую юзеры заносят данные личного характера. Когда одному из них нужно просмотреть свои записи, приложение может затребовать уникальное его имя в качестве AAD. В этом сценарии, функция AES откажет в расшифровке данных, если для крипт/декрипта не используется одно и то-же значение AAD. Параметр является опциональным в функции шифрования BCryptEncrypt(), однако он всегда принимает участие в процессе – если мы игнорируем его, просто используется пустая строка.


3. Алгоритм AES в режиме GCM

На рисунке ниже представлена схема режима AES/GCM где видно, что он состоит из двух самостоятельных модулей – это шифрование и аутентификация (серый блок). Обратите внимание на локацию доп.данных AAD, обозначенных здесь как "Auth-Data". Они поступают в модуль GMAC и не шифруются, подвергаясь только операции хеш. Результатом функций AEAD является зашифрованный текст + тег аутентификации МАС (синие блоки). Вместе с ключом, вектором IV и необязательным AAD, 16-байтный тег МАС необходим для расшифровки закрытого CipherText. Криптографы всегда рекомендуют использовать режимы AEAD для блочных алгоритмов шифрования с симметричным ключом:


AES_GCM.png


Если присмотреться к этой схеме, то режим AES/GMAC есть ничто-иное как расширенная версия режима CTR (см.предыдущую часть статьи), к которому добавлен модуль GMAC для одновременного вычисления хеша:

AES-CTR.png


Все блочные алгоритмы с счётчиком на борту, благодаря по сути превращаются в потоковый шифр. Поэтому важно, чтобы для каждого блока AES использовался не повторяющийся IV, для чего собственно и служит счётчик. В отличии от блочных шифров, поточные не увеличивают исходные данные после шифрования – это относится и к AES/GCM.

Зоопарк режимов AES может иметь разные характеристики производительности. GCM в полной мере использует преимущества параллельной обработки на нескольких ядрах процессора, и его реализация эффективно использует конвейер команд CPU. А вот цепочечные режимы AES (типа CBC) напротив приводят к остановкам конвейера, и это снижает их производительность.

Чтобы увеличить скорость шифрования, начиная с процессоров "Xeon-56xx" Intel ввела специальный набор инструкций под названием AES-NI (New Instruction). Так появилась
шифрования AES и хеширования SHA. Несмотря на то, что в данный набор входят всего несколько инструкций, они относятся к разряду SIMD, когда одна инструкция выполняет сразу несколько микроопераций (Single Instruction, Multiple Data). Благодаря тому, что каждая такая инструкция проделывает свою часть глобальной работы (AES включает в себя 4-этапа) – это позволило добиться 5-кратного увеличения скорости. Таким образом, если вам нужен быстрый режим шифрования с массой дополнений, используйте AES/GCM.

Типичный интерфейс его программирования выглядит так:

• Шифрование

- Вход: открытый текст + ключ + IV + опционально AAD в виде открытого текста, который не будет зашифрован, но будет защищён аутентификацией.
- Выход: зашифрованный текст + тег аутентификации МАС.

• Расшифровка
- Вход: зашифрованный текст + ключ + IV + МАС + AAD (если он использовался во время шифрования).
- Выход: открытый текст или ошибка, если МАС или AAD не соответствуют зашифрованному тексту.

Кстати в литературе, мнение криптографов на счёт определения "вектор инициализации IV" расходятся. Дело в том, что термин применяется как в контексте шифрования, так и в руководствах по хешированию данных. Поэтому (если дотошно придираться к мелочам), здесь нужно внести ясность..

Для хэш-функций, вектор инициализации – это постоянная константа, которая никогда не меняется (полином, см.в
). Для блочных-же шифров, одним из обязательных условий является уникальный IV для каждого блока AES – такие векторы назвали Nonce, или одноразовое не повторяющееся значение N(once). Это вносит путаницу и делает свойства двух терминов неоднозначными. Но т.к. IV в промышленных процедурах хеширования зарыт глубоко внутри и его хвост не торчит снаружи, то им можно пренебречь. Таким образом остаётся только шифрование, где термины IV/nonce превращаются в синонимы, даже на уровне документации MSDN.


4. Практика – шифрование AES без аутентификации

Теперь к практической реализации AES..
Говорят, что качество имеет различные метрики, и это утверждение как-нельзя лучше подходит к выбору нами алгоритма шифрования. Если задача проста и не требует особой криптко-стойкости, то зачем нагружать программу лишним кодом, ведь чем он проще, тем стабильней работает софт. По этой причине, сначала рассмотрим обычное шифрование AES без тегов аутентификации МАС (в числе которых режимы CBC и CFB), а потом и AEAD-режимы CCM/GCM с дополнительными данными AAD.

Всё-что нужно режимам без МАС, это ключ шифрования и вектор инициализации IV. Как-правило, оба эти значения на подготовительном этапе генерируются рандомно, при этом мы можем выбрать один из трёх размеров ключей на своё усмотрение: 16, 24, 32 байта (для AES-128, 192, 256 соответственно). Поскольку в данных режимах нет внутреннего 4-байтного счётчика, то вектор IV должен быть строго размером 16 байт, чтобы он захватил весь первый блок AES:


CBC_CFB.png


Программная реализация шифрования включает в себя пять этапов по такой схеме:

1. BCryptOpenAlgorithmProvider() с аргументом "AES_ALGORITHM" – возвращает дескриптор алгоритма;
2. BCryptSetProperty() с аргументом "CHAIN_MODE_CBC" – устанавливает в свойствах дескриптора режим CBC;
3. BCryptGenRandom() – сгенерировать случайные ключ и вектор IV;
4. BCryptGenerateSymmetricKey() – получить дескриптор симметричного ключа шифрования;
5. BCryptEncrypt() – зашифровать данные ключом и вектором IV.

На что здесь следует обратить особое внимание, так это на значение вектора IV. Дело в том, что согласно докам MSDN, функция шифрования BCryptEncrypt() может испортить его при переходе к следующему блоку AES, и если мы не хотим потерять исходные данные, то должны заблаговременно сделать резервную копию оригинального IV, чтобы в первоначальном виде передать его функции последующей расшифровки BCryptDecrypt(). Иначе мы получим невалидный вектор, и данные уйдут к праотцам. Отметьте сей факт красным фломастером!

Функция-же непосредственно шифрования BCryptEncrypt() принимает на грудь 10 параметров, из них в приоритете четвёртый "PaddingInfo" и последний "dwFlags". Два этих аргумента содержат информацию о заполнении 16-байтных блоков AES, и для режимов без аутентификации первый должен быть нуль, а во-втором нужно выставить флаг авто-выравнивания "BCRYPT_BLOCK_PADDING". Если его не указать, функция шифрования начнёт требовать от нас, чтобы мы подавали на вход кратные блоку AES данные, т.е. минимум 16 и дальше 32,48 и т.д. байт. Так-что паддинг ставим в единицу, что даст возможность вводить пароли произвольной длины.


C-подобный:
NTSTATUS BCryptEncrypt  ;// <----- 0 = OK!
  hKey           dd  0  ;// дескриптор ключа от BCryptGenerateSymmetricKey()
  pbInput        dd  0  ;// буфер с открытым текстом
  cbInput        dd  0  ;// размер буфера
  pPaddingInfo   dd  0  ;// инфа о заполнении (только асим и аутен)
  pbIV           dd  0  ;// адрес IV
  cbIV           dd  0  ;// размер IV
  pbOutput       dd  0  ;// адрес приёмного буфа (нуль = вычислить размер)
  cbOutput       dd  0  ;// размер
  pcbResult      dd  0  ;// получает кол-во байт на выходе
  dwFlags        dd  0  ;// нуль для ССМ и GCM

• pPaddingInfo
Указатель на структуру, содержащую информацию о заполнении. Этот параметр используется только с асимметричными ключами и режимами аутентифицированного шифрования. Если используется Auth-режим, параметр должен указывать на структуру "BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO". Если используются асимметричные ключи, тип структуры определяется значением "dwFlags". В противном случае параметр должен иметь значение нуль.

В демке ниже продемонстрирован процесс шифрования и сразу-же расшифровки данных, обратной по назначению функцией BCryptDecrypt(). Параметры у них одинаковы и то-что мы передаём в функцию шифрования, нужно будет возвратить и в функцию декрипта (т.е. ключ, оригинальный IV и накрытые AES'ом данные). Поскольку блочные режимы AES увеличивают информацию на выходе и делают её кратной 16-байтному блоку, необходимо узнать их размер для аргумента "cbOutput". Для этого функция вызывается дважды – сначала вместо адреса приёмного буфа "pbOutput" подставляем нуль, и в переменной "pcbResult" получаем требуемый размер, который передаём во-второй вызов той-же функции. Это действительно как для крипта, так и для декрипта. Вот пример:

C-подобный:
format pe console
entry  start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\bcrypt.inc'
;//------------
.data
pcbResult     dd  0
passLen       dd  0
keyRandom     rb  32
ivRandom      rb  16     ;// оригинальный IV,
ivBackup      rb  16     ;// ..и его бэкап для передачи в декрипт.

;//<-------- данные AES-256/CBC
align 16
aesAlgHndl    dd  0
aesKeyHndl    dd  0
aesHndl       dd  0
aesBlockLen   dd  0
aesObjLen     dd  0

align 16
encryptBuff   rb  128    ;// под результат шифрования
decryptBuff   rb  128    ;// под результат расшифровки
buff          db  0      ;// пароль для шифрования
;//------------
.code
start:   invoke  SetConsoleTitle,<'*** CNG. Crypt-Next-Generation ***',0>

        cinvoke  printf,<10,' AES-256/CBC password crypt example',\
                         10,' ========================================',10,0>

;// Получить дескриптор AES-256/CBC, и собрать об алгоритме инфу.
;// Для этого в свойствах дескриптора нужно выставить флаг "BCRYPT_CHAIN_MODE_CBC"
         invoke  BCryptOpenAlgorithmProvider,aesAlgHndl,BCRYPT_AES_ALGORITHM,0,0
         invoke  BCryptSetProperty,[aesAlgHndl],BCRYPT_CHAINING_MODE, BCRYPT_CHAIN_MODE_CBC,30,0

         invoke  BCryptGetProperty,[aesAlgHndl],BCRYPT_BLOCK_LENGTH, aesBlockLen,4,pcbResult,0
         invoke  BCryptGetProperty,[aesAlgHndl],BCRYPT_OBJECT_LENGTH, aesObjLen,4, pcbResult,0

         mov     eax,[aesBlockLen]
         mov     ebx,eax
         shl     ebx,3
        cinvoke  printf,<10,'   Crypt block len....:  %03d byte  (%d bit)',\
                         10,'   Temp object len....:  %03d byte',0>,eax,ebx,[aesObjLen]

;// Сгенерим 32-байт рандом для ключа
         invoke  BCryptGenRandom,0,keyRandom,32,BCRYPT_USE_SYSTEM_PREFERRED_RNG
        cinvoke  printf,<10,'   32-byte crypt key..:  ',0>
         mov     ecx,32
         mov     esi,keyRandom
         call    PrintHexString

;// Сгенерим 16-байт рандом для вектора IV
         invoke  BCryptGenRandom,0,ivRandom,16,BCRYPT_USE_SYSTEM_PREFERRED_RNG
        cinvoke  printf,<10,'   16-byte crypt IV...:  ',0>
         mov     ecx,16
         mov     esi,ivRandom
         push    esi ecx
         call    PrintHexString

;// Функция Encrypt() портит оригин.IV в буфере, поэтому сделаем его бэкап для расшифровки
         mov     edi,ivBackup
         pop     ecx esi
         rep     movsb

;//************* ШИФРОВАНИЕ **********************************
;// Запрашиваем пасс юзера в свой буфер
        cinvoke  printf,<10,10,' ========================================',\
                         10,' Type pass............:  ',0>
        cinvoke  scanf,<'%s',0>,buff
         invoke  lstrlen,buff
         mov     [passLen],eax

;// Получим дескриптор ключа AES-256/CBC
         invoke  BCryptGenerateSymmetricKey,[aesAlgHndl],aesKeyHndl,0,0,keyRandom,32,0

;// Первый вызов, чтобы узнать размер выходных данных (в переменную "pcbResult")
         invoke  BCryptEncrypt,[aesKeyHndl],buff,[passLen],0,ivRandom,16,\
                               0,0,pcbResult,BCRYPT_BLOCK_PADDING

;// Второй вызов - непосредственно шифрование в буфер "encryptBuff"
         invoke  BCryptEncrypt,[aesKeyHndl],buff,[passLen],0,ivRandom,16,\
                               encryptBuff,[pcbResult],pcbResult,BCRYPT_BLOCK_PADDING

        cinvoke  printf,<10,'    Plain  pass size..:  %02d byte',\
                         10,'    Cipher pass size..:  %02d byte',\
                         10,'    Pass cipher text..:  %s',0>,[passLen],[pcbResult],encryptBuff

;//************* РАСШИФРОВКА **********************************
;// Соответственно чтобы обратно расшифровать пароль,
;// нужно вызвать BCryptDecrypt() передав ей: ключ, оригинальный IV, и адрес данных
        cinvoke  printf,<10,10,' ========================================',\
                         10,' Decrypt password *****',0>

;// Расшифровка - получим дескриптор ключа из значения
         invoke  BCryptGenerateSymmetricKey,[aesAlgHndl],aesKeyHndl,0,0,keyRandom,32,0

;// Запросим в EAX размер зашифрованных данных
         invoke  lstrlen,encryptBuff
         push    eax

;// Первый вызов, чтобы узнать размер выходных данных (в переменную "pcbResult")
         invoke  BCryptDecrypt,[aesKeyHndl],encryptBuff,eax,0,ivBackup,16,\
                               0,0,pcbResult,BCRYPT_BLOCK_PADDING

;// Второй вызов - непосредственно расшифровка в буфер "decryptBuff"
         pop     eax
         invoke  BCryptDecrypt,[aesKeyHndl],encryptBuff,eax,0,ivBackup,16,\
                               decryptBuff,[pcbResult],pcbResult,BCRYPT_BLOCK_PADDING

        cinvoke  printf,<10,'    Plain pass size...:  %02d byte',\
                         10,'    Pass plain text...:  %s',0>,[pcbResult],decryptBuff


@exit:  cinvoke  _getch
        cinvoke  exit,0
;//------------
;//----- Процедура вывода Hex-строки на консоль --------------
;//----- на входе: ESI = указатель на данные, ECX = длина
proc PrintHexString
@@:       xor     eax,eax
          lodsb
          push    ecx esi
         cinvoke  printf,<'%02x',0>,eax
          pop     esi ecx
          loop    @b
          ret
endp
;//------------
section '.idata' import data readable
library  kernel32,'kernel32.dll',msvcrt,'msvcrt.dll',bcrypt,'bcrypt.dll'

include  'api\kernel32.inc'
include  'api\msvcrt.inc'
include  'api\bcrypt.inc'

CBC_result.png


Как видим тут всё просто, но программисты предпочитают что-то позабористей, ведь за почти такое-же количество "приседаний" можно получить более лучший эффект. Достаточно в этом коде задействовать параметр "pPaddingInfo", как в нашем распоряжении оказываются дополнительные данные AAD, с возможностью получить тег-аутентификации МАС.

Если мы сталкиваемся с зашифрованными данными и их длина кратна 16-ти, то можно предположить, что это AES в блочном режиме. Если-же длина кратна восьми, как вариант перед нами DES.


4.1. Практика – шифрование AES-256 в режиме GCM

Режимы шифрования с аутентификацией GCM/CCM можно сравнить с безоборотным метанием ножа – они при любых обстоятельствах бьют точно в цель. Такие бонусы мы получаем благодаря структуре "BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO", указатель на которую передаётся в параметре(4) Padding.

C этой структурой связана одна мерзкая неприятность – по неизвестным причинам, её размер везде (на MSDN и хидере bcrypt.h) указывается на 8-байт меньше, чем этого требуют функции BCryptEncrypt/Decrypt(). Правится баг обычным добавление в хвост структуры ещё одного 8-байтного поля, которое я обозначил как резерв. Иначе крипто-функции выше возвращают ошибку "INVALID_PARAMETR" с кодом 0xC000000d. Вот её исправленное описание:


C-подобный:
struct BCRYPT_AUTH_CIPHER_MODE_INFO
   cbSize          dd   64  ;// размер данной структуры (должен быть мин.64 байт для х32)
   dwInfoVer       dd   1   ;// версия структуры, всегда = 1
   pbNonce         dd   0   ;// линк на буфер, содержащий IV (dq для х64)
   cbNonce         dd   0   ;// размер IV = 12 байт
   pbAuthData      dd   0   ;// линк на буфер с данными-аутентификации AAD (dq для х64)
   cbAuthData      dd   0   ;//
   pbTag           dd   0   ;// линк на буфер для кода-аутентификации (dq для х64)
   cbTag           dd   0   ;//
   pbMacContext    dd   0   ;// линк на буфер для МАС, (только при связанном вызове, dq для х64)
   cbMacContext    dd   0   ;//
   cbAAD           dd   0   ;// AAD (только при связанном вызове)
   cbData          dq   0   ;// длина данных полезной нагрузки (только при связанном вызове)
   dwFlags         dd   0   ;// флаг связывания вызовов = 0
   Reserved        dq   0   ;// <------------------ (!)добавить, чтобы на х32 размер стал 64 байт!
ends

Функция BCryptEncrypt() способна за один выстрел шифровать данные, которые расположены сразу в нескольких разных буферах – такую схему назвали "связанный вызов" (активируется флагом BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG). Если разработчики её предусмотрели, значит кому-то это нужно. В этой схеме мы получаем единую зашифрованную массу из всех буферов, и один тег-аутентификации МАС на всех. Вызовы такого рода больше исключение, чем правило и в большинстве случаях отмеченные в скобках поля сбрасываются в нуль (т.е.остаются в дефолте).

Таким образом, при стандартной схеме с одним буфером исходных данных, нам остаётся заполнить только каждую пару полей: "pbNonce", "pbAuthData" и "pbTag". При этом AuthData является опциональным полем (можно не использовать AAD, тогда в место него будет пустая строка), а остальные два обязательны. Если мы шифруем данные, то значение "pbTag" должно быть адресом приёмного буфера, куда функция вернёт сгенерированный на выходе МАС (тег-аутентификации). Соответственно при расшифровке – поле используется как указатель на источник.

Некоторых пояснений требует и поле "pbNonce" в этой структуре. Как уже говорилось ранее, в контексте шифрования, термины "Nonce" и "вектор IV" нужно воспринимать как оговорку по Фрейду – они обозначают одно и то-же. Однако здесь есть нюанс, который не следует сбрасывать со-счетов!

Если в двух словах, то когда мы шифруем алгоритмами без аутентификации не используя структуру "BCRYPT_AUTH_CIPHER_MODE_INFO" (см.предыдущий пример), указатель на вектор IV и его размер должны находится в пятом и шестом параметрах функции BCryptEncrypt(). Если-же планируем шифровать в режиме GCM с тегом МАС, то в самой функции параметры IV нужно обязательно сбросить в нуль, и поместить линк на буфер IV в поле "pbNonce" данной структуры. Очевидно, что нашаманили здесь мелкомягкие так, что без танцев с бубном не разберёшь.

Ну и немного практики..
Следуя предыдущему примеру, продемонстрируем опять шифрование и сразу восстановление данных.
Чтобы охватить всё вышеизложенное, помимо ввода пароля, я добавил и ввод дополнительных данных AAD. Если такое подтверждение не нужно, то просто жмякаем Enter и получаем пустую строку AAD. Но если мы шифруем при помощи доп.данных, то и при расшифровке нужно будет ввести валидную строку AAD. Кстати макс.размер этой строки равен 64 кБайт, поэтому функцию консольного ввода scanf() требуется заменить на gets() (первая читает ввод до первого пробела, что лишает нас возможности вводить предложения). Юзаем..


C-подобный:
format pe console
entry  start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\bcrypt.inc'
;//------------
.data
pcbResult     dd  0
passLen       dd  0

keyRandom     rb  32
ivRandom      rb  16
ivBackup      rb  16
tagBuff       rb  16
chainMode     rb  64

;//<----- данные AES-256/GCM
align 16
aesKeyHndl    dd  0
aesAlgHndl    dd  0
aesHndl       dd  0
aesBlockLen   dd  0
aesObjLen     dd  0

align 16
authInfo      BCRYPT_AUTH_CIPHER_MODE_INFO
tagLen        BCRYPT_AUTH_TAG_LENGTHS_STRUCT

align 16
authLen       dd  0
authData      rb  128   ;//<---- буфер под строку AAD
encryptBuff   rb  128   ;//<---- буфер под зашифрованные данные
decryptBuff   rb  128   ;//<---- сюда расшифруем
buff          rb  0     ;//<---- пароль юзера
;//------------

.code
start:   invoke  SetConsoleTitle,<'*** CNG. Crypt-Next-Generation ***',0>

        cinvoke  printf,<10,' AES-256/GCM password crypt example',\
                         10,' =========================================',10,0>

;// Получить дескриптор AES-256/GCM, и собрать о нём инфу.
;// В свойствах дескриптора ставим флаг "BCRYPT_CHAIN_MODE_GCM"
         invoke  BCryptOpenAlgorithmProvider,aesAlgHndl,BCRYPT_AES_ALGORITHM,0,0
         invoke  BCryptSetProperty,[aesAlgHndl],BCRYPT_CHAINING_MODE, BCRYPT_CHAIN_MODE_GCM,30,0

         invoke  BCryptGetProperty,[aesAlgHndl],BCRYPT_BLOCK_LENGTH, aesBlockLen,4,pcbResult,0
         invoke  BCryptGetProperty,[aesAlgHndl],BCRYPT_OBJECT_LENGTH, aesObjLen,4, pcbResult,0
         invoke  BCryptGetProperty,[aesAlgHndl],BCRYPT_AUTH_TAG_LENGTH,tagLen,4*3,pcbResult,0
         invoke  BCryptGetProperty,[aesAlgHndl],BCRYPT_CHAINING_MODE,chainMode,64,pcbResult,0   ;//<--- запрос текущего режима

         mov     eax,[aesBlockLen]
         mov     ebx,eax
         shl     ebx,3
        cinvoke  printf,<10,' AES-256/GCM information ********',\
                         10,'   Chaining mode......:  %ls',\
                         10,'   Crypt block len....:  %03d byte  (%d bit)',\
                         10,'   Temp object len....:  %03d byte',\
                         10,'   Auth-tag    len....:  %d...%d byte, increment %d byte',0>,\
                         chainMode,eax,ebx,[aesObjLen],\
                         [tagLen.dwMinLen],[tagLen.dwMaxLen],[tagLen.dwIncrement]

;// Сгенерим рандомы Key(32) + IV(12) байт, и сохраним IV в резервный бэкап
        cinvoke  printf,<10,10,'   12-byte IV.........:  ',0>
         invoke  BCryptGenRandom,0,ivRandom,12,BCRYPT_USE_SYSTEM_PREFERRED_RNG
         mov     ecx,12
         mov     esi,ivRandom
         push    ecx esi
         call    PrintHexString

         pop     esi ecx
         mov     edi,ivBackup
         rep     movsb

        cinvoke  printf,<10,'   32-byte Key........:  ',0>
         invoke  BCryptGenRandom,0,keyRandom,32,BCRYPT_USE_SYSTEM_PREFERRED_RNG
         mov     ecx,32
         mov     esi,keyRandom
         call    PrintHexString

;//***********************************************
;// Запрашиваем в свои буферы пароль и "AAD"
        cinvoke  printf,<10,10,' ================ ENCRYPT =================',\
                         10,' Type pass............:  ',0>
        cinvoke  gets,buff
         invoke  lstrlen,buff
         mov     [passLen],eax

        cinvoke  printf,<' Type AAD.............:  ',0>
        cinvoke  gets,authData
         invoke  lstrlen,authData
         mov     [authLen],eax

;//************* ШИФРОВАНИЕ **********************
;// Запросить дескриптор ключа AES-256/GCM
         invoke  BCryptGenerateSymmetricKey,[aesAlgHndl],aesKeyHndl,0,0,keyRandom,32,0

;// Заполнить структуру "BCRYPT_AUTH_CIPHER_MODE_INFO"
         mov    [authInfo.pbNonce],ivRandom
         mov    [authInfo.cbNonce],12
         mov    [authInfo.pbTag],tagBuff
         mov    [authInfo.cbTag],16
         mov     eax,[authLen]
         mov    [authInfo.pbAuthData],authData
         mov    [authInfo.cbAuthData],eax

         invoke  BCryptEncrypt,[aesKeyHndl],buff,[passLen],authInfo,0,0,\
                               encryptBuff,[passLen],pcbResult,0

        cinvoke  printf,<10,' 12-byte origin IV....:  ',0>
         mov     ecx,12
         mov     esi,ivBackup
         call    PrintHexString

        cinvoke  printf,<10,' Encrypted pass.......:  %s',0>,encryptBuff

        cinvoke  printf,<10,' 16-byte GMAC.........:  ',0>
         mov     ecx,16
         mov     esi,tagBuff
         call    PrintHexString

;//************* РАСШИФРОВКА **********************
;// Очистить введённые данные в буфере AAD
         mov     edi,authData
         mov     ecx,128/4
         xor     eax,eax
         rep     stosd

;// Запрос юзеру на подтверждение AAD
        cinvoke  printf,<10,10,' ================ DECRYPT =================',\
                         10,' Type origin AAD......:  ',0>
        cinvoke  gets,authData
         invoke  lstrlen,authData
         mov     [authLen],eax

;// Узнать длину зашифрованного пароля
         invoke  lstrlen,encryptBuff
         mov     [passLen],eax

;// Заполнить структуру "BCRYPT_AUTH_CIPHER_MODE_INFO"
         mov    [authInfo.pbNonce],ivBackup
         mov    [authInfo.cbNonce],12
         mov    [authInfo.pbTag],tagBuff
         mov    [authInfo.cbTag],16
         mov     eax,[authLen]
         mov    [authInfo.pbAuthData],authData
         mov    [authInfo.cbAuthData],eax

;// Запросить дескриптор ключа AES-256/GCM
         invoke  BCryptGenerateSymmetricKey,[aesAlgHndl],aesKeyHndl,0,0,keyRandom,32,0

;// Расшифровать пароль!
         invoke  BCryptDecrypt,[aesKeyHndl],encryptBuff,[passLen],authInfo,0,0,\
                               decryptBuff,[passLen],pcbResult,0
         or      eax,eax
         je      @f
        cinvoke  printf,<10,' Decrypt ERROR! Get valid data. ',0>
         jmp     @exit
@@:     cinvoke  printf,<' Ok! Decrypted pass...:  %s',0>,decryptBuff


@exit:  cinvoke  _getch
        cinvoke  exit,0
;//------------
;//----- Процедура вывода Hex-строки на консоль ---------
;//----- на входе: ESI = указатель на данные, ECX = длина
proc PrintHexString
@@:       xor     eax,eax
          lodsb
          push    ecx esi
         cinvoke  printf,<'%02x',0>,eax
          pop     esi ecx
          loop    @b
          ret
endp
;//------------
section '.idata' import data readable
library  kernel32,'kernel32.dll',msvcrt,'msvcrt.dll',bcrypt,'bcrypt.dll'

include  'api\kernel32.inc'
include  'api\msvcrt.inc'
include  'api\bcrypt.inc'

AES_result.png


Если расчехлить отладчик и вскормить ему этот код, то нырнув в функцию BCryptEncrypt() по F7 можно обнаружить, что функция не использует преимущества аппаратного шифрования AES-NI, а весь алгоритм реализует исключительно обычными MOV, XOR и прочими. То есть почему-то Intel ввела инструкции AES-NI, а мелкософт не использует их. Может на это есть свои причины?

Обратите внимание на формат результата шифрования – он включает в себя три составляющие: 12-байтный вектор IV, зашифрованный пароль, и в хвосте тег-аутентификации GМАС. А вот данные AAD как-правило не передаются принимающей стороне, иначе в них теряется смысл. Их нужно держать в тайне, накрыв протектом на уровне самой программы. Это-же относится и 32-байтному ключу шифрования AES. Рассмотрим, как справляется с этой задачей, например, браузер Хром.


5. Использование AEAD в браузерах

Поскольку высказывания ниже могут классифицироваться в рамках шпионажа, я не буду заострять внимание на мелочах, а лишь приведу общую картину. Достаточно будет сказать, что алгоритм проверенный и воркает исправно.

На предыдущем скрине, я не зря расположил результат шифрования AES/GCM в таком порядке. По сути, от перестановки мест слагаемых сумма не меняется. Однако интернет браузеры Chrome версии 80+ держат эту информацию в своих базах SQLite именно в таком виде, только добавляют к ним ещё и сигнатуру "v10". То-есть получаем сл.табличку:


ChromeFormat.png


Это типичный формат хранения хромом паролей и кукисов, базы которых можно найти по следующим адресам:

• Пароли – AppData\Local\Google\Chrome\User Data\Default\Login Data
• Кукисы – AppData\Local\Google\Chrome\User Data\Default\Cookies

Открываем базу кукисов в каком-нибудь редакторе SQLite3 (типа SQLite Expert Personal), и обнаруживаем в ней две таблицы с названиями "cookies" и "meta". В первой имеется столбец "encrypted_value", где и хранятся зашифрованные алгоритмом AES-256/GCM непосредственно куки различных сайтов. Если щёлкнуть на пимпу любой строки в этом столбце, то перед нами откроется встроенный Hex-редактор, где будет красоваться соответствующий закриптованный кукис. Вот как это выглядит у меня:

Chrome_Blob.png


В процессе расшифровки данного кукиса, нам нужно просто отбросить сигнатуру "v10" и передать всю оставшуюся информацию в свой код. Отметим, что Хром не использует данные AAD, тогда для достижении цели нам остаётся добыть только 32-байтный ключ шифрования. Как и следовало ожидать, он прописан там-же, только в файле Json по адресу:

• AppData\Local\Google\Chrome\User Data\Local State

Помимо ключа, в этом файле хранится всякое барахло,
а сам ключ в нём можно найти по Json-строке "os_crypt":{"encrypted_key":

За этой строкой и вплоть до обратной/фигурной скобки, будет лежать закодированный в Base64 ключ. Значит нам нужно сначала раскодировать его из Base64, а затем снять с него протект при помощи функции DPAPI CryptUnprotectData() (рассматривалась в этой статье). После всех этих операций мы получим ключ расшифровки кукисов алгоритмом AES-256/GCM, а так-как имеем уже IV, MAC и зашифрованные данные, то дело остаётся за малым.

Json_Key.png



6. Послесловие

Шифрование является основным методом защиты информации, однако в основе криптографии лежит не только крипт, но и ряд вспомогательных методов. В частности это не рассмотренная здесь "электронная подпись" (используется для подтверждения авторства передаваемых данных). Такие алгоритмы называют "сигнатурными", и в них подписывается обычно хэш данных, а не все данные целиком. Они используют два вида ключей – секретный для вычисления электронной подписи, и открытый для её проверки.

У нас не было-бы безопасного современного Интернета без работы В.Реймена, Д.Дэемен, Д.Виеги, Д.МакГрю и бесчисленного множества других криптографов и исследователей безопасности, которые сделали возможным использование AES в режиме GCM. Это по истине один из самых продвинутых на сегодняшний день алгоритмов шифрования, который прочно пустил свои корни во-все направления современной сферы IT.

В скрепке лежат три рассмотренных выше исполняемых файла, а так-же инклуд "вcrypt.inc" для сборки исходников.
Надеюсь ещё увидимся, удачи, пока!
 

Вложения

  • AES_GCM.zip
    12,4 КБ · Просмотры: 361

rusrst

Green Team
04.10.2020
22
27
BIT
0
Прекрасные статьи! Было бы ещё очень интересно в Вашем исполнении прочитать про winsocs и wininet.
 

rusrst

Green Team
04.10.2020
22
27
BIT
0
Я читал про winsocs, и даже работал с ними :) но у автора уж больно хорошо и красиво выходит. Ну есть ещё wininet, которая организует протоколы высокого уровня над winsocs и которой уделено уже гораздо меньше внимания :(
За ссылки спасибо, ознакомлюсь!
P.s. ещё раз спасибо за ссылки, они как-то мимо меня прошли!!!
 
  • Нравится
Реакции: Marylin
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!