Реализация блочного криптографического алгоритма Threefish

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

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