Есть поговорка: "Программистами не рождаются – ими умирают". Исходя из этой парадигмы можно сделать вывод, что за долгий свой/программерский путь, у каждого из нас снежным комом накапливаются различные техники и алгоритмы, поделиться с общественностью которыми было-бы не грех. К примеру на этапе моего начинания, мир процессора для меня был так юн, что некоторые вещи не приобрели ещё свои имена и приходилось указывать на них пальцем. Позже, все просветления было решено записывать в блокнот, который постепенно разбух до увесистого романа. Фильтр элементарных заметок сделал своё дело и на данный момент в этом блокноте осталось ~1000 строк, которые я и решил сбросить сюда с краткими комментариями. Надеюсь у топика найдутся свои "евангелисты" и кто-нибудь почерпнёт с него что-то полезное.
-------------------------------------------
1. Логические операции на уровне битов
Загибание пальцев начну с логики и перечислю лишь наиболее достойные на мой взгляд моменты, т.к. охватить всё направление не представляется возможным. Нужно отметить, что на логические инструкции процессор тратит наименьшее кол-во своих тактов, потому на критических участках кода практичнее использовать именно логику. В умелых руках она позволяет творить чудеса, а её инструкции на уровне микро/архитектуры являются фундаментальными для всех остальных.
1.0 Инструкции SHR и ROR – циклический сдвиг и ротация
Для начала рассмотрим магию сдвиговых инструкций
Фишка в том, что сдвиг на 1-позицию влево равносилен умножению числа на 2; соответственно сдвиг на 2-позиции умножает число уже на 4, и т.д. Таким образом, если нам нужно быстро умножить содержимое регистра на степень(2), то можно применять сдвиги. Запустите виндовый калькулятор в режиме "Программист" и сравните полученные числа:
Это-же касается и деления числа на степень(2), только сдвигать нужно не влево, а вправо. Например, что-бы моментально перевести некоторое значение из байтов в килобайты, можно применить инструкцию
1.1 Связка SHR и RCL – зеркальное отражение бит
Интересный эффект можно получить контролируя выдвигаемые инструкцией
Вот как можно организовать подобное зеркало:
Что-то подобное делает и инструкция
1.2 Связка SHR и флаг(CF) – преобразование в BIN
По непонятной причине, в системе нет ни одной API-функции, которая могла-бы преобразовать число, в строку двоичного вида. Например, тот-же wsprintf() из Kernel32.dll не имеет спецификатора для BIN, а только OCT, DEC, HEX. В общем случае, в природе для группы функций printf() предусмотрены следующие спецификаторы, которые мы должны вскормить им в первом аргументе:
Для перевода числа в бинарную строку программисты изворачиваются кто-как может, а лично я предпочитаю по-битное выталкивание разрядов числа, с последующим преобразованием их в символы. Если заглянуть в ASCII-таблицу, то в ней наблюдается закономерность, что числа в диапазоне 0..9 отличаются от символьного своего представления ровно на
Если к этому коду прицепить вложенный цикл на 4 или 8 итераций, то можно организовать форматированный бинарный вывод, вставляя после каждой тетрады/байта точку или пробел. Это на порядок повысит читабельность бинарной строки.
1.3 Инструкция AND (лог.умножение(И), конъюнкция)
Остаток от деления любого числа на степень(2) можно получить при помощи логической инструкции обнуления битов
Допустим имеем в регистре число 18'500 и нужно узнать остаток от деления его на 128.
Для этого, отнимаем от 128 единицу и получаем число 127, двоичное представление которого состоит из шести последовательных единиц:
Другое применение инструкции
Например, если в регистре
Выравнивание особенно актуально для регистра-указателя на стек
2. Баг с флагом ZF – определение вендора CPU
Корпорации Intel и AMD – это вечные соперники. Каждый из них имеет свой штат сотрудников и инженеров, которые хоть и воруют иногда идеи друг-у-друга, но всё-же работают в закрытых офисах параллельно. По этой причине, в некоторых вопросах у них получаются разногласия, ухватившись за которые можно определить, продуктом какого именно вендора мы пользуемся.
Более того, человеку присущи ошибки и поскольку разработки ведутся независимо, то соответственно и баги в железяках у них свои. На запрос "Intel CPU bugs" можно выловить в гугле длинную портянку характерных конкретному производителю ошибок, от чего в последующем и плясать.
Одной из таких принципиальных отличий процессоров Intel от AMD является отношение инженеров к флагу(ZF) процессора, который информирует нас о нулевом состоянии какого-либо регистра – Zero Flag. К примеру, если взвести его на процессорах Intel и следом выполнить операцию деления
Если по существу, то продуманы из AMD на мой взгляд здесь правы, поскольку оперируют этим флагом только тогда, когда мы требуем этого явно, ..например проверяем конкретный регистр на нуль инструкцией
Таким образом имеем сигнатурный штамм, по которому можем вычислить производителя. В примере ниже мы явно взводим флаг(ZF) при обнулении регистра
Способ проверенный и работает на всех современных процессорах, расходуя всего с десяток тактов CPU. Можно сравнить его со-специально предназначенной для идентификации профессоров инструкцией
3. Как получить массив случайных чисел "RANDOM"
Перед каждым программером рано или поздно встаёт вопрос генерации случайных чисел, что на буржуйский манер звучит как Random. У мастдая с этим проблемы, и всё-что может предложить нам система – это сишную библиотеку времени исполнения msvcrt.dll (C-Run-Time library, или CRT), внутри которой имеется функция rand(). Она возвращает всего-лишь 16-битное случайное значение в пределах 32767, и то с очень плохим рассеиванием бит, поэтому никак не годится для криптографии.
Правда современные процессоры поддерживают уже инструкцию ассемблера
Однако мало кто знает, что в Windows имеется своя крипто-система, сосредоточенная в библиотеках Advapi32.dll и Crypt32.dll. Этот мощный механизм известен под общим названием Crypto-API и позволяет генерировать случайные числа без приставки "псевдо", т.к. заточен специально под шифрование. Можно получить криптографически стойкие ключи буквально неограниченного размера, а источниками для энтропии являются дребезжание контактов физической аппаратуры, или-же частотный анализ системы питания. Рассмотрим этот механизм в двух словах..
Работу данной крипто-системы поддерживают несколько провайдеров, которые зарыты в нёдрах оси. Каждый из них предоставляет нам свои услуги и генерирует ключи по своему индивидуальному алгоритму. Соответственно перед тем-как сгенерировать ключ, мы должны выбрать конкретного поставщика при помощи функции CryptAcquireContext(). Эта функция возвращает нам дескриптор, через который мы будем в дальнейшем обращаться к выбранному поставщику.
На рисунке ниже представлена общая схема крипто-системы Win, с краткой информацией о провайдерах. Надеюсь она прояснит ситуацию:
После того-как поставщик выбран (в примере я воспользуюсь услугами первого PROV_RSA_FULL=1), мы запрашиваем у него ключ функцией CryptGenRandom(), не забыв при этом указать необходимую длину ключа. Из таблицы выше видно, что мой провайдер с константой(1) генерит и шифрует ключ по алгоритму RC2 или 4, после чего применяет к нему хеш SHA или MD5 (на своё усмотрение). По окончании, нужно обязательно оповестить поставщика, что мы не нуждаемся более в его услугах. Закрывает открытый дескриптор функция CryptReleaseContext().
Таким образом в буфере получим Random требуемой длины, и теперь нам нужно как-минимум вывести его на консоль. Значит задача – число неизвестной длины перевести в строку соответствующего вида. Для этого, выталкивая при помощи
На сайте мелкомягких лежит утверждение, что мол функция CryptGenRandom() уже устарела и не следует использовать её в своих программах. Однако она прекрасно отрабатывает не только на моей Win-7, но даже и на последней десятке Win-10. Поэтому думаю рановато сбрасывать её со-счётов.
Попробуйте изменить длину ключа в константе "keyLen", и получите рандомное значение исполинских размеров, например 2048-байт и больше. Проецируя такой ключ на массив данных, можно организовать блочное шифрование по типу бородатого алгоритма Виженера.
Послесловие...
Что-то букаф много стало для одной статьи, поэтому по мере возможности буду расширять её позже. Если у кого-то имеются интересные на их взгляд алгоритмы и техники, то добро пожаловать под капот. Не обязательно, чтобы это был ассемблер – можно на сишке, или других языках. Лишь-бы с краткими пояснениями сути дела, а дальше реализовать можно на любом сленге. А пока закругляюсь и до скорого..
-------------------------------------------
1. Логические операции на уровне битов
Загибание пальцев начну с логики и перечислю лишь наиболее достойные на мой взгляд моменты, т.к. охватить всё направление не представляется возможным. Нужно отметить, что на логические инструкции процессор тратит наименьшее кол-во своих тактов, потому на критических участках кода практичнее использовать именно логику. В умелых руках она позволяет творить чудеса, а её инструкции на уровне микро/архитектуры являются фундаментальными для всех остальных.
1.0 Инструкции SHR и ROR – циклический сдвиг и ротация
Для начала рассмотрим магию сдвиговых инструкций
SHR
и SHL
(Shift to Right/Left, сдвиги вправо/влево соответственно). Операндами этих инструкций являются регистр-приёмник и счётчик сдвигаемых бит. Так, инструкция SHL EAX,5
сместит все биты регистра EAX
на пять позиций влево. К этим инструкциям присовокупляются и кольцевые их варианты ROR/ROL
(Rotate Right/Left), которые при сдвиге, например, вправо на 1-бит, устанавливают выдвинутый справа бит, старшим слева. То-есть получаем сдвиг (ротацию) без потери выдвинутых, в то время как при обычных циклических сдвигах, выдвинутые биты улетают безвозвратно в космос. Взяв за основу ротацию, можно организовать шифрование данных, т.к. информационная ценность при этом сохраняется.Фишка в том, что сдвиг на 1-позицию влево равносилен умножению числа на 2; соответственно сдвиг на 2-позиции умножает число уже на 4, и т.д. Таким образом, если нам нужно быстро умножить содержимое регистра на степень(2), то можно применять сдвиги. Запустите виндовый калькулятор в режиме "Программист" и сравните полученные числа:
Код:
;// Умножение числа(1) на степень(2) сдвигом влево
-----------+-------------+------------------------
Сдвиг на.. | Десятичное | Состояние бит в слове
-----------+-------------+------------------------
0 1 0000000000000001
1 2 0000000000000010
2 4 0000000000000100
3 8 0000000000001000
4 16 0000000000010000
5 32 0000000000100000
6 64 0000000001000000
7 128 0000000010000000
8 256 0000000100000000
9 512 0000001000000000
10 1024 0000010000000000
11 2048 0000100000000000
12 4096 0001000000000000
13 8192 0010000000000000
14 16384 0100000000000000
15 32768 1000000000000000
Это-же касается и деления числа на степень(2), только сдвигать нужно не влево, а вправо. Например, что-бы моментально перевести некоторое значение из байтов в килобайты, можно применить инструкцию
SHR EAX,10
. При этом даже древний процессор AMD-K7 (Pentium-4) потратит на эту операцию всего 1/3 тактов, не в пример к операции обычного деления DIV
аж 23-такта (см.растактовку Агнера Фога). Правда у этого способа есть один минус – доступно только целочисленное деление, без остатка.1.1 Связка SHR и RCL – зеркальное отражение бит
Интересный эффект можно получить контролируя выдвигаемые инструкцией
SHR
биты, через флаг(CF) процессора – CarryFlag, флаг переноса битов. Есть несколько инструкций, которые используют этот флаг для своих нужд – одной из них является RCL
. Она снимает значение флага(CF) в регистр и сдвигает этот регистр на указанное кол-во бит влево или вправо. Если выдвигая младшие биты вправо устанавливать их старшими слева, то в регистре-приёмнике получим зеркальное отражение оригинальных бит, как на схеме ниже. Область применения – шифрование, без потери информационной ценности (т.е. кол-во разрядов в числе остаётся прежним, только меняется их позиция). Повторив эту операцию ещё раз, можно восстановить оригинальные данные:
Код:
Было: 0x1234 Стало: 0x2c48
--------------------|---------------------
00010010.00110100 <---> 00101100.01001000
Вот как можно организовать подобное зеркало:
C-подобный:
.code
start: mov ebx,12345678h ;// исходное число в регистре
xor eax,eax ;// здесь будет результат
mov ecx,32 ;// кол-во разрядов в числе (длина цикла)
@@: shr ebx,1 ;// выдвинуть из EBX бит вправо - он попадает во-флаг(CF)
rcl eax,1 ;// снять флаг(CF) в EAX, и сдвинуть EAX влево
loop @b ;// повторить цикл ECX-раз..
;//---------------
;// в регистре EAX лежит зеркало от значения 12345678h = 1E6A2C48h
Что-то подобное делает и инструкция
BSWAP
, только она переставляет местами не биты 4-байтного (32-битного) числа, а целиком его байты. Например если в регистре EAX
было значение 12345678h
, то после инструкции BSWAP EAX
получим 78563412h
. Как видим, рассеиваемость бит по сравнению с оригиналом оставляет желать лучшего, хотя вариант тоже имеет право на жизнь. По-сути, эта фурия меняет порядок байт в памяти от "Big-Endian" на "Litle-Endian", чего требует архитектура некоторых процессоров.1.2 Связка SHR и флаг(CF) – преобразование в BIN
По непонятной причине, в системе нет ни одной API-функции, которая могла-бы преобразовать число, в строку двоичного вида. Например, тот-же wsprintf() из Kernel32.dll не имеет спецификатора для BIN, а только OCT, DEC, HEX. В общем случае, в природе для группы функций printf() предусмотрены следующие спецификаторы, которые мы должны вскормить им в первом аргументе:
C-подобный:
%f - плавающая точка fpu,
%u - десятичное беззнаковое целое DEC,
%d - десятичное знаковое целое DEC,
%o - восьмеричное представление OCT,
%x - шестнадцатеричное представление HEX,
%p – выводит указатель,
%c - символьное представление,
%s - строка,
%I64x - 64 битное HEX-число.
%I64u - 64 битное DEC-число.
Для перевода числа в бинарную строку программисты изворачиваются кто-как может, а лично я предпочитаю по-битное выталкивание разрядов числа, с последующим преобразованием их в символы. Если заглянуть в ASCII-таблицу, то в ней наблюдается закономерность, что числа в диапазоне 0..9 отличаются от символьного своего представления ровно на
30h
. То-есть числу(1) соответствует код 31h
, двойке – 32h
, девятке – 39h
. Теперь, при помощи SHR
выталкивая биты вправо, можно следить за состоянием флага(CF) и прибавив к нему 30h
записывать в буфер, для последующего вывода в форточку, или на консоль. Вот пример:
C-подобный:
.data
buff db 0 ;// приёмный буфер, до конца 4К-байтной секции-данных.
;// если обозначить его в самом хвосте данных,
;// то это предотвратит его переполнение.
.code
start:
mov ebx,12345678h ;// число для преобразования в строку BIN
mov ecx,32 ;// кол-во разрядов в числе (32-бит), и длина цикла
mov edi,buff ;// указатель на приёмник для STOSB
clc ;// очистить флаг(CF) – Clear CF
@bin2str: ;// начало цикла..
mov al,30h ;// в дефолте сохранять в буфере символ нуль
shr ebx,1 ;// выдвинуть из числа бит вправо (он запоминается во-флаге CF)
jnc @skip ;// если CF=0 (Jump No Carry) – выдвинулся бит со-значением нуль
inc al ;// иначе: AL+1 и получили символ "1" = 31h
@skip: stosb ;// сохранить его в EDI, и сместить EDI на 1
loop @bin2str ;// промотать цикл ECX-раз..
cinvoke printf,<'%s',0>,buff ;// вывод буфера на консоль, в виде строки "%s"
Если к этому коду прицепить вложенный цикл на 4 или 8 итераций, то можно организовать форматированный бинарный вывод, вставляя после каждой тетрады/байта точку или пробел. Это на порядок повысит читабельность бинарной строки.
1.3 Инструкция AND (лог.умножение(И), конъюнкция)
Остаток от деления любого числа на степень(2) можно получить при помощи логической инструкции обнуления битов
AND
. Второй операнд этой инструкции представляет собой двоичную маску, которая указывает, какие именно биты числа мы хотим сбросить. Обнуляются только те биты исходного числа, которые в маске имеют значение нуль. Допустим имеем в регистре число 18'500 и нужно узнать остаток от деления его на 128.
Для этого, отнимаем от 128 единицу и получаем число 127, двоичное представление которого состоит из шести последовательных единиц:
01111111 = 127
. Теперь применяем эту маску к регистру, в результате чего в нём сбросятся все биты, кроме младших шести: AND EAX,127
. Вот как это выглядит в двоичном виде.. Кстати, не нужно путать остаток, с дробной частью (которую показывает калькулятор). Чтобы в регистрах получить дробную часть, нужно использовать сопр FPU:
C-подобный:
;// Вычисление остатка от 18500/128 логикой
;//----------------------------------------
EAX = число..: 01001000.01000100 = 18500
AND = маска..: 00000000.01111111 = 127
----------------------------------------------------
Остаток в EAX: 00000000.01000100 = 68
;// Вычисление остатка от 385/256 логикой
;//----------------------------------------
EAX = число..: 00000001.10000001 = 385
AND = маска..: 00000000.11111111 = 255
----------------------------------------------------
Остаток в EAX: 00000000.10000001 = 129
Другое применение инструкции
AND
– это выравнивание адреса на границу степени(2).Например, если в регистре
EBX
лежит некоторый указатель, то инструкция AND EBX,1000h
позволит установить его на начало 4-Кбайтной страницы вирт.памяти. В данном случае, эта инструкция сбросит 12-младших бит адреса (три тетрады нулей), а поскольку 12^2=4096, получаем размер страницы 4К. Старшие биты могут быть любыми и выбирают номер конкретной страницы из всего пула, а младшие – смещают указатель на начало, внутри страницы(N).Выравнивание особенно актуально для регистра-указателя на стек
ESP
. Если в управляющем регистре процессора CR0
взведён бит(АМ) и в регистре флагов EFLAGS
выставлен флаг(АС) Alignment Control, то любое обращение к стеку по нечётным адресам будет возбуждать исключение #AC. Кроме того и функция выделения памяти VirtualAlloc() требует выравнивания адреса на 4К-страницу. Здесь и пригодится инструкция AND
, которая за треть такта решит проблему.2. Баг с флагом ZF – определение вендора CPU
Корпорации Intel и AMD – это вечные соперники. Каждый из них имеет свой штат сотрудников и инженеров, которые хоть и воруют иногда идеи друг-у-друга, но всё-же работают в закрытых офисах параллельно. По этой причине, в некоторых вопросах у них получаются разногласия, ухватившись за которые можно определить, продуктом какого именно вендора мы пользуемся.
Более того, человеку присущи ошибки и поскольку разработки ведутся независимо, то соответственно и баги в железяках у них свои. На запрос "Intel CPU bugs" можно выловить в гугле длинную портянку характерных конкретному производителю ошибок, от чего в последующем и плясать.
Одной из таких принципиальных отличий процессоров Intel от AMD является отношение инженеров к флагу(ZF) процессора, который информирует нас о нулевом состоянии какого-либо регистра – Zero Flag. К примеру, если взвести его на процессорах Intel и следом выполнить операцию деления
DIV
, то на выходе из операции Intel сбрасывает флаг(ZF) в нуль, а вот инженеры AMD посчитали иначе и в данном случае не трогают его – он как был взведённым до операции, так и останется после.Если по существу, то продуманы из AMD на мой взгляд здесь правы, поскольку оперируют этим флагом только тогда, когда мы требуем этого явно, ..например проверяем конкретный регистр на нуль инструкцией
OR EAX,EAX
. Тут мы сами говорим процессору, мол "взведи флаг-нуля, если в регистре EAX лежит нуль". Однако непонятно, чем руководствуются инженеры Intel сбрасывая ZF после операции деления? Ведь мы не знаем, какой именно регистр обнулился, хотя флаг ZF об этом сигнализирует.Таким образом имеем сигнатурный штамм, по которому можем вычислить производителя. В примере ниже мы явно взводим флаг(ZF) при обнулении регистра
EDX
инструкцией XOR EDX,EDX
, поэтому оба вендора его исправно устанавливают в единицу. Зато после деления – AMD его игнорирует, а Intel сбрасывает, на чём и палится:
C-подобный:
.data
intel db 'Intel',0
amd db 'AMD',0
.code
start: xor edx,edx ;// обнуляем EDX, устанавливая флаг ZF=1
mov eax,5 ;// EDX:EAX = делимое
mov ebx,2 ;// EBX = делитель
div ebx ;// произвести операцию деления EAX на EBX !!!
mov eax,intel ;// в дефолте выводим INTEL
jz @prn ;// пропустить, если флаг ZF=0 сброшен (Jump if Zero)
mov eax,amd ;// иначе ZF=1 остался взведённым, и поменять мессагу на AMD.
@prn:
cinvoke printf,eax ;// вывод сообщения на консоль!
Способ проверенный и работает на всех современных процессорах, расходуя всего с десяток тактов CPU. Можно сравнить его со-специально предназначенной для идентификации профессоров инструкцией
CPUID
, которая растягивается на несколько тысяч тактов, хотя и возвращает намного больше конфиденциальной инфы о своей тушке. Так-что область применения глюка строго ограничена тупой проверкой вендора.3. Как получить массив случайных чисел "RANDOM"
Перед каждым программером рано или поздно встаёт вопрос генерации случайных чисел, что на буржуйский манер звучит как Random. У мастдая с этим проблемы, и всё-что может предложить нам система – это сишную библиотеку времени исполнения msvcrt.dll (C-Run-Time library, или CRT), внутри которой имеется функция rand(). Она возвращает всего-лишь 16-битное случайное значение в пределах 32767, и то с очень плохим рассеиванием бит, поэтому никак не годится для криптографии.
Правда современные процессоры поддерживают уже инструкцию ассемблера
RDRAND
(опкод 0FC7h), которая организована на уровне микро/архитектуры. Но и в этом случае макс.размер псевдослучайного числа не превышает 8-байт, или 64-бит.Однако мало кто знает, что в Windows имеется своя крипто-система, сосредоточенная в библиотеках Advapi32.dll и Crypt32.dll. Этот мощный механизм известен под общим названием Crypto-API и позволяет генерировать случайные числа без приставки "псевдо", т.к. заточен специально под шифрование. Можно получить криптографически стойкие ключи буквально неограниченного размера, а источниками для энтропии являются дребезжание контактов физической аппаратуры, или-же частотный анализ системы питания. Рассмотрим этот механизм в двух словах..
Работу данной крипто-системы поддерживают несколько провайдеров, которые зарыты в нёдрах оси. Каждый из них предоставляет нам свои услуги и генерирует ключи по своему индивидуальному алгоритму. Соответственно перед тем-как сгенерировать ключ, мы должны выбрать конкретного поставщика при помощи функции CryptAcquireContext(). Эта функция возвращает нам дескриптор, через который мы будем в дальнейшем обращаться к выбранному поставщику.
C-подобный:
BOOL CryptAcquireContextA()
phProv dd 0 ;// указатель на переменную, под дескриптор поставщика
szContainer dd 0 ;// указатель на строку с именем контейнера ключей – ставим нуль
szProvider dd 0 ;// указатель на строку с именем поставщика – ставим нуль
dwProvType dd 0 ;// константа с типом поставщика (провайдера)
dwFlags dd 0 ;// флаг – ставим CRYPT_VERIFY_CONTEXT = 0xF0000000
На рисунке ниже представлена общая схема крипто-системы Win, с краткой информацией о провайдерах. Надеюсь она прояснит ситуацию:
После того-как поставщик выбран (в примере я воспользуюсь услугами первого PROV_RSA_FULL=1), мы запрашиваем у него ключ функцией CryptGenRandom(), не забыв при этом указать необходимую длину ключа. Из таблицы выше видно, что мой провайдер с константой(1) генерит и шифрует ключ по алгоритму RC2 или 4, после чего применяет к нему хеш SHA или MD5 (на своё усмотрение). По окончании, нужно обязательно оповестить поставщика, что мы не нуждаемся более в его услугах. Закрывает открытый дескриптор функция CryptReleaseContext().
Таким образом в буфере получим Random требуемой длины, и теперь нам нужно как-минимум вывести его на консоль. Значит задача – число неизвестной длины перевести в строку соответствующего вида. Для этого, выталкивая при помощи
SHR
из каждого байта ключа по 4-бита, будем использовать эту тетраду как индекс в представляющей HEX-числа символьной строке "0123456789ABCDEF". Макс.значение, которое можно закодировать в четырёх битах как-раз F=16=1111b
, поэтому проблем не будет. Вот пример реализации рандома, с размером ключа 128 байт х8 = 1024 бит. Зачем он такой длинный нужен? Да был-бы рандом, а применение ему обязательно найдётся:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//--------
.data
PROV_RSA_FULL = 1 ;// код провайдера
CRYPT_VER_CONTEXT = 0xF0000000 ;// его флаги
title db 'RANDOM Gen v.0.1',0
capt db 10
db 10,' 128-byte RANDOM value'
db 10,'=======================',10,0
ascii db '0123456789ABCDEF' ;// шаблон перевода в HEX
keyLen = 128 ;// длина RANDOM ключа
handle dd 0 ;// под хэндл провайдера
counter db 3 ;// счётчик вывода на консоль
buff db 0 ;// буфер для HEX-ASCII строки
;//--------
.code
start: invoke SetConsoleTitle,title
;//------ Выбираем поставщика из Advapi32.dll
invoke CryptAcquireContext,handle,0,0,PROV_RSA_FULL,CRYPT_VER_CONTEXT
;//------ Цикл генерации случайных ключей
;// в функцию CryptGenRandom() передаём длину ключа, и адрес приёмного буфера
@getRnd:
cinvoke printf,capt
invoke CryptGenRandom,[handle],keyLen,buff
;//------ Процедура вывода ключа на консоль
;// переводит из HEX в ASCII таблично-индексным методом
;// преобразование идёт в обратном порядке, от хвоста к голове
mov esi,keyLen ;// ESI = индекс хвоста источника
mov edi,keyLen *2 ;// EDI = хвост приёмника
mov [buff + edi],0 ;// ..(вставить туда терминальный нуль)
xor eax,eax ;// EAX = 0
@bin2hex: mov al,[buff +esi -1] ;// AL = очередной байт
shr al,4 ;// оставить ст.тетраду
mov cl,[ascii +eax] ;// взять по ней символ из таблицы
mov [buff +edi -1],cl ;// запомнить символ в EDI
dec edi ;// указатель влево..
mov al,[buff +esi -1] ;// взять тот-же байт
shl al,4 ;// оставить мл.тетраду
shr al,4 ;// ...^^^
mov cl,[ascii +eax] ;// взять по ней символ из таблицы
mov [buff +edi -1],cl ;// запомнить символ в EDI
dec edi ;// сместить указатели влево..
dec esi ;// ...^^^
jnz @bin2hex ;// повторить по всему буферу, пока ESI > 0
;//------ Вывод на консоль и пауза 2-сек.
cinvoke printf,buff
invoke Sleep,2000
dec [counter] ;// уменьшить счётчик повторов RANDOM
jnz @getRnd ;// повторить пока счётчик > 0.
;//------ Освобождаем поставщика по его хэндлу.
invoke CryptReleaseContext,[handle],0
cinvoke gets,buff
cinvoke exit, 0
;//-----------
section '.idata' import data readable
library msvcrt, 'msvcrt.dll',\
advapi32,'advapi32.dll',\
kernel32,'kernel32.dll'
import msvcrt, printf,'printf',gets,'gets',exit,'exit'
include 'api\advapi32.inc'
include 'api\kernel32.inc'
На сайте мелкомягких лежит утверждение, что мол функция CryptGenRandom() уже устарела и не следует использовать её в своих программах. Однако она прекрасно отрабатывает не только на моей Win-7, но даже и на последней десятке Win-10. Поэтому думаю рановато сбрасывать её со-счётов.
Попробуйте изменить длину ключа в константе "keyLen", и получите рандомное значение исполинских размеров, например 2048-байт и больше. Проецируя такой ключ на массив данных, можно организовать блочное шифрование по типу бородатого алгоритма Виженера.
Послесловие...
Что-то букаф много стало для одной статьи, поэтому по мере возможности буду расширять её позже. Если у кого-то имеются интересные на их взгляд алгоритмы и техники, то добро пожаловать под капот. Не обязательно, чтобы это был ассемблер – можно на сишке, или других языках. Лишь-бы с краткими пояснениями сути дела, а дальше реализовать можно на любом сленге. А пока закругляюсь и до скорого..