Реализация поточной системы шифрования Salsa20

В этом скромном рецепте мы вам покажем, как реализовать систему поточного шифрования Salsa20 от уже знакомого нам Даниэля Бернштейна (того самого, кто придумал redo) в D. Сразу скажем, что наша реализация — это голый порт с C99 с некоторыми улучшениями, но нам показалось, что неплохо бы было поделиться с читателями этим незначительным портом.

Алгоритм Salsa20 — это неплохой алгоритм симметричного шифрования (т.е такое шифрование в котором ключ для шифрования и расшифрования один и тот же), в котором используется ряд простых криптографических преобразований, легко поддающихся распараллеливанию. В нашей реализации все сделано прямолинейно и просто (а это значит, что параллелизма нет — при желании сделаете его сами) и многое нужно подвергнуть улучшению — в частности, сделать безопасные обертки для ключевых точек алгоритма, но мы всем этим пожертвовали в угоду простоте и целостности алгоритма.

Реализация выглядит так и представляет собой порт этой библиотеки на C99:

module salsa20;

enum SALSA_KEY_LENGTH
{
	BIT_256,
	BIT_128
};

enum SALSA_STATUS 
{
	OK,
	ERROR
};

class Salsa20
{
	private @system @nogc
	{
		static uint rotateLeft(uint value, int shift) 
		{
			return (value << shift) | (value >> (32 - shift));
		}
		
		static void quarterRound(uint* y0, uint* y1, uint* y2, uint* y3)
		{
			*y1 = *y1 ^ rotateLeft(*y0 + *y3, 7);
			*y2 = *y2 ^ rotateLeft(*y1 + *y0, 9);
			*y3 = *y3 ^ rotateLeft(*y2 + *y1, 13);
			*y0 = *y0 ^ rotateLeft(*y3 + *y2, 18);
		}
		
		static void rowRound(uint* y)
		{
			quarterRound(&y[0], &y[1], &y[2], &y[3]);
			quarterRound(&y[5], &y[6], &y[7], &y[4]);
			quarterRound(&y[10], &y[11], &y[8], &y[9]);
			quarterRound(&y[15], &y[12], &y[13], &y[14]);
		}
		
		static void columnRound(uint* x)
		{
			quarterRound(&x[0], &x[4], &x[8], &x[12]);
			quarterRound(&x[5], &x[9], &x[13], &x[1]);
			quarterRound(&x[10], &x[14], &x[2], &x[6]);
			quarterRound(&x[15], &x[3], &x[7], &x[11]);
		}
		
		static void doubleRound(uint* x)
		{
			columnRound(x);
			rowRound(x);
		}
		
		static uint littleEndian(ubyte* b)
		{
			return b[0] + ushort(b[1] << 8) + uint(b[2] << 16) + uint(b[3] << 24);
		}
		
		static void reverseLittleEndian(ubyte* b, uint w)
		{
			b[0] = cast(ubyte) w;
			b[1] = cast(ubyte) (w >> 8);
			b[2] = cast(ubyte) (w >> 16);
			b[3] = cast(ubyte) (w >> 24);
		}
	}
	
	static void hash(ubyte* seq) @system @nogc
	{
		int i;
		uint[16] x;
		uint[16] z;
		
		for (i = 0; i < 16; ++i)
		{
			x[i] = z[i] = littleEndian(seq + (4 * i));
	    }
	    
	    for (i = 0; i < 10; ++i)
	    {
			doubleRound(z.ptr);
	    }
	    
	    for (i = 0; i < 16; ++i) 
	    {
			z[i] += x[i];
			reverseLittleEndian(seq + (4 * i), z[i]);
	    }
	}
	
	static void expand16(ubyte* k, ubyte* n, ubyte* keystream) @system @nogc
	{
		int i, j;
		
		ubyte[4][4] t = [
			[ 'e', 'x', 'p', 'a' ],
			[ 'n', 'd', ' ', '1' ],
			[ '6', '-', 'b', 'y' ],
			[ 't', 'e', ' ', 'k' ]
	    ];
	    
	    for (i = 0; i < 64; i += 20)
	    {
			for (j = 0; j < 4; ++j)
			{
				keystream[i + j] = t[i / 20][j];
	        }
	    }
	    
	    for (i = 0; i < 16; ++i) 
	    {
			keystream[4+i]  = k[i];
			keystream[44+i] = k[i];
			keystream[24+i] = n[i];
	    }
	    
	    hash(keystream);
	}
	
	static void expand32(ubyte* k, ubyte* n, ubyte* keystream) @system @nogc
	{
		int i, j;
		
		ubyte[4][4] o = [
			[ 'e', 'x', 'p', 'a' ],
			[ 'n', 'd', ' ', '3' ],
			[ '2', '-', 'b', 'y' ],
			[ 't', 'e', ' ', 'k' ]
	    ];
	    
	    for (i = 0; i < 64; i += 20)
	    {
			for (j = 0; j < 4; ++j) 
			{
				keystream[i + j] = o[i / 20][j];
			}
	    }
	    
	    for (i = 0; i < 16; ++i) 
	    {
			keystream[4+i]  = k[i];
			keystream[44+i] = k[i+16];
			keystream[24+i] = n[i];
	    }
	    
	    hash(keystream);
	}
	
	SALSA_STATUS crypt(ubyte* key, SALSA_KEY_LENGTH keylen, ubyte* nonce, uint si, ubyte* buf, uint buflen) @system @nogc
	{
		ubyte[64] keystream;
		ubyte[16] n = 0;
	    uint i;
	
	    @system @nogc void function(ubyte* k, ubyte* n, ubyte* keystream) expand; 
	  
	    if (keylen == SALSA_KEY_LENGTH.BIT_256)
	    {
			expand = &this.expand32;
		}
	    
	    if (keylen == SALSA_KEY_LENGTH.BIT_128)
	    {
			expand = &this.expand16;
		}
		
		if ((expand == null) || (key == null) || (nonce == null) || (buf == null))
		{
			return SALSA_STATUS.ERROR;
		}
		
		for (i = 0; i < 8; ++i)
		{
			n[i] = nonce[i];
		}
		
		if (si % 64 != 0) 
		{
			reverseLittleEndian(n.ptr + 8, si / 64);
			(*expand)(key, n.ptr, keystream.ptr);
		}
		
		for (i = 0; i < buflen; ++i) 
		{
			if ((si + i) % 64 == 0) 
			{
				reverseLittleEndian(n.ptr + 8, ((si + i) / 64));
				(*expand)(key, n.ptr, keystream.ptr);
            }

			buf[i] ^= keystream[(si + i) % 64];
        }
	
		return SALSA_STATUS.OK;
	}
	
	this()
	{	
	}
}

Испытать шифрование и расшифрование может помочь следующий код, который стоит запускать как скрипт dub:

#!/usr/bin/env dub
/+ dub.sdl:
	dependency "salsa20" version="~>0.0.1"
+/
import std.stdio;
import salsa20;

void main()
{
	auto salsa = new Salsa20;
	
	ubyte[32] key = [
		0xab, 0xcd, 0xef, 0x01, 0x02, 0x03, 0x04, 0x05,
		0xab, 0xcd, 0xef, 0x01, 0x02, 0x03, 0x04, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0xab, 0xcd, 0xef, 0x01, 0x02, 0x03, 0x04, 0x04
	];
	
	ubyte[16] key1 = [
		0xab, 0xcd, 0xef, 0x01, 0x02, 0x03, 0x04, 0x05,
		0xab, 0xcd, 0xef, 0x01, 0x02, 0x03, 0x04, 0x00,
	];
	ubyte[8] nonce = [0xab, 0xcd, 0xef, 0x01, 0x02, 0x03, 0x04, 0x05];
	uint si = 0;
	ubyte[10] buf = [
		0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba
	];
	
	foreach (e; buf)
	{
		writef("%0.2x ", e);
	}
	
	writeln;
	
	salsa.crypt(key1.ptr, SALSA_KEY_LENGTH.BIT_128, nonce.ptr, 0, buf.ptr, buf.length);
	foreach (e; buf)
	{
		writef("%0.2x ", e);
	}
	
	writeln;
	
	salsa.crypt(key1.ptr, SALSA_KEY_LENGTH.BIT_128, nonce.ptr, 0, buf.ptr, buf.length);
	
	foreach (e; buf)
	{
		writef("%0.2x ", e);
	}
	
	writeln;
}

Возможность такого включения Salsa20 является одной из новостей для наших подписчиков: дело в том, что мы уже подготовили для вас реализацию в форме библиотеки dub и найти вы ее можете здесь.

А на этом все, и если вы хотите использовать данный код призываем вас поработать и над его улучшением, что вы можете сделать как для самого себя так и для сообщества, организовав патч для нашей скромной библиотеки.

aquaratixc

Программист-самоучка и программист-любитель

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