Как стать автором
Обновить

Ещё раз о шифровании ГОСТ 28147-89

Время на прочтение3 мин
Количество просмотров103K
О реализации этого алгоритма шифрования уже рассказывал FTM: как в общем и целом, так и про режим простой замены. После изучения существующих библиотек и отдельных реализаций этого ГОСТа на C# я решил написать свой велосипед, в первую очередь, ради интереса и опыта. Результатами этой работы мне и хотелось бы поделиться с уважаемым сообществом.

ГОСТ 28147-89 — симметричный блочный алгоритм шифрования с 256-битным ключом, оперирует блоками данных по 64 бита.
Один из режимов его работы, гаммирования с обратной связью, является потоковым режимом блочного шифра.

Описание алгоритма


  1. Исходное сообщение разбивается на блоки по 64 бита
  2. На каждый блок XOR'ом «накладывается» гамма, тоже длиной 64 бита
  3. Гамма формируется шифрованием 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);
}

Источники


Теги:
Хабы:
Всего голосов 16: ↑13 и ↓3+10
Комментарии12

Публикации

Истории

Работа

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань