О реализации этого алгоритма шифрования уже рассказывал FTM: как в общем и целом, так и про режим простой замены. После изучения существующих библиотек и отдельных реализаций этого ГОСТа на C# я решил написать свой велосипед, в первую очередь, ради интереса и опыта. Результатами этой работы мне и хотелось бы поделиться с уважаемым сообществом.
ГОСТ 28147-89 — симметричный блочный алгоритм шифрования с 256-битным ключом, оперирует блоками данных по 64 бита.
Один из режимов его работы, гаммирования с обратной связью, является потоковым режимом блочного шифра.
Обратите внимание, что приведённая последовательность действий справедлива как для шифрования, так и расшифрования. Разница в том, откуда берётся зашифрованный блок текста для обработки следующего блока, это лучше всего видно на картинке:
Данный алгоритм был реализован в форме плагина к менеджеру паролей KeePass.
Исходники доступны на GitHub.
Ниже приведён фрагмент кода класса, реализующего стандартный интерфейс ICryptoTransform, собственно выполняющий криптографическое преобразование данных поблочно. При создании экземпляра в атрибут _state записывается значение синхропосылки, в дальнейшем от направления работы (шифрование или расшифровывание) в него заносится очередной блок зашифрованных данных.
GostECB.Process — реализация того же ГОСТа в режиме простой замены, или «электронной кодовой книги». Хорошее описание алгоритма есть в соответствующем разделе статьи Википедии, а также в статье ГОСТ 28147-89 (Часть 2. Режим простой замены) на Хабрахабре.
Размер снихропосылки, гаммы и «состояния» равен 64 байтам, поэтому шифрование в режиме простой замены можно рассматривать в рамках одного блока. Впрочем, было бы несколько — они просто шифровались бы по очереди.
Для работы с 32-битными частями исходного блока очень удобно использовать тип uint.
Так, в функции F() сложение по модулю ключа и части блока, а также циклический сдвиг на 11 бит запишется просто и лаконично:
Метод подстановки по S-блокам работает с 4-битными кусочками 32-битного подблока, их достаточно удобно отделять побитовым сдвигом и дальнейшим умножением на 0x0f:
Шифрование от расшифровывания в режиме простой замены отличается порядком использования ключей. На самом деле, применительно к режиму гаммирования с обратной связью нам не надо ничего расшифровывать, однако для полноты реализации можно предусмотреть и эту возможность:
ГОСТ 28147-89 — симметричный блочный алгоритм шифрования с 256-битным ключом, оперирует блоками данных по 64 бита.
Один из режимов его работы, гаммирования с обратной связью, является потоковым режимом блочного шифра.
Описание алгоритма
- Исходное сообщение разбивается на блоки по 64 бита
- На каждый блок XOR'ом «накладывается» гамма, тоже длиной 64 бита
- Гамма формируется шифрованием 64-битного блока «состояния» с помощью ключа в режиме простой замены
- В момент начала шифрования сообщения блок принимается равным синхропосылке или вектору инициализации
- В следующей итерации вместо синхропосылки используется зашифрованный блок текста из предыдущей
Обратите внимание, что приведённая последовательность действий справедлива как для шифрования, так и расшифрования. Разница в том, откуда берётся зашифрованный блок текста для обработки следующего блока, это лучше всего видно на картинке:
Реализация
Данный алгоритм был реализован в форме плагина к менеджеру паролей KeePass.
Исходники доступны на GitHub.
Гаммирование с обратной связью
Ниже приведён фрагмент кода класса, реализующего стандартный интерфейс ICryptoTransform, собственно выполняющий криптографическое преобразование данных поблочно. При создании экземпляра в атрибут _state записывается значение синхропосылки, в дальнейшем от направления работы (шифрование или расшифровывание) в него заносится очередной блок зашифрованных данных.
public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
byte[] dataBlock = new byte[inputCount];
byte[] gamma = new byte[GostECB.BlockSize];
byte[] result = new byte[inputCount];
Array.Copy(inputBuffer, inputOffset, dataBlock, 0, inputCount);
gamma = GostECB.Process(_state, _key, GostECB.SBox_CryptoPro_A, true);
result = XOr(dataBlock, gamma);
Array.Copy(result, 0, outputBuffer, outputOffset, inputCount);
Array.Copy(_encrypt ? result : dataBlock, _state, inputCount);
return inputCount;
}
Режим простой замены
GostECB.Process — реализация того же ГОСТа в режиме простой замены, или «электронной кодовой книги». Хорошее описание алгоритма есть в соответствующем разделе статьи Википедии, а также в статье ГОСТ 28147-89 (Часть 2. Режим простой замены) на Хабрахабре.
Размер снихропосылки, гаммы и «состояния» равен 64 байтам, поэтому шифрование в режиме простой замены можно рассматривать в рамках одного блока. Впрочем, было бы несколько — они просто шифровались бы по очереди.
Исходный код метода GostECB.Process
public static byte[] Process(byte[] data, byte[] key, byte[][] sBox, bool encrypt)
{
Debug.Assert(data.Length == BlockSize, "BlockSize must be 64-bit long");
Debug.Assert(key.Length == KeyLength, "Key must be 256-bit long");
var a = BitConverter.ToUInt32(data, 0);
var b = BitConverter.ToUInt32(data, 4);
var subKeys = GetSubKeys(key);
var result = new byte[8];
for (int i = 0; i < 32; i++)
{
var keyIndex = GetKeyIndex(i, encrypt);
var subKey = subKeys[keyIndex];
var fValue = F(a, subKey, sBox);
var round = b ^ fValue;
if (i < 31)
{
b = a;
a = round;
}
else
{
b = round;
}
}
Array.Copy(BitConverter.GetBytes(a), 0, result, 0, 4);
Array.Copy(BitConverter.GetBytes(b), 0, result, 4, 4);
return result;
}
Для работы с 32-битными частями исходного блока очень удобно использовать тип uint.
Так, в функции F() сложение по модулю ключа и части блока, а также циклический сдвиг на 11 бит запишется просто и лаконично:
private static uint F(uint block, uint subKey, byte[][] sBox)
{
block = (block + subKey) % uint.MaxValue;
block = Substitute(block, sBox);
block = (block << 11) | (block >> 21);
return block;
}
Метод подстановки по S-блокам работает с 4-битными кусочками 32-битного подблока, их достаточно удобно отделять побитовым сдвигом и дальнейшим умножением на 0x0f:
private static uint Substitute(uint value, byte[][] sBox)
{
byte index, sBlock;
uint result = 0;
for (int i = 0; i < 8; i++)
{
index = (byte)(value >> (4 * i) & 0x0f);
sBlock = sBox[i][index];
result |= (uint)sBlock << (4 * i);
}
return result;
}
Шифрование от расшифровывания в режиме простой замены отличается порядком использования ключей. На самом деле, применительно к режиму гаммирования с обратной связью нам не надо ничего расшифровывать, однако для полноты реализации можно предусмотреть и эту возможность:
private static int GetKeyIndex(int i, bool encrypt)
{
return encrypt ? (i < 24) ? i % 8 : 7 - (i % 8)
: (i < 8) ? i % 8 : 7 - (i % 8);
}
Источники
- Статья ГОСТ 28147-89 на Википедии
- Описание режимов работы блочных шифров в англоязычной Википедии Block cipher mode of operation
- Исходники реализации ГОСТ 28147-89 Gromila/CryptoAlgorithms на GitHub
- Исходники реализации ГОСТ 28147-89 sftp/gost28147 на GitHub