Эта статья является продолжением статьи про ГОСТ 28147-89. Как уже говорилось ранее, ГОСТ 28147-89 поддерживает четыре режима работы, но, пожалуй, главным из них является режим простой замены, который используется как самостоятельно, так и как составная часть других режимов.
В статье приведен код на c++, реализующий данный режим.
Чтобы понять, как работает ГОСТ 28147-89 в данном режиме, необходимо рассмотреть следующую схему:Собственно, суть
Зашифрование
- Открытые данные разбиваются на блоки по 64 бита.
- Далее производится ввод первого блока в накопители N1 и N2. При этом биты открытой информации вводятся следующим образом: 1-й бит открытой информации — в 1-й разряд накопителя N1, ..., 32-й — в 32-й разряд накопителя N1, 33-й — в 1-й разряд накопителя N2 и так далее, пока 64-й бит открытой информации не будет введен в 32-й разряд накопителя N2.
- В КЗУ вводится ключ длиной 256 бит способом, рассмотренным в статье ГОСТ 28147-89 (Часть 1. Введение и общие принципы).
Производится зашифрование открытых данных в режиме простой замены (в 32 цикла):
- В первом цикле содержимое регистра N1 суммируется с заполнением X0 из КЗУ по модулю 232 в сумматоре СМ1.
- В блоке подстановки K производится замена 32 бит информации, поступившей из сумматоре СМ1 по правилам, рассмотренным в первой статье.
- В регистре сдвига R осуществляется циклический сдвиг на 11 в сторону старшего разряда.
- Информация с регистра сдвига R и накопителя N2 суммируется по модулю 2 в сумматоре СМ2.
- Старое заполнение накопителя N1 переписывается в накопитель N2.
- Результат с выхода сумматора СМ2 переписывается в накопитель N1.
- Первый цикл заканчивается.
- Последующие циклы аналогичны первому, с тем лишь отличием, что во 2-м цикле вводится ключ X1, в 8-м — X7, в 9-м — X0 и так далее в том же порядке до 24 цикла. С 25 по 32 цикл ключ вводится в обратном порядке: X7 — в 25-м, X0 — в 32-м.
- После 32-го цикла в N1 информация сохраняется, а вот результат с выхода сумматора СМ2 переписывается в N2.
- Заполнение N1 и N2 и есть первый блок зашифрованных данных.
- Следующие блоки зашифровываются аналогично.
Расшифрование
Расшифрование осуществляется по тому же алгоритму, что и зашифрование, только на вход накопителей N1 и N2 поступают разбитые на блоки по 64 бита зашифрованные данные.Важным отличием является еще и то, что в прямом порядке (с X0 по X7) ключ вводится только в первых 8 циклах РПЗ, в остальных — в обратном (с X7 по X0).А так, после прохождения 32 циклов в накопителях N1 и N2 содержатся блоки открытых данных.Поясним все вышесказанное кодом на C++
Данный код писался в консольном режиме в C++ Builder 6 достаточно давно и не претендует на звание самого лучшего, так что не бейте сильно, оптимизировать нет времени. Просто, он работает, причем, довольно быстро.gost28147.cpp
//--------------------------------------------------------------------------- #include <vcl.h> #include <stdio.h> #include <conio.h> //--------------------------------------------------------------------------- // взято из хелпа, определяем размер файла long filesize(FILE *stream) { long curpos, length; curpos = ftell(stream); fseek(stream, 0L, SEEK_END); length = ftell(stream); fseek(stream, curpos, SEEK_SET); return length; } // функция, реализующая работу ГОСТ 28147-89 в режиме простой замены void rpz(int rezh, char* opener, char* saver) { FILE *f_begin, *f_end; // потоки для исходного и конечного файлов // таблица замен byte Tab_Z[8][16] = { 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF }; // ключик unsigned long key[8] = { 0x0123, 0x4567, 0x89AB, 0xCDEF, 0x0123, 0x4567, 0x89AB, 0xCDEF }; char N[4]; // 32-разрядный накопитель, unsigned long n1=0, n2=0, SUM232=0; // накопители N1, N2, и сумматор // открываем файлы f_begin = fopen (opener,"rb"); f_end = fopen (saver,"wb"); // определим количество блоков float blokoff; blokoff = 8*filesize(f_begin); blokoff = blokoff/64; int block = blokoff; if (blokoff-block>0) block++; int sh; if (filesize(f_begin)>=4) sh = 4; else sh = filesize(f_begin); int sh1 = 0; int flag=0; // начнем считывание и преобразование блоков // присутствуют проверки на полноту блоков, чтобы считать только нужное количество бит for (int i=0; i<block; i++) { // записываем в накопитель N1 for (int q=0; q<4; q++) *((byte *)&N+q) = 0x00; if ((sh1+sh)<filesize(f_begin)) { fread (N,sh,1,f_begin); sh1+=sh; } else { sh=filesize(f_begin)-sh1; fread (N,sh,1,f_begin); flag=1; } n1 = *((unsigned long *)&N); // записываем в накопитель N2 for (int q=0; q<4; q++) *((byte *)&N+q) = 0x00; if ((sh1+sh)<filesize(f_begin)) { fread (N,sh,1,f_begin); sh1+=sh; } else { if (flag==0) { sh=filesize(f_begin)-sh1; fread (N,sh,1,f_begin); } } n2 = *((unsigned long *)&N); // 32 цикла простой замены // ключ считываем в требуемом ГОСТом порядке int c = 0; for (int k=0; k<32; k++) { if (rezh==1) { if (k==24) c = 7; } else { if (k==8) c = 7; } // суммируем в сумматоре СМ1 SUM232 = key[c] + n1; // заменяем по таблице замен byte first_byte=0,second_byte=0,zam_symbol=0; int n = 7; for (int q=3; q>=0; q--) { zam_symbol = *((byte *)&SUM232+q); first_byte = (zam_symbol & 0xF0) >> 4; second_byte = (zam_symbol & 0x0F); first_byte = Tab_Z[n][first_byte]; n--; second_byte = Tab_Z[n][second_byte]; n--; zam_symbol = (first_byte << 4) | second_byte; *((byte *)&SUM232+q) = zam_symbol; } SUM232 = (SUM232<<11)|(SUM232>>21); // циклический сдвиг на 11 SUM232 = n2^SUM232; // складываем в сумматоре СМ2 if (k<31) { n2 = n1; n1 = SUM232; } if (rezh==1) { if (k<24) { c++; if (c>7) c = 0; } else { c--; if (c<0) c = 7; } } else { if (k<8) { c++; if (c>7) c = 0; } else { c--; if (c<0) c = 7; } } } n2 = SUM232; // вывод результата в файл char sym_rez; for (int q=0; q<=3; q++) { sym_rez = *((byte *)&n1+q); fprintf(f_end, "%c", sym_rez); } for (int q=0; q<=3; q++) { sym_rez = *((byte *)&n2+q); fprintf(f_end, "%c", sym_rez); } } fclose (f_begin); fclose (f_end); } //--------------------------------------------------------------------------- int main() { // выбираем шифрование или расшифрование int rezhim = 0; do { printf("Выберите режим работы:\nШифрование - 1\nРасшифрование - 2\n"); scanf("%d", &rezhim); } while ((rezhim!=1)&&(rezhim!=2)); // повторяем до тех пор, пока не будет введено 1 или // выбираем исходный и конечный файлы (слэш '\' в пути писать как '\\') char open_str[50], save_str[50]; printf("\nВведите путь до исходного файла\n"); scanf("%s", &open_str); printf("\nВведите путь до файла, в который требуется записать результат\n"); scanf("%s", &save_str); rpz(rezhim, open_str, save_str); // запускаем РПЗ return 0; } //---------------------------------------------------------------------------
Отметим достоинства и недостатки данного режима.
Достоинства:
- Исключается влияние перекрытия шифра на стойкость шифрования.
- Возможно расшифровать любой блок независимо от его местоположения в криптограмме.
- Простота синхронизации.
Недостатки:
- Статистика сообщения проникает в статистику криптограммы.
- Одна ошибка типа «замена знака» в криптограмме ведет за собой полное разрушение блока при расшифровании.