Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Практическая криптография алгоритмы и их программирование
.pdf
Глава 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 года обращение, призывающее предлагать алгоритмы для защиты
компьютерных данных от несанкционированного доступа. Эти алгоритмы не должны были использоваться для охраны секретов государственной важности, а должны были быть инструментами защиты для коммерсантов и коммерческих тайн
как средних, так и крупных финансовых корпораций и банков. В то время отклики на обращение НБС в федеральном реестре показали, что интерес к разработке
такого стандарта существует, но открытые сведения по технике шифрования
практически отсутствуют. Конечно же в процессе разработки и принятия стандартов не обошлось без содействия Агентства национальной безопасности (National 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];
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
