Эта статья является продолжением статьи про ГОСТ 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;
}
//---------------------------------------------------------------------------
Отметим достоинства и недостатки данного режима.
Достоинства:
- Исключается влияние перекрытия шифра на стойкость шифрования.
- Возможно расшифровать любой блок независимо от его местоположения в криптограмме.
- Простота синхронизации.
Недостатки:
- Статистика сообщения проникает в статистику криптограммы.
- Одна ошибка типа «замена знака» в криптограмме ведет за собой полное разрушение блока при расшифровании.