В этом скромном рецепте мы покажем вам, как можно реализовать шифрование в режиме сцепления блоков шифротекста с помощью нашей маленькой библиотеки 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 и передаем в них исходный файл и заранее созданный файл приемник данных для шифрования/дешифрования.
И все. Пользуйтесь.