О реализации этого алгоритма шифрования уже рассказывал FTM: как в общем и целом, так и про режим простой замены. После изучения существующих библиотек и отдельных реализаций этого ГОСТа на C# я решил написать свой велосипед, в первую очередь, ради интереса и опыта. Результатами этой работы мне и хотелось бы поделиться с уважаемым сообществом.
ГОСТ 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[InputBlockSize]; Array.Copy(inputBuffer, inputOffset, dataBlock, 0, inputCount); byte[] processed = GostECB.Process(_state, _key, GostECB.SBox_CryptoPro_A, true); byte[] result = XOr(dataBlock, processed); Array.Copy(result, 0, outputBuffer, outputOffset, inputCount); Array.Copy(_encrypt ? result : dataBlock, _state, GostECB.BlockSize); return inputCount; }
Режим простой замены
GostECB.Process — реализация того же ГОСТа в режиме простой замены, или «электронной кодовой книги». Хорошее описание алгоритма есть в соответствующем разделе статьи Википедии, а также в статье ГОСТ 28147-89 (Часть 2. Режим простой замены) на Хабрахабре.
Размер снихропосылки, гаммы и «состояния» равен 64 байтам, поэтому шифрование в режиме простой замены можно рассматривать в рамках одного блока. Впрочем, было бы несколько — они просто шифровались бы по очереди.
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
ссылка на оригинал статьи http://habrahabr.ru/post/256843/
Добавить комментарий