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