Шифрование в режиме CBC с помощью threefish512

В этом скромном рецепте мы покажем вам, как можно реализовать шифрование в режиме сцепления блоков шифротекста с помощью нашей маленькой библиотеки threefish512.

Режим сцепления блоков шифротекста (или CBC — Cipher Block Chaining) — это один из режим использования симметричных блочных шифров с использованием сложения по модулю 2 (операция xor, выполняемая побитово) и обратной связи. Каждый блок открытого текста (т.е того текста, который шифруется) побитово складывается с предыдущим результатом шифрования. Но поскольку для первого блока открытого текста еще нет зашифрованного текста, то сложение первого блока с помощью xor происходит с так называемым вектором инициализации (или изначальным значением — initial value, IV).

Для восприятия может оказаться сложным такое описание, но следующая схема может помочь в понимании:

Схема шифрования блочным сисмметричным шифровом в режиме CBC

Код на D для использования режима CBC в D:

module threefish512file;

private import threefish512;

class ThreeFish512_File
{
    private
    {
        Threefish512 _threefish;
        ulong[8] _iv;

        auto fromByteArray(ubyte[] bytes)
        {
            ulong x = 0;

            x |= ulong(bytes[7]);
            x |= ulong(bytes[6]) << 8;
            x |= ulong(bytes[5]) << 16;
            x |= ulong(bytes[4]) << 24;
            x |= ulong(bytes[3]) << 32;
            x |= ulong(bytes[2]) << 40;
            x |= ulong(bytes[1]) << 48;
            x |= ulong(bytes[0]) << 56;

            return x;
        }

        auto toByteArray(ulong x)
        {
            ubyte[] bytes;

            bytes ~= ubyte((x >> 56) & 0xFF);
            bytes ~= ubyte((x >> 48) & 0xFF);
            bytes ~= ubyte((x >> 40) & 0xFF);
            bytes ~= ubyte((x >> 32) & 0xFF);
            bytes ~= ubyte((x >> 24) & 0xFF);
            bytes ~= ubyte((x >> 16) & 0xFF);
            bytes ~= ubyte((x >> 8) & 0xFF);
            bytes ~= ubyte(x & 0xFF);

            return bytes;
        }

        auto toPaddedArray(ubyte[] bytes)
        {
            if (bytes.length < 64)
            {
                bytes ~= 0x01;
            }

            while (bytes.length < 64)
            {
                bytes ~= 0x00;
            }

            return bytes;
        }

        auto toThreefishBlock(ubyte[] bytes)
        {
            ulong[8] block = 0;
            auto tfblock = toPaddedArray(bytes);

            foreach (i; 0..8)
            {
                block[i] = fromByteArray(tfblock[i*8..i*8+8]);
            }

            return block;
        }

        auto writeBlock(File file, ulong[] block)
        {
            foreach (b; block)
            {
                file.write(
                    cast(char[]) toByteArray(b)
                );
            }
        }

        void truncateFile(File file, ulong size)
        {
            import std.file : readBytes = read, writeBytes = write;
            auto filename = file.name;

            filename.writeBytes(
                filename.readBytes(size)
            );
        }
    }

    void cbc_setup(ulong[8] key, ulong[2] tweak, ulong[8] iv)
    {
        _threefish = new Threefish512;
        _threefish.setup(key, tweak);
        _iv = iv;
    }

    void cbc_crypt(File src, File dst)
    {
        auto iv = _iv;

        foreach (ubyte[] tfbytes; src.chunks(64))
        {
            auto block = toThreefishBlock(tfbytes);
            block[] ^= iv[];
            auto tfblock = _threefish.crypt(block);
            writeBlock(dst, tfblock);
            iv = tfblock;
        }

        auto size = toThreefishBlock(
            toByteArray(src.size)
        );

        size[] ^= iv[];
        auto tfblock = _threefish.crypt(size);
        writeBlock(dst, tfblock);
    }

    void cbc_decrypt(File src, File dst)
    {
        auto iv = _iv;
        ulong size;

        foreach (ubyte[] tfbytes; src.chunks(64))
        {
            auto block = toThreefishBlock(tfbytes);

            auto tfblock = _threefish.decrypt(block);
            tfblock[] ^= iv[];

            writeBlock(dst, tfblock);
            size = tfblock[0];
            iv = block;
        }

        dst.close;
        truncateFile(dst, size);
    }
}

Код практически следует схеме и использует библиотеку threefish512, но тут есть один занимательный момент: из-за того, что сам по себе Threefish блочный шифр, то файл который требуется зашифровать надо разбить на блоки. Разбиение на блоки идет кратно 64 байтам (помните, что значит 512 в имени шифра, да ?) и поскольку не каждый файл имеет в своем размере кратное этой величине количество байт, то существует процедура выравнивания до нужного размера. Из-за этого, требуется записывать где-то размер файла до процедуры выравнивания (точнее, будет сказать процедуры дополнения), более того, необходимо зная размер исходного файла «обрезать» дешифрованный файл, иначе файл будет как бы «поврежден» из-за наличия лишних байтов в конце.

И вот тут мы сталкиваемся с проблемой: надо обрезать файл, но процедуры аналогичной по смыслу функции truncate в некоторых языках в D нет. Поэтому мы создали урезание файла самостоятельно и довольно прямолинейно — и никаких оптимизаций, вроде буферизации тут не предусмотрено, но при желании доработку легко будет провести.

Как вы поняли, использование кода максимально простое: создаем экземпляр класса ThreeFish512_File, инициализируем его ключом, твикинговым значением и вектором инициализации, вызываем методы cbc_crypt/cbc_decrypt и передаем в них исходный файл и заранее созданный файл приемник данных для шифрования/дешифрования.

И все. Пользуйтесь.

Добавить комментарий