Как известно, процессор не обеспечивает математику длинных чисел. На данный момент, процессоры работают с числами длиной 64 бита. В языке C это соответствует типу long long. В криптографии необходимо работать с числами намного большей длины. Имеется два способа решения данной проблемы: использовать существующие сторонние библиотеки, либо написать все самому. Компания Extra Systems в данном продукте (как обычно мы это и делаем) выбрала второй вариант. В системе шифросвязи Extra Systems Cypher Net работает только код, написанный нами.
Для обработки длинных чисел Extra Systems Cypher Net использует массивы типа unsigned char такой длины, чтобы в них помещались числа необходимого размера. Существует два формата хранения: обычный и упакованный. В упакованном формате используются все биты, а в обычном - только половина. Таким образом, в обычном формате каждый байт содержит одну шестнадцатиричную цифру - четыре бита. Длина упакованных чисел определяется константой PACKED_VALUE_LENTH. Если мы работаем с 1024-битными ключами, то математика должна быть 2048-битная (поскольку произведение может содержать в два раза больше цифр, чем сомножители). В байте содержится 8 бит, следовательно значение параметра PACKED_VALUE_LENTH равно 256 (2048 делить на 8). А VALUE_LENTH будет в два раза больше, то есть 512.
Упаковку и распаковку длинных чисел производят процедуры pack_value и unpack_value:
void pack_value(unsigned char *value, unsigned char *packed_value)
{
unsigned char x, y;
unsigned int i = 0, j = 0;
memset(packed_value, 0, PACKED_VALUE_LENTH);
while (i < PACKED_VALUE_LENTH)
{
x = value[j++];
y = value[j++];
packed_value[i++] = x | (y << 4);
}
}
void unpack_value(unsigned char *packed_value, unsigned char *value)
{
unsigned char cur_char;
unsigned int i = 0, j = 0;
clear_value(value);
while (i < PACKED_VALUE_LENTH)
{
cur_char = packed_value[i++];
value[j++] = cur_char & 0xF;
value[j++] = cur_char >> 4;
}
}
Также приведем в качестве примера процедуру вычитания длинных чисел:
void value_sub(signed char *operand1, signed char *operand2)
{ //operand1 -= operand2
int i;
for (i = 0; i < VALUE_LENTH; i++)
{
*(operand1 + i) -= *(operand2 + i);
if (*(operand1 + i) < 0)
{
*(operand1 + i) += RADIX;
if ((i + 1) < VALUE_LENTH) *(operand1 + i + 1) -= 1;
}
}
}
Другие процедуры (умножение, деление и т.п.) мы здесь не приводим, поскольку к криптографии они (равно как и опубликованная выше процедура вычитания) никакого отношения не имеют.
Разумеется, приводимые выше алгоритмы имеют отношение к реализации системы без использования вставок на ассемблере. При использовании таких вставок положение дел меняется радикальным образом. В ассемблерной реализации операции упаковки и распаковки сводятся к тождественному преобразованию:
void pack_value(void *value, void *packed_value) { memcpy(packed_value, value, VALUE_LENTH); }
void unpack_value(void *packed_value, void *value) { memcpy(value, packed_value, VALUE_LENTH); }
А константы VALUE_LENTH и PACKED_VALUE_LENTH в нашей реализации длинной математики на языке ассемблера имеют одно и то же значение (256 при 1024-битных ключах). Это связано с тем, что ассемблерные модули не используют никакого "разрежения" битов, а хранение информации производится плотно, с использованием всех бит.
В настоящее время мы используем 64-битный код, поэтому единицей обработки длинных чисел является объект размером 8 байт. Фактически, наша ассемблерная арифметика использует 64-битные регистры в качестве цифр, так что "основание системы исчисления" у нас в ассемблере равно 2 в степени 64. В качестве примера продемонстрируем наш исходный код на ассемблере процедуры инкремента длинного числа (адрес операнда передается сюда в регистре rsi):
;======================================================
WORD_SIZE EQU 8
LONG_INT_LENTH EQU VALUE_LENTH / WORD_SIZE
;======================================================
SECTION .text
;======================================================
ii_inc_long_int: mov rcx,LONG_INT_LENTH
mov rax,1
mov rdx,0
.m10: add [rsi+rdx*WORD_SIZE],rax
jnc .fin
inc rdx
loop .m10
.fin: ret
;======================================================
Таким образом, на ассемблере, в случае 1024-битных ключей RSA, обрабатываются числа всего лишь из 32-х (параметр LONG_INT_LENTH) "знаков" (каждый из которых для данной архитектуры представляет собой машинное слово длиной 64 бита). Это, конечно, дает существенный прирост производительности по сравнению с модулями длинной математики на языке C (которые работают со словами длиной всего лишь 4 бита, и не имея при этом к тому же, в отличие от ассемблера, аппаратной поддержки контроля переполнения).
Контент этой страницы доступен также на английском, французском, немецком, португальском, испанском, итальянском и украинском языке.
| © Extra Systems, 2024 |
|