В этой статье мы покажем реализацию симметричного блочного криптографического алгоритма 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.