345 lines
15 KiB
D
345 lines
15 KiB
D
module chacha20;
|
||
|
||
private import std.string : format;
|
||
private import std.range : isInputRange, isForwardRange, ElementType;
|
||
private import std.array;
|
||
private import std.traits : hasElaborateCopyConstructor;
|
||
|
||
private import bitmanip;
|
||
|
||
public:
|
||
|
||
// TODO: Check unittests (Use reliable software to check if the results are correct)
|
||
|
||
auto chacha20Cipher(R)(R range, ubyte[32] key, ubyte[8] nonce)
|
||
if(isInputRange!R && is(ElementType!R : ubyte))
|
||
{
|
||
static struct rangeResult
|
||
{
|
||
private:
|
||
ubyte[32] key;
|
||
ubyte[8] nonce;
|
||
ulong count;
|
||
R range;
|
||
ubyte[] chachaSection;
|
||
public:
|
||
this(R range, in ubyte[32] key, in ubyte[8] nonce)
|
||
{
|
||
this.range = range;
|
||
this.key = key;
|
||
this.nonce = nonce;
|
||
this.count = 0;
|
||
chachaSection = chacha20Exp(key, nonce ~ littleEndianInv(count));
|
||
}
|
||
|
||
bool empty() @property
|
||
{
|
||
return range.empty || (count == ulong.max && chachaSection.empty);
|
||
}
|
||
|
||
ubyte front() @property
|
||
{
|
||
assert(!empty);
|
||
return range.front ^ chachaSection.front;
|
||
}
|
||
|
||
void popFront()
|
||
{
|
||
assert(!empty);
|
||
chachaSection.popFront();
|
||
if(chachaSection.empty)
|
||
{
|
||
++count;
|
||
chachaSection = chacha20Exp(key, nonce ~ littleEndianInv(count));
|
||
}
|
||
range.popFront();
|
||
}
|
||
static if (isForwardRange!R)
|
||
{
|
||
auto save() @property
|
||
{
|
||
rangeResult copy;
|
||
copy.range = range.save;
|
||
copy.key = key.dup;
|
||
copy.nonce = nonce.dup;
|
||
copy.count = count;
|
||
copy.chachaSection = chachaSection.dup;
|
||
return copy;
|
||
}
|
||
}
|
||
}
|
||
return rangeResult(range, key, nonce);
|
||
}
|
||
|
||
private:
|
||
|
||
enum string quarterRound(alias _x0, alias _x1, alias _x2, alias _x3) = q{
|
||
%1$s += %2$s; %4$s ^= %1$s; %4$s = rotateLeft(%4$s, 16);
|
||
%3$s += %4$s; %2$s ^= %3$s; %2$s = rotateLeft(%2$s, 12);
|
||
%1$s += %2$s; %4$s ^= %1$s; %4$s = rotateLeft(%4$s, 8);
|
||
%3$s += %4$s; %2$s ^= %3$s; %2$s = rotateLeft(%2$s, 7);
|
||
}.format(__traits(identifier, _x0), __traits(identifier, _x1),
|
||
__traits(identifier, _x2), __traits(identifier, _x3));
|
||
|
||
unittest
|
||
{
|
||
uint a1 = 0x00000000, a2 = 0x00000000, a3 = 0x00000000, a4 = 0x00000000,
|
||
b1 = 0x00000001, b2 = 0x00000000, b3 = 0x00000000, b4 = 0x00000000,
|
||
c1 = 0x00000000, c2 = 0x00000001, c3 = 0x00000000, c4 = 0x00000000,
|
||
d1 = 0x00000000, d2 = 0x00000000, d3 = 0x00000001, d4 = 0x00000000,
|
||
e1 = 0x00000000, e2 = 0x00000000, e3 = 0x00000000, e4 = 0x00000001,
|
||
f1 = 0xe7e8c006, f2 = 0xc4f9417d, f3 = 0x6479b4b2, f4 = 0x68c67137,
|
||
g1 = 0xd3917c5b, g2 = 0x55f1c407, g3 = 0x52a58a7a, g4 = 0x8f887a3b;
|
||
|
||
mixin(quarterRound!(a1, a2, a3, a4));
|
||
mixin(quarterRound!(b1, b2, b3, b4));
|
||
mixin(quarterRound!(c1, c2, c3, c4));
|
||
mixin(quarterRound!(d1, d2, d3, d4));
|
||
mixin(quarterRound!(e1, e2, e3, e4));
|
||
mixin(quarterRound!(f1, f2, f3, f4));
|
||
mixin(quarterRound!(g1, g2, g3, g4));
|
||
|
||
assert([a1, a2, a3, a4] == [0x00000000, 0x00000000, 0x00000000, 0x00000000]);
|
||
assert([b1, b2, b3, b4] == [0x10000001, 0x80808808, 0x01010110, 0x01000110]);
|
||
assert([c1, c2, c3, c4] == [0x10001001, 0x88888808, 0x01110110, 0x01100110]);
|
||
assert([d1, d2, d3, d4] == [0x00001000, 0x08080080, 0x00100001, 0x00100000]);
|
||
assert([e1, e2, e3, e4] == [0x10000000, 0x80800808, 0x01010010, 0x01000010]);
|
||
assert([f1, f2, f3, f4] == [0x207cb2a0, 0x1f261df7, 0x9da4fd26, 0xc8768450]);
|
||
assert([g1, g2, g3, g4] == [0x18cb6df2, 0x41821bf8, 0x1fcb29a7, 0x92cbf922]);
|
||
}
|
||
|
||
enum string rowRound(alias _x00, alias _x01, alias _x02, alias _x03,
|
||
alias _x04, alias _x05, alias _x06, alias _x07,
|
||
alias _x08, alias _x09, alias _x10, alias _x11,
|
||
alias _x12, alias _x13, alias _x14, alias _x15,) = q{
|
||
mixin(quarterRound!(%1$s, %6$s, %11$s, %16$s));
|
||
mixin(quarterRound!(%2$s, %7$s, %12$s, %13$s));
|
||
mixin(quarterRound!(%3$s, %8$s, %9$s, %14$s));
|
||
mixin(quarterRound!(%4$s, %5$s, %10$s, %15$s));
|
||
}.format(__traits(identifier, _x00), __traits(identifier, _x01),
|
||
__traits(identifier, _x02), __traits(identifier, _x03),
|
||
__traits(identifier, _x04), __traits(identifier, _x05),
|
||
__traits(identifier, _x06), __traits(identifier, _x07),
|
||
__traits(identifier, _x08), __traits(identifier, _x09),
|
||
__traits(identifier, _x10), __traits(identifier, _x11),
|
||
__traits(identifier, _x12), __traits(identifier, _x13),
|
||
__traits(identifier, _x14), __traits(identifier, _x15));
|
||
|
||
unittest
|
||
{
|
||
uint y00 = 0x00000001, y01 = 0x00000000, y02 = 0x00000000, y03 = 0x00000000,
|
||
y04 = 0x00000001, y05 = 0x00000000, y06 = 0x00000000, y07 = 0x00000000,
|
||
y08 = 0x00000001, y09 = 0x00000000, y10 = 0x00000000, y11 = 0x00000000,
|
||
y12 = 0x00000001, y13 = 0x00000000, y14 = 0x00000000, y15 = 0x00000000;
|
||
|
||
uint x00 = 0x08521bd6, x01 = 0x1fe88837, x02 = 0xbb2aa576, x03 = 0x3aa26365,
|
||
x04 = 0xc54c6a5b, x05 = 0x2fc74c2f, x06 = 0x6dd39cc3, x07 = 0xda0a64f6,
|
||
x08 = 0x90a2f23d, x09 = 0x067f95a6, x10 = 0x06b35f61, x11 = 0x41e4732e,
|
||
x12 = 0xe859c100, x13 = 0xea4d84b7, x14 = 0x0f619bff, x15 = 0xbc6e965a;
|
||
|
||
mixin(rowRound!(y00, y01, y02, y03, y04, y05, y06, y07,
|
||
y08, y09, y10, y11, y12, y13, y14, y15));
|
||
|
||
mixin(rowRound!(x00, x01, x02, x03, x04, x05, x06, x07,
|
||
x08, x09, x10, x11, x12, x13, x14, x15));
|
||
|
||
uint[] test0 = [y00, y01, y02, y03, y04, y05, y06, y07,
|
||
y08, y09, y10, y11, y12, y13, y14, y15];
|
||
|
||
uint[] test1 = [x00, x01, x02, x03, x04, x05, x06, x07,
|
||
x08, x09, x10, x11, x12, x13, x14, x15];
|
||
|
||
assert(test0 == [0x10000001, 0x10000000, 0x00001000, 0x10001001,
|
||
0x88888808, 0x80808808, 0x80800808, 0x08080080,
|
||
0x00100001, 0x01110110, 0x01010110, 0x01010010,
|
||
0x01000010, 0x00100000, 0x01100110, 0x01000110]);
|
||
|
||
assert(test1 == [0x9318dab2, 0x6219299a, 0xd68946c3, 0x3eb5b75f,
|
||
0xe0df816c, 0x476df40b, 0xbb2e1d6f, 0x7608ad18,
|
||
0x71b82d0d, 0xe707569d, 0x4c71a945, 0x0b2b589a,
|
||
0xe34c7f87, 0x5239bb58, 0x8a47d068, 0x475ec56d]);
|
||
}
|
||
|
||
enum string colRound(alias _x00, alias _x01, alias _x02, alias _x03,
|
||
alias _x04, alias _x05, alias _x06, alias _x07,
|
||
alias _x08, alias _x09, alias _x10, alias _x11,
|
||
alias _x12, alias _x13, alias _x14, alias _x15,) = q{
|
||
mixin(quarterRound!(%1$s, %5$s, %9$s, %13$s));
|
||
mixin(quarterRound!(%2$s, %6$s, %10$s, %14$s));
|
||
mixin(quarterRound!(%3$s, %7$s, %11$s, %15$s));
|
||
mixin(quarterRound!(%4$s, %8$s, %12$s, %16$s));
|
||
}.format(__traits(identifier, _x00), __traits(identifier, _x01),
|
||
__traits(identifier, _x02), __traits(identifier, _x03),
|
||
__traits(identifier, _x04), __traits(identifier, _x05),
|
||
__traits(identifier, _x06), __traits(identifier, _x07),
|
||
__traits(identifier, _x08), __traits(identifier, _x09),
|
||
__traits(identifier, _x10), __traits(identifier, _x11),
|
||
__traits(identifier, _x12), __traits(identifier, _x13),
|
||
__traits(identifier, _x14), __traits(identifier, _x15));
|
||
|
||
unittest{
|
||
uint y00 = 0x00000001, y01 = 0x00000000, y02 = 0x00000000, y03 = 0x00000000,
|
||
y04 = 0x00000001, y05 = 0x00000000, y06 = 0x00000000, y07 = 0x00000000,
|
||
y08 = 0x00000001, y09 = 0x00000000, y10 = 0x00000000, y11 = 0x00000000,
|
||
y12 = 0x00000001, y13 = 0x00000000, y14 = 0x00000000, y15 = 0x00000000;
|
||
|
||
uint x00 = 0x08521bd6, x01 = 0x1fe88837, x02 = 0xbb2aa576, x03 = 0x3aa26365,
|
||
x04 = 0xc54c6a5b, x05 = 0x2fc74c2f, x06 = 0x6dd39cc3, x07 = 0xda0a64f6,
|
||
x08 = 0x90a2f23d, x09 = 0x067f95a6, x10 = 0x06b35f61, x11 = 0x41e4732e,
|
||
x12 = 0xe859c100, x13 = 0xea4d84b7, x14 = 0x0f619bff, x15 = 0xbc6e965a;
|
||
|
||
|
||
mixin(colRound!(y00, y01, y02, y03, y04, y05, y06, y07,
|
||
y08, y09, y10, y11, y12, y13, y14, y15));
|
||
|
||
mixin(colRound!(x00, x01, x02, x03, x04, x05, x06, x07,
|
||
x08, x09, x10, x11, x12, x13, x14, x15));
|
||
|
||
uint[] test0 = [y00, y01, y02, y03, y04, y05, y06, y07,
|
||
y08, y09, y10, y11, y12, y13, y14, y15];
|
||
|
||
uint[] test1 = [x00, x01, x02, x03, x04, x05, x06, x07,
|
||
x08, x09, x10, x11, x12, x13, x14, x15];
|
||
|
||
assert(test0 == [0x30000002, 0x00000000, 0x00000000, 0x00000000,
|
||
0x81811899, 0x00000000, 0x00000000, 0x00000000,
|
||
0x03030231, 0x00000000, 0x00000000, 0x00000000,
|
||
0x03000230, 0x00000000, 0x00000000, 0x00000000]);
|
||
|
||
assert(test1 == [0x54c4775a, 0xb72a4bef, 0xcaba7b13, 0xdc9d2cb9,
|
||
0x01a21f25, 0x229f731a, 0xb515e4fe, 0xcd79967d,
|
||
0xcd26b517, 0x533f496f, 0x5cd61313, 0x3c6a9772,
|
||
0xf5529d13, 0xfbee0de7, 0x7c5c8c13, 0x9c847b82]);
|
||
}
|
||
|
||
ubyte[] chacha20(in ubyte[] input) @safe nothrow pure
|
||
in
|
||
{
|
||
assert(input.length == 64);
|
||
}
|
||
out(result)
|
||
{
|
||
assert(result.length == 64);
|
||
}
|
||
body
|
||
{
|
||
auto x00 = littleEndian(input[0..4]), x01 = littleEndian(input[4..8]),
|
||
x02 = littleEndian(input[8..12]), x03 = littleEndian(input[12..16]),
|
||
x04 = littleEndian(input[16..20]), x05 = littleEndian(input[20..24]),
|
||
x06 = littleEndian(input[24..28]), x07 = littleEndian(input[28..32]),
|
||
x08 = littleEndian(input[32..36]), x09 = littleEndian(input[36..40]),
|
||
x10 = littleEndian(input[40..44]), x11 = littleEndian(input[44..48]),
|
||
x12 = littleEndian(input[48..52]), x13 = littleEndian(input[52..56]),
|
||
x14 = littleEndian(input[56..60]), x15 = littleEndian(input[60..64]);
|
||
|
||
auto y00 = x00, y01 = x01, y02 = x02, y03 = x03,
|
||
y04 = x04, y05 = x05, y06 = x06, y07 = x07,
|
||
y08 = x08, y09 = x09, y10 = x10, y11 = x11,
|
||
y12 = x12, y13 = x13, y14 = x14, y15 = x15;
|
||
|
||
foreach(i; 0..10)
|
||
{
|
||
mixin(colRound!(x00, x01, x02, x03, x04, x05, x06, x07,
|
||
x08, x09, x10, x11, x12, x13, x14, x15));
|
||
|
||
mixin(rowRound!(x00, x01, x02, x03, x04, x05, x06, x07,
|
||
x08, x09, x10, x11, x12, x13, x14, x15));
|
||
}
|
||
|
||
return littleEndianInv(x00 + y00) ~ littleEndianInv(x01 + y01) ~
|
||
littleEndianInv(x02 + y02) ~ littleEndianInv(x03 + y03) ~
|
||
littleEndianInv(x04 + y04) ~ littleEndianInv(x05 + y05) ~
|
||
littleEndianInv(x06 + y06) ~ littleEndianInv(x07 + y07) ~
|
||
littleEndianInv(x08 + y08) ~ littleEndianInv(x09 + y09) ~
|
||
littleEndianInv(x10 + y10) ~ littleEndianInv(x11 + y11) ~
|
||
littleEndianInv(x12 + y12) ~ littleEndianInv(x13 + y13) ~
|
||
littleEndianInv(x14 + y14) ~ littleEndianInv(x15 + y15);
|
||
}
|
||
|
||
unittest
|
||
{
|
||
ubyte[] test0 = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||
test0 = chacha20(test0);
|
||
|
||
ubyte[] test1 = [211,159, 13,115, 76, 55, 82,183, 3,117,222, 37,191,187,234,136,
|
||
49,237,179, 48, 1,106,178,219,175,199,166, 48, 86, 16,179,207,
|
||
31,240, 32, 63, 15, 83, 93,161,116,147, 48,113,238, 55,204, 36,
|
||
79,201,235, 79, 3, 81,156, 47,203, 26,244,243, 88,118,104, 54];
|
||
test1 = chacha20(test1);
|
||
|
||
ubyte[] test2 = [ 88,118,104, 54, 79,201,235, 79, 3, 81,156, 47,203, 26,244,243,
|
||
191,187,234,136,211,159, 13,115, 76, 55, 82,183, 3,117,222, 37,
|
||
86, 16,179,207, 49,237,179, 48, 1,106,178,219,175,199,166, 48,
|
||
238, 55,204, 36, 31,240, 32, 63, 15, 83, 93,161,116,147, 48,113];
|
||
test2 = chacha20(test2);
|
||
|
||
ubyte[] test3 = [ 6,124, 83,146, 38,191, 9, 50, 4,161, 47,222,122,182,223,185,
|
||
75, 27, 0,216, 16,122, 7, 89,162,104,101,147,213, 21, 54, 95,
|
||
225,253,139,176,105,132, 23,116, 76, 41,176,207,221, 34,157,108,
|
||
94, 94, 99, 52, 90,117, 91,220,146,190,239,143,196,176,130,186];
|
||
foreach(i; 0..1000000)
|
||
test3 = chacha20(test3);
|
||
|
||
assert(test0 == [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||
|
||
assert(test1 == [ 19,135,149, 14, 40,180,217,109,187,122,124,114, 23, 14,107,241,
|
||
231, 18, 85,210, 56,159, 57,224, 96, 71, 85,167,202,180, 98, 96,
|
||
185, 76, 56,161,168,230,132,189, 84, 59, 55,254, 39, 21, 41,153,
|
||
144,151, 5,208,189, 9,140, 64,226,151,219, 26, 80,221,237,194]);
|
||
|
||
assert(test2 == [ 76, 1, 30, 79,130,234,169,252,194, 25,110,243,141, 64, 68,140,
|
||
106, 32,248,152,219, 73,115,133, 10,143, 99,162, 72,155,138, 55,
|
||
58,214, 79, 49, 97, 63,201,155,178,203, 15, 94, 66,224,216,184,
|
||
64, 85,186,170, 65, 79, 11, 91, 22,247,191, 72, 68,250,229,108]);
|
||
|
||
assert(test3 == [ 88, 97,168, 54,215,192,249,115,185,160,231, 86,203,147, 27, 34,
|
||
237,252,228, 40, 1,135,107, 88,160,239, 5,182,250, 16,217,224,
|
||
101, 27,113, 5, 50,192, 52, 70, 7,110,217, 10, 3,226, 85,129,
|
||
29,214,235,224, 96, 42, 55, 53,188, 35, 81,171, 81,218,221, 44]);
|
||
}
|
||
|
||
enum ubyte[4] σ0 = [101, 120, 112, 97];
|
||
enum ubyte[4] σ1 = [110, 100, 32, 51];
|
||
enum ubyte[4] σ2 = [ 50, 45, 98, 121];
|
||
enum ubyte[4] σ3 = [116, 101, 32, 107];
|
||
|
||
ubyte[] chacha20Exp(in ubyte[] key, in ubyte[] n) @safe nothrow pure
|
||
in
|
||
{
|
||
assert(key.length == 32);
|
||
assert(n.length == 16);
|
||
}
|
||
out(result)
|
||
{
|
||
assert(result.length == 64);
|
||
}
|
||
body
|
||
{
|
||
return chacha20(σ0 ~ key[0..16] ~ σ1 ~ n ~ σ2 ~ key[16..$] ~ σ3);
|
||
}
|
||
|
||
unittest
|
||
{
|
||
ubyte[] key;
|
||
ubyte[] n;
|
||
key.length = 32;
|
||
n.length = 16;
|
||
foreach(i; 0..16)
|
||
key[i] = cast(ubyte)(i + 1);
|
||
foreach(i; 16..32)
|
||
key[i] = cast(ubyte)(i + 200 - 15);
|
||
|
||
foreach(i; 0..16)
|
||
n[i] = cast(ubyte)(i + 1+ 100);
|
||
|
||
assert(chacha20Exp(key, n) == [ 2, 7, 55,183,240,232, 0,145,207,208,120,131,146, 9,130, 31,
|
||
99,154, 60, 98,194,161,191, 80,167, 61,100,101,173,193, 48,203,
|
||
248, 45, 55, 12, 69, 21,147,216,142,141,137,131, 14, 7,181, 1,
|
||
63,126,214,246, 74,167, 55,124,119,140,129,165,170,250,173, 94]);
|
||
}
|