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

Код на 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 и передаем в них исходный файл и заранее созданный файл приемник данных для шифрования/дешифрования.
И все. Пользуйтесь.