В этой статье мы покажем реализацию симметричного блочного криптографического алгоритма Threefish на языке программирования D, реализацию функций настройки алгоритма, шифрования и дешифрования одного блока, что, дает скоростной и практичный криптографический примитив для использования в своих проектах.
Threefish – это симметричный (т.е. ключ и для шифрования, и для дешифрования одинаков) блочный криптографический алгоритм, разработанный группой специалистов во главе с Брюсом Шнайером (автор популярной книги “Практическая криптография”). Основными критериями при разработке были следующие принципы: минимальное использование памяти, необходимая криптографическая устойчивость, оптимизация под 64-разрядные процессоры и простота реализация. Несмотря на то, что оптимизация рассчитана на 64-разрядные системы, в виду простоты реализации, сам алгоритм легко приспособить и под другие системы, в том числе, и под устройства с ограниченными возможностями (микроконтроллеры, встраиваемые системы и т.д).
Наша реализация алгоритма не использует стандартную библиотеку D (используется только прямое взаимодействие со стандартной библиотекой языка программирования C) и выглядит так:
import std.stdio;
extern(C) nothrow @nogc void* memcpy(void* dst, const void* src, size_t n);
enum blockSize = 64;
enum Nw = 8;
enum Nr = 72;
enum Ns = (Nr / 4) + 1;
uint[8] p = [2, 1, 4, 7, 6, 5, 0, 3];
uint[8] p_1 = [6, 1, 0, 7, 2, 5, 4, 3];
uint[4][8] r = [
[38, 30, 50, 53],
[48, 20, 43, 31],
[34, 14, 15, 27],
[26, 12, 58, 7 ],
[33, 49, 8, 42 ],
[39, 27, 41, 14],
[29, 26, 11, 9 ],
[33, 51, 39, 35]
];
ulong[3] t;
ulong[8][Ns] subKeys;
auto _mix(ref ulong[2] x, ulong r, ref ulong[2] y) @nogc @system
{
y[0] = x[0] + x[1];
y[1] = (x[1] << r) | (x[1] >> (64 - r));
y[1] ^= y[0];
}
auto _demix(ref ulong[2] y, ulong r, ref ulong[2] x) @nogc @system
{
y[1] ^= y[0];
x[1] = (y[1] << (64 - r)) | (y[1] >> r);
x[0] = y[0] - x[1];
}
void crypt(ulong* plain, ulong* c) @nogc @system
{
uint round;
ulong[8] f;
ulong[8] e;
ulong[2] y;
ulong[2] x;
ulong[8] v;
uint s;
uint i;
memcpy (&v[0], plain, 64);
for (round = 0; round < Nr; round++)
{
if (round % 4 == 0)
{
s = round / 4;
for (i = 0; i < Nw; i++)
{
e[i] = v[i] + subKeys[s][i];
}
}
else
{
for (i = 0; i < Nw; i++)
{
e[i] = v[i];
}
}
for (i = 0; i < Nw / 2; i++)
{
x[0] = e[i * 2];
x[1] = e[i * 2 + 1];
_mix(x, r[round % 8][i], y);
f[i * 2] = y[0];
f[i * 2 + 1] = y[1];
}
for (i = 0; i < Nw; i++)
{
v[i] = f[p[i]];
}
}
for (i = 0; i < Nw; i++)
{
c[i] = v[i] + subKeys[Nr / 4][i];
}
}
void decrypt(ulong* plain, ulong* c) @nogc @system
{
uint round;
ulong[8] f;
ulong[8] e;
ulong[2] y;
ulong[2] x;
ulong[8] v;
uint s;
uint i;
memcpy(&v[0], plain, 64);
for (round = Nr; round > 0; round--)
{
if (round % 4 == 0)
{
s = round / 4;
for (i = 0; i < Nw; i++)
{
f[i] = v[i] - subKeys[s][i];
}
}
else
{
for (i = 0; i < Nw; i++)
{
f[i] = v[i];
}
}
for (i = 0; i < Nw; i++)
{
e[i] = f[p_1[i]];
}
for (i = 0; i < Nw / 2; i++)
{
y[0] = e[i * 2];
y[1] = e[i * 2 + 1];
_demix(y, r[(round - 1) % 8][i], x);
v[i * 2] = x[0];
v[i * 2 + 1] = x[1];
}
}
for (i = 0; i < Nw; i++)
{
c[i] = v[i] - subKeys[0][i];
}
}
void setup(ulong* keyData, ulong* tweakData) @nogc @system
{
uint i, round;
ulong[8] K;
ulong[2] T;
ulong[9] key;
ulong kNw = 6148914691236517205L;
memcpy(&K[0], &keyData[0], 64);
memcpy(&T[0], &tweakData[0], 16);
for (i = 0; i < Nw; i++)
{
kNw ^= K[i];
key[i] = K[i];
}
key[8] = kNw;
t[0] = T[0];
t[1] = T[1];
t[2] = T[0] ^ T[1];
for (round = 0; round <= Nr / 4; round++)
{
for (i = 0; i < Nw; i++)
{
subKeys[round][i] = key[(round + i) % (Nw + 1)];
if (i == Nw - 3)
{
subKeys[round][i] += t[round % 3];
}
else if (i == Nw - 2)
{
subKeys[round][i] += t[(round + 1) % 3];
}
else if (i == Nw - 1)
{
subKeys[round][i] += round;
}
}
}
}Описанный выше код подразумевает использование Threefish-512, т.е. потоковый шифр из семейства Threefish с размером блока 512 бит. Также, в семействе этих шифров размер блока также определяет и размер ключа, поэтому размер ключа также составляет 512 бит. Однако, помимо ключа, в алгоритме есть еще дополнительная возможность настройки и для этого в нем предусмотрен параметр, который называется tweak-значением. Данное значение является 128-битным и позволяет произвести еще один уровень настройки, что проектировалось для использования Threefish в алгоримах хэширования (есть такая хэш-функция, которая на этом основана, называется Skein).
Приведенный нами код является практически буквальным портированием референсной реализации на C и содержит три основные функции:
- setup – принимает два указателя на массивы – с ключом (это массив из 8 значений типа ulong) и с tweak-значением (массив из 2 значений типа ulong);
- crypt – принимает два указателя на массивы – массив с блоком под шифрование (также 8 значений типа ulong) и массив-приемник (также 8 значений типа ulong), в который будет помещен результат шифрования блока и который, во избежание побочных эффектов, предварительно лучше заполнить нулями;
- decrypt – принимает два указателя на массивы, аналогично функции crypt, массив-приемник результата также лучше предварительно заполнить нулями.
Работать с этим криптографическим примитивом просто: создаем необходимые массивы под ключ, tweak-значение и приемник, запускаем процедуру setup, а затем применяем crypt или decrypt, что можно проиллюстрировать следующим примером:
void main()
{
ulong[8] keyData = [
0x01020304abcdef05, 0xabcdef0102030405, 0xabcdef01efcdab01, 0x01deadbeef010101,
0x01020304abcdef05, 0xabcdef0102030405, 0xabcdef01efcdab01, 0x01deadbeef010101
];
ulong[2] tweakValue = [0x243f6a8885a308d3, 0x13198a2e03707344];
ulong[8] plainData = [
0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0x0000000000000004,
0x0000000000000005, 0x0000000000000006, 0x0000000000000007, 0x0000000000000008
];
ulong[8] result = 0;
ulong[8] result2 = 0;
setup(keyData.ptr, tweakValue.ptr);
crypt(plainData.ptr, result.ptr);
writeln(result);
decrypt(result.ptr, result2.ptr);
writeln(result2);
}В качестве ключа выбран самый банальный вариант, а в качестве tweak-значения – первые шестнадцатеричные цифры числа Пи, вычисленные через формулу Бейли-Боруэйна-Плаффа, и таким тестированием для блока проверяются обе функции и crypt/decrypt.
Внимание! В этой реализации используется старое значение для одной из констант (а именно для C240), а также старые значения для таблицы сдвигов, поэтому мы настоятельно рекомендуем сменить данные параметры в алгоритме, согласно официальному его описанию, или же можно использовать исправленную версию из реестра dub под названием threefish512.