Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Практическая криптография алгоритмы и их программирование

.pdf
Скачиваний:
0
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
Глава 4. Как устроены современные шифры 91
дополнительный массив памяти и помещать результат в него, мы можем смело применять XOR левой половины left с самим подключом (см. листинг 4.7), затем используя в качестве гаммы сам подключ subkey[i].
Листинг 4.7
subkey[i][1] ^= right[0]; subkey[i][2] ^= right[1]; subkey[i][3] ^= right[2]; subkey[i][4] ^= right[3]; subkey[i][5] ^= right[4]; subkey[i][6] ^= right[5]; subkey[i][7] ^= right[6]; subkey[i][8] ^= right[7];
Первый байт (который является копией второго байта) подключа subkey[i][0] остался без изменения. Он потребуется чуть позже, когда меняются местами по­лубайты гаммы subkey[i][1...9] (см. листинг 4.8). Таким образом, мы ничего не те­ряем по пути и при этом стараемся эффективно использовать память. Конечно, выкраивание пары лишних байтов для программирования современных компьюте­ров может показаться не актуальным. Но мы будем и дальше проделывать это, на­поминая читателю, что реализации криптоалгоритмов должны хорошо работать не только на «персоналках», но и, например, на PIC-процессорах или любых дру­гих аппаратных платах.
Листинг 4.8
// в соответствии с первым байтом подключа // меняем местами полубайты получившейся гаммы for(b=0;b<8;b++)
// если соответствующий бит ключа равен 1 if ((subkey[i][0] >> b)&1!=0){
// меняем местами старший и младший полубайты subkey[i][b+1] =
(subkey[i][b+1] << 4) | (subkey[i][b+1] >> 4);
}
Дотошный читатель, может быть, уже заметил вкравшуюся в рассуждения ошибку. Дело в том, что мы предоставили листинг, реализующий работу алгорит­ма на одном блоке данных. С формальной точки зрения все верно и придраться не к чему, но, если попытаться переделать функцию enc_Lucifer для зашифрования нескольких блоков данных, читатель невооруженным глазом сможет увидеть не­соответствия между парой блоков, зашифрованных раздельно и последовательно. Дело в том, что, вычислив один раз подключи, мы затем в каждом цикле шифрова­ния безнадежно портим их, складывая с левыми половинками и постоянно преоб­разуя. На самом деле это не так плохо и получившийся алгоритм вполне может быть эффективнее оригинала (возможно, даже более криптостойкий), поскольку представляет собой тот же алгоритм Lucifer, но уже в режиме зацепления (анало­гичные схемы см. в разделе «Все режимы шифрования»).
92 Глава 4. Как устроены современные шифры
Кроме того, мы позволили себе немножко оптимизировать реализацию алго­ритма (но не сам алгоритм!) в нескольких узких местах. Обратите внимание на то, что в алгоритме на самом деле не переставляются местами левая и правая по­ловины блока, меняются лишь указатели на них. Тем не менее мы оставили очень медленную и громоздкую реализацию перестановки битов без изменения. На са­мом деле в ряде случаев можно придумать способы реализовать ее очень быстро.
Для простоты понимания представим себе упрощенную версию «бутерброда» из P-блоков и S-блоков. Пусть фиксированный P-áëîê è S-блок будут для начала одного размера и будут не очень большими (рис. 4.9).
x1x2x3S-áëîê(x) x1x2x3 P-áëîê(xi)
000100 123321 001111 010001 011010 100110 101101 110011 111000
Ðèñ. 4.9. Узел замены и блок перестановки
Посмотрим на результат их взаимодействия в двух случаях — при действии на данные сначала P-блока, затем S-блока и наоборот. В первом случае если x = = 101, òî P(x) = 011, à S(P(x)) = S(011) = 010. Используя данные для всевозмож­ных значений x, попробуем построить таблицу SP-блока, которая осуществляла бы сразу перестановку и подстановку одновременно (рис. 4.10). Сразу заметим, что, вообще говоря, PS-блоки ни в коем случае не идентичны SP-блокам, то есть
P°S = P(S(x)) != S(P(x)) = S°P. Это существенное замечание.
õ1x2x
000000100 001010001 010100110 011110011 100001111 101011010 110101101 111111000
Ðèñ. 4.10. Узел одновременной перестановки и замены
P(x) SP-áëîê(x)
3
Глава 4. Как устроены современные шифры 93
Аналогичную таблицу можно подсчитать для PS-блока (рис 4.11). В нем к данным сначала применяется подстановка S-блока, а затем результат перемеши­вается перестановкой P-блока.
x1x2x
000100001 001111111 010001010 011010100 100110101 101101011 110011110 111000000
Ðèñ. 4.11. Узел одновременной замены и перестановки
S(x) PS-áëîê(x)
3
Таким образом, однажды подсчитав таблицы SP-блоков и PS-блоков, мы из­бавляем себя от необходимости использовать очень медленную перестановку би­тов в цикле так, как это было сделано в листинге алгоритма Lucifer (см. эту часть в листинге 4.9).
Листинг 4.9
// переставляем биты в p memset(p, 0, 8); for(j=0;j<8;j++) {
for(b=0;b<8;b++) {
s = P_box[j*8 + b]; p[s / 8] ^= ((subkey[i][j] >> b) & 1) << (s % 8);
}
}
Здесь, перестановка с помощью P-блока реализована в виде последователь­ной перестановки каждого из битов блока в цикле. Побитовые операции не явля­ются очень быстрыми практически ни на одном существующем сегодня процессо­ре. С этим приходится мириться и придумывать способы осуществлять неслож­ную операцию перестановки битов на существующих аппаратных платформах другими, более эффективными путями.
Одним из вариантов такой реализации является метод с использованием своего рода изомофорной операции — все операции проводятся не на битовых, а на байтовых или пословных операндах. Для этого 128-битовый блок данных пред­ставляется в виде массива из 128 целых байтовых или пословных элементов. Вы­бор слова в качестве элемента массива обусловлен тем, что архитектура совре­менных процессоров предназначена для более эффективной работы со словами, чем с байтами или полубайтами. Если использовать такой подход, то участок кода в листинге 4.9 можно переписать заново (см. листинг 4.10).
94 Глава 4. Как устроены современные шифры
Листинг 4.10
uint8_t S_mask[8] = { 128, 64, 32, 16, 8, 4, 2, 1 }; uint32_t block_bits[128]; // элементы-псевдобиты uint32_t block_bits_d[128]; // копия для перестановки ... // заполняем block_bits for (j = 0; j < 32; j++)
for(b=0;b<8;b++)
block_bits[(j << 3) + b] = (subkey[j] >> b) & 0x1;
// быстро переставляем for (j = 0; j < 128; j++)
block_bits_d[j] = block_buts[P_box[j]];
// возвращаем биты на место for (j = 0; j < 32; j++)
for(b=0;b<8;b++)
subkey[j] |= (block_bits[(j << 3) + b] << S_mask[b]);
Однако и этот вариант реализации алгоритма существенно медленнее, чем если бы мы использовали заранее заготовленные таблицы замены-перестановки.
Листинг 4.11
// заранее вычисленные таблицы двух PS-блоков: P+S0 и P+S1 uint8_t PS0[256] = {
87, 21,117, 54, 23, 55, 20, 84,116,118, 22, 53, 85,119, 52, 86, 223,157,253,190,159,191,156,220,252,254,158,189,221,255,188,222, 207,141,237,174,143,175,140,204,236,238,142,173,205,239,172,206, 211,145,241,178,147,179,144,208,240,242,146,177,209,243,176,210, 215,149,245,182,151,183,148,212,244,246,150,181,213,247,180,214,
95, 29,125, 62, 31, 63, 28, 92,124,126, 30, 61, 93,127, 60, 94, 219,153,249,186,155,187,152,216,248,250,154,185,217,251,184,218,
67, 1, 97, 34, 3, 35, 0, 64, 96, 98, 2, 33, 65, 99, 32, 66, 195,129,225,162,131,163,128,192,224,226,130,161,193,227,160,194, 199,133,229,166,135,167,132,196,228,230,134,165,197,231,164,198, 203,137,233,170,139,171,136,200,232,234,138,169,201,235,168,202,
75, 9,105, 42, 11, 43, 8, 72,104,106, 10, 41, 73,107, 40, 74,
91, 25,121, 58, 27, 59, 24, 88,120,122, 26, 57, 89,123, 56, 90,
71, 5,101, 38, 7, 39, 4, 68,100,102, 6, 37, 69,103, 36, 70,
79, 13,109, 46, 15, 47, 12, 76,108,110, 14, 45, 77,111, 44, 78,
83, 17,113, 50, 19, 51, 16, 80,112,114, 18, 49, 81,115, 48, 82
};
uint8_t PS1[256] = {
87,223,207,211,215, 95,219, 67,195,199,203, 75, 91, 71, 79, 83,
21,157,141,145,149, 29,153, 1,129,133,137, 9, 25, 5, 13, 17, 117,253,237,241,245,125,249, 97,225,229,233,105,121,101,109,113,
54,190,174,178,182, 62,186, 34,162,166,170, 42, 58, 38, 46, 50,
23,159,143,147,151, 31,155, 3,131,135,139, 11, 27, 7, 15, 19,
55,191,175,179,183, 63,187, 35,163,167,171, 43, 59, 39, 47, 51,
20,156,140,144,148, 28,152, 0,128,132,136, 8, 24, 4, 12, 16,
84,220,204,208,212, 92,216, 64,192,196,200, 72, 88, 68, 76, 80,
Глава 4. Как устроены современные шифры 95
116,252,236,240,244,124,248, 96,224,228,232,104,120,100,108,112, 118,254,238,242,246,126,250, 98,226,230,234,106,122,102,110,114,
22,158,142,146,150, 30,154, 2,130,134,138, 10, 26, 6, 14, 18,
53,189,173,177,181, 61,185, 33,161,165,169, 41, 57, 37, 45, 49,
85,221,205,209,213, 93,217, 65,193,197,201, 73, 89, 69, 77, 81, 119,255,239,243,247,127,251, 99,227,231,235,107,123,103,111,115,
52,188,172,176,180, 60,184, 32,160,164,168, 40, 56, 36, 44, 48,
86,222,206,210,214, 94,218, 66,194,198,202, 74, 90, 70, 78, 82
};
uint8_t S_mask[8] = { 128, 64, 32, 16, 8, 4, 2, 1 };
// в процедуре void enc_Lucifer(uint8_t *data, uint8_t *key): ...
uint8_t *pS_mask = S_mask;
... for(j=0;j<8;j++) {
if (subkey[i][0] & *pS_mask++ )
val = PS1[right[j] & 0xFF];
else
val = PS0[right[j] & 0xFF];
}
...
Например, для шифра Lucifer можно заменить медленный цикл с помощью управляемых ключом подстановок из двух таблиц (см. листинг 4.11). Там же не­много ускорено управление в зависимости от битов ключа, введя дополнительную таблицу, которая содержала бы все те значения битов, при которых используется, и осуществляя сравнение битов по этой таблице. В листинге она представлена как S_mask, а указатель на нее pS_mask. Для более быстрой адресации к элемен­там таблицы используется указатель pS_mask, который после каждого обраще­ния просто сдвигается по таблице к следующему элементу простым инкременти­рованием pS_mask++.
Первыe стандарты шифрования
Многообразие различных криптосистем, способов и методов защиты инфор­мации от несанкционированного доступа и ознакомления безусловно должно было привести к какому-то единому стандарту. Вдобавок к концу 60-х годов и в отечественных «коридорах власти», и в зарубежных правительственных структу­рах уже вовсю использовали свои негласные стандарты. В довольно либеральной экономической политике США методы шифрования могли и должны были занять одну из ключевых позиций. Поэтому Национальное бюро стандартов (National Bureau of Standards) ных на основе различных публикаций в открытой прессе и доступных научных материалов, опубликовав в сборнике федерального реестра Соединенных Штатов
17
Национальное бюро стандартов стало впоследствии (и остается до сих пор) Националь
ным институтом стандартов и технологий США (National Institute of Standards and Technology).
17
США приступило к разработке стандарта шифрования дан-
-
96 Глава 4. Как устроены современные шифры
в мае 1973 года обращение, призывающее предлагать алгоритмы для защиты компьютерных данных от несанкционированного доступа. Эти алгоритмы не дол­жны были использоваться для охраны секретов государственной важности, а дол­жны были быть инструментами защиты для коммерсантов и коммерческих тайн как средних, так и крупных финансовых корпораций и банков. В то время откли­ки на обращение НБС в федеральном реестре показали, что интерес к разработке такого стандарта существует, но открытые сведения по технике шифрования практически отсутствуют. Конечно же в процессе разработки и принятия стан­дартов не обошлось без содействия Агентства национальной безопасности (Natio­nal Security Agency) — государственного органа США, контролирующего всю криптографическую деятельность в США.
Фирма IBM предоставила свой криптографический алгоритм НБС. НБС тут же обратилось с просьбами к АНБ оценить алгоритм по отношению к потенциа­льному набору требований и одновременно к IBM — рассмотреть вопрос о пре­доставлении неисключительных прав (без лицензионной платы) на изготовле­ние, использование и продажу устройств, реализующих данный алгоритм. Эти просьбы привели к многочисленным переговорам между НБС и обеими органи­зациями.
Спустя почти два года, в марте 1975 года, НБС поместило в сборник федера­льного реестра сразу два сообщения. В первом был полностью опубликован пред­лагаемый «Алгоритм шифрования для защиты данных ЭВМ», указывая на то, что он удовлетворяет основным техническим требованиям к алгоритму, соответству­ющему стандарту шифрования данных.
Кроме того, в этом же сообщении содержалась информация, что некото­рые американские патенты и патенты других стран могут содержать пункты формулы изобретения, которые могут распространиться на реализацию и испо­льзование этого алгоритма, и что связанные с ним криптографические устрой­ства и технические данные могут подлежать экспортному контролю со сторо­ны АНБ. Во втором сообщении отмечалось, что фирма IBM предоставит запра­шиваемые неисключительные права без лицензионной платы при условии, что Министерство торговли США к 1 сентября 1976 года утвердит стандарт шиф­рования данных.
И наконец, в январе 1977 года предложенная криптографическая система была принята Национальным бюро стандартов в качестве государственного стан­дарта шифрования США. Сама система получила название Стандарта шифрова­ния данных (Data Encryption Standard).
Новая криптосистема оказалась весьма похожа на разработку Файстеля — шифр Люцифер, — которую IBM предлагала НБС в качестве ответа на первое обращение в федеральный реестр. Можно даже сказать, дьявольски похожа. Однако АНБ пожелало иметь не 128-битный алгоритм шифрования, подобный Люциферу, а 56-битный DES, резюмируя свое пожелание новыми требованиями к стандарту. Впрочем, этого и следовало ожидать. Люцифер стал первым пред­ставителем семейства шифров, построенных с помощью архитектуры сети Фай­стеля. В соответствии с требованиями АНБ новый (на то время) стандарт созда­вался так же, как и Люцифер, в лабораториях IBM, правда, уже под руководст
-
Глава 4. Как устроены современные шифры 97
вом доктора Уолтера Тачмена (Walter Tuchman), который возглавил группу разработчиков криптографических систем в начале 70-х годов. Результатом рабо­ты этой группы стало несколько публикаций в прессе, несколько патентов, крип­тографических устройств и алгоритмов.
В алгоритме, принятом в итоге в качестве государственного стандарта, испо­льзуется структура, немного отличная от подобной в шифре Люцифер, но и она также базируется на применении блочных узлов замен и битовых перестановках. АНБ предложило две существенные модификации Люцифера. Первая сводилась к изменению длины ключа и сответствующей поцедуре его разворачивания. Вторая модификация была также несомненно существенной — узлы замены, использо­вавшиеся в Люцифере, были полностью переработаны. Однако общая схема алго­ритма осталась прежней.
По сравнению с Люцифером в DES сильно изменен изначально слабый алгоритм разворачивания ключа, который повторял каждый бит ключа четы­ре раза, не производя никаких дополнительных изменений. В DES этот про­цесс явно сложнее, хотя и здесь не обошлось без недостатков, — слишком маленькую длину ключа начали критиковать почти сразу же после принятия стандарта.
Алгоритм разворачивания ключей генерирует несколько подключей — столь­ко же, сколько числов раундов в алгоритме зашифрования данных. В классиче­ском варианте, принятом в качестве стандарта, число раундов равно 16, соответ­ственно и процедура данного алгоритма разворачивала ключ на 16 различных подключей, при этом для разворачивания ключа не используются восемь различ­ных битов исходного ключа, таким образом, результирующие подключи имеют длину в 48 битов и не зависят от исходного ключа. Изначально предполагалось, что эти биты смогут использоваться для контроля четности битов в ключе. Поэто­му, говоря о длине ключа, необходимо помнить, что действительная зависимость существует только лишь от 56, а не от 64 битов, и потому DES называют 56-бит­ным алгоритмом шифрования.
Перед началом работы ключ подвергается процедуре перестановки битов по фиксированной таблице (см. листинг 4.12 — массив pc1). Как видно из нее, не об­рабатываются биты 8, 16, 25, 32, 40, 48, 56 и 64, то есть самые младшие биты каждого байта ключа.
Листинг 4.12
/* выборочная таблица для ключа */ uint8_t pc1[56] = {
57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4
};
98 Глава 4. Как устроены современные шифры
/* длина сдвига */ uint8_t rotate[16] = {
1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28
};
/* номера битов */ int bytebit[8] = { 128, 64, 32, 16, 8, 4, 2, 1 };
/* выборочная таблица */ uint8_t pc2[48] = {
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32
};
Ключ разбивается на две последовательности. Первые 28 элементов массива pc1 определяют, как выбирается первая последовательность, вторые — как выби­рается вторая последовательность. Например, в первую попадут биты 57, 49, 41, ..., 36, а во вторую только 63, 55, 47, ..., 4 биты ключа.
После выбора соответствующих битов получившиеся последовательности преобразовывают 16 раз определенным набором операции, на ходу получая не­сколько аналогичных им рекурсивных последовательностей, — это и будут под­ключи.
В зависимости от номера итерации обе последовательности циклически сдви­гаются на 1 или 2 бита влево, затем преобразовываются с помощью еще одной (аналогичной pc1) битовой выборки-перестановки pc2. Получившиеся 48 битов входят в состав очередного подключа.
Процедура, реализующая инициализацию подключевых элементов, продемон­стрирована в листинге 4.13.
Листинг 4.13
... // структура для хранения выработанных подключей typedef struct {
uint32_t subkey[16][2];
} DES_KEY;
// разворачиваем ключ void setup_DES(uint8_t *pass, DES_KEY *key, bool encrypt) {
uint8_t pc1m[56]; uint8_t pcr[56]; uint8_t shedule[8]; uint32_t i, j, l; int m;
Глава 4. Как устроены современные шифры 99
// конвертируем таблицу pc1 в биты для ключа
for (j = 0; j < 56; j++) { // в какой именно бит записывать l = pc1[j];
// номер бита в байте
m=l&0x7;
// записываем единицу или ноль в зависимости от этого бита
pc1m[j] = (pass[l >> 3] & bytebit[m])?1:0;
}
for (i = 0; i < 16; i++) {
memset(shedule, 0, sizeof(shedule));
for (j = 0; j < 56; j++) { l=j+rotate[encrypt?i:15-i];
pcr[j] = pc1m[ (l < (j < 28 ? 28 : 56))?l:l-28 ];
}
// правые и левые части вращаем независимо
for (j = 0; j < 48; j++)
if (pcr[ pc2[j]-1 ]) {
// устанавливаем биты в "развороте" ключа l=j%6; shedule[ j/6 ] |= bytebit[l] >> 2;
}
// теперь заполняем итерационные подключи key->subkey[i][0] = ((uint32_t) shedule[0] << 24) | ((uint32_t) shedule[2] << 16) | ((uint32_t) shedule[4] << 8) | ((uint32_t) shedule[6]); key->subkey[i][1] = ((uint32_t) shedule[1] << 24) | ((uint32_t) shedule[3] << 16) | ((uint32_t) shedule[5] << 8) | ((uint32_t) shedule[7]);
}
}
...
После начальной перестановки блок данных делится на две равные половины (работа алгоритма представляет собой работу сбалансированной сети Файстеля) длиной по 32 бита каждая. Затем над двумя этими половинками производятся со­ответствующие сети Файстеля операции с помощью функции усложнения F. Для этого на каждом раунде шифрования правая (старшая) половина блока побитово складывается (по модулю 2) с результатом функции гаммирования F от младшей половины шифруемого блока и 48-битового подключа. Между раундами половины блока меняются местами.
Для реализации функции гаммирования мы поступим весьма популярным об­разом — опишем ее в качестве макроса на языке Cи следующим способом (см. ли­стинг 4.14):
100 Глава 4. Как устроены современные шифры
Листинг 4.14
// все переменные должны быть 32-битовыми // дополнительно требуется временная переменная temp #define F(l, r, key){ \
temp = ((r >> 4) | (r << 28)) ^ key[0]; \ l ^= SP_box[6][temp & 0x3F]; \ l ^= SP_box[4][(temp >> 8) & 0x3F]; \ l ^= SP_box[2][(temp >> 16) & 0x3F]; \ l ^= SP_box[0][(temp >> 24) & 0x3F]; \ temp=r^key[1]; \ l ^= SP_box[7][temp & 0x3F]; \ l ^= SP_box[5][(temp >> 8) & 0x3F]; \ l ^= SP_box[3][(temp >> 16) & 0x3F]; \ l ^= SP_box[1][(temp >> 24) & 0x3F]; \
}
Делается это для того, чтобы, не усложняя алгоритм, было бы как можно про­ще его описывать и при этом не терять скорости на накладных расходах по вызову дополнительных функций. Если в классическом варианте, описывая функцию шифрования, нам пришлось бы вызывать функцию F_func, передавая ей (скорее всего, через медленный стек) несколько параметров, то в варианте листинга 4.14 этого делать не нужно. Вместо этого после компиляции мы получаем разверну­тый быстрый код, который в исходном тексте выглядит так же наглядно и понят­но, как и вызовы функций.
В листинге 4.14 можно провести небольшую модификацию, добавив «оберт­ку» из оператора do {} while (0) (см. листинг 4.15). Такая странная конструкция часто встречается в исходных текстах сложных и критичных по времени и надеж­ности исполнения систем. Например, макросами изобилуют исходные тексты ядер операционных систем FreeBSD и Linux.
Смысл ее использования заключается в переносимости подобного участка кода и его независимости от окружающих макросов в исходном коде операторов. Предположим, что мы не используем do {} while (0), описывая макрос листинга
4.14 без них. Если бы мы использовали условный оператор, например, в такой конфигурации:
if (left != right)
F(left, right, key);
else
next operators
то выполнен (если вообще будет скомпилирован) он будет, скорее всего, не­правильно. Дело в том, что компилятор развернет указанный участок кода в сле­дующий:
if (left != right)
temp = ((r >> 4) | (r << 28)) ^ key[0];
l ^= SP_box[6][temp & 0x3F]; l ^= SP_box[4][(temp >> 8) & 0x3F]; l ^= SP_box[2][(temp >> 16) & 0x3F]; l ^= SP_box[0][(temp >> 24) & 0x3F];
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]