From 20dc2ff534bd1b12a5007235b62416c34eb83e06 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Mon, 4 Jul 2016 15:00:52 +0200 Subject: [PATCH] optimized speed and memory usage of salsa20.d and actions.d. Some more small fixes --- source/actions.d | 67 ++++++++++++++++++++--------- source/app.d | 29 ++++++------- source/cipher.d | 23 +++++----- source/salsa20.d | 109 ++++++++++++++++++++++++++++++++++------------- 4 files changed, 152 insertions(+), 76 deletions(-) diff --git a/source/actions.d b/source/actions.d index ae1b964..53984ad 100644 --- a/source/actions.d +++ b/source/actions.d @@ -10,6 +10,7 @@ import cipher; public: enum chunkSize = 4096; +enum base64ChunkSize = 57; enum string randomDeviceName = "/dev/random"; @@ -19,43 +20,70 @@ void encrypt(string keyFileName, Cipher cipher, bool armor) ubyte[8] nonce; if (armor) { - ubyte[] buf; + ubyte[base64ChunkSize] buf; + char[Base64.encodeLength(base64ChunkSize)] buf2; + int counter; foreach (b; stdin.byChunk(chunkSize).joiner.cipherFunction(key, nonce, cipher)) { - buf ~= [b]; - if (buf.length == 57) + buf[counter] = b; + ++counter; + if (counter == 57) { - stdout.writeln(Base64.encode(buf)); - buf = []; + Base64.encode(buf, buf2[]); + stdout.writeln(buf2); + counter = 0; } } - if (buf !is null) - stdout.writeln(Base64.encode(buf)); + if (counter > 0) + { + Base64.encode(buf[0..counter], buf2[]); + stdout.writeln(buf2[0..Base64.encodeLength(counter)]); + } } else { + ubyte[chunkSize] buf; + int counter; foreach (b; stdin.byChunk(chunkSize).joiner.cipherFunction(key, nonce, cipher)) - stdout.rawWrite([b]); + { + buf[counter] = b; + ++counter; + if (counter == chunkSize) + { + stdout.rawWrite(buf); + counter = 0; + } + } + if (counter > 0) + stdout.rawWrite(buf[0..counter]); } } void decrypt(string keyFileName, Cipher cipher, bool armor) { + import std.range; auto key = loadKey(keyFileName, armor); ubyte[8] nonce; + ubyte[chunkSize] buf; + int counter; + InputRange!ubyte r; if (armor) + r = Base64.decoder(stdin.byLine).joiner.cipherFunction(key, nonce, cipher).inputRangeObject; + else + r = stdin.byChunk(chunkSize).joiner.cipherFunction(key, nonce, cipher).inputRangeObject; + + foreach (b; r) { - ubyte[] buf; - foreach (b; Base64.decoder(stdin.byLine).joiner.cipherFunction(key, nonce, cipher)) + buf[counter] = b; + ++counter; + if (counter == 4096) { - stdout.rawWrite([b]); + stdout.rawWrite(buf); + counter = 0; } } - else - { - foreach (b; stdin.byChunk(chunkSize).joiner.cipherFunction(key, nonce, cipher)) - stdout.rawWrite([b]); - } + if(counter > 0) + stdout.rawWrite(buf[0..counter]); } void generateKey(bool armor) @@ -90,12 +118,11 @@ ubyte[32] loadKey(string filename, bool armor) scope (exit) keyFile.close(); ubyte[32] key; + ubyte[33] temp; if (armor) { - ubyte[] tempKey; - foreach (line; keyFile.byLine) - tempKey ~= Base64.decode(line); - key = tempKey; + import std.string : chomp; + key = Base64.decode(keyFile.readln.chomp, temp[]); } else { diff --git a/source/app.d b/source/app.d index 498d213..bd00ea7 100644 --- a/source/app.d +++ b/source/app.d @@ -7,7 +7,7 @@ import actions; int main(string[] args) { - bool[string] actions = ["genKey" : false, "encrypt" : false, "decrypt" : false]; + bool[string] actions = ["genKey" : false, "encrypt" : false, "decrypt" : false]; Cipher cipher = Cipher.chacha20; string keyFileName = "symkey.asc"; @@ -16,14 +16,13 @@ int main(string[] args) GetoptResult result; try { - result = getopt(args, - std.getopt.config.bundling, - "gen-key|g", "Generate a new 256 bit key.", &actions["genKey"], - "encrypt|e", "Encrypt a message.", &actions["encrypt"], - "decrypt|d", "Decrypt a message.", &actions["decrypt"], - "cipher|c", "The cipher to use (default: %s).".format(cipher), &cipher, - "key|k", "The file which contains the key (default: %s).".format(keyFileName), &keyFileName, - "armor|a", "use ascii-armored I/O.", &armor); + result = getopt(args, std.getopt.config.bundling, "gen-key|g", + "Generate a new 256 bit key.", &actions["genKey"], + "encrypt|e", "Encrypt a message.", &actions["encrypt"], + "decrypt|d", "Decrypt a message.", &actions["decrypt"], + "cipher|c", "The cipher to use (default: %s).".format(cipher), &cipher, "key|k", + "The file which contains the key (default: %s).".format(keyFileName), + &keyFileName, "armor|a", "use ascii-armored I/O.", &armor); } catch (Exception e) { @@ -71,12 +70,8 @@ int main(string[] args) void printHelp(Option[] options) { - defaultGetoptPrinter("Usage: ./learncrypt [options]\n\nCommon options:", - options[$-1..$]); - defaultGetoptPrinter("\nGlobal options:", - options[$-2..$-1]); - defaultGetoptPrinter("\nActions:", - options[0..3]); - defaultGetoptPrinter("\nAction options:", - options[3..5]); + defaultGetoptPrinter("Usage: ./learncrypt [options]\n\nCommon options:", options[$ - 1 .. $]); + defaultGetoptPrinter("\nGlobal options:", options[$ - 2 .. $ - 1]); + defaultGetoptPrinter("\nActions:", options[0 .. 3]); + defaultGetoptPrinter("\nAction options:", options[3 .. 5]); } diff --git a/source/cipher.d b/source/cipher.d index d7195c3..78cad74 100644 --- a/source/cipher.d +++ b/source/cipher.d @@ -1,9 +1,12 @@ module cipher; -private import std.range : isInputRange, isForwardRange, ElementType, InputRange, ForwardRange, inputRangeObject; +private import std.range : isInputRange, isForwardRange, ElementType, + InputRange, ForwardRange, inputRangeObject; + private import std.string : format; private import salsa20; + private import chacha20; public: @@ -15,9 +18,9 @@ enum Cipher } template cipherFunction(R) -if(isInputRange!R && is(ElementType!R : ubyte)) + if (isInputRange!R && is(ElementType!R : ubyte)) { - static if(isForwardRange!R) + static if (isForwardRange!R) alias ReturnType = ForwardRange; else alias ReturnType = InputRange; @@ -26,10 +29,10 @@ if(isInputRange!R && is(ElementType!R : ubyte)) { final switch (cipher) { - case Cipher.salsa20: - return range.salsa20Cipher(key, nonce).inputRangeObject; - case Cipher.chacha20: - return range.chacha20Cipher(key, nonce).inputRangeObject; + case Cipher.salsa20: + return range.salsa20Cipher(key, nonce).inputRangeObject; + case Cipher.chacha20: + return range.chacha20Cipher(key, nonce).inputRangeObject; } } } @@ -37,9 +40,9 @@ if(isInputRange!R && is(ElementType!R : ubyte)) unittest { import std.array; - - ubyte[32] key; - ubyte[8] nonce; + + immutable ubyte[32] key; + immutable ubyte[8] nonce; ubyte[] a = [1, 2, 3]; auto b = a.cipherFunction(key, nonce, Cipher.salsa20); InputRange!ubyte c = a.inputRangeObject; diff --git a/source/salsa20.d b/source/salsa20.d index b19d21d..fad16c5 100644 --- a/source/salsa20.d +++ b/source/salsa20.d @@ -35,8 +35,10 @@ auto salsa20Cipher(R)(R range, ubyte[32] key, ubyte[8] nonce) salsaSection.popFront(); if (salsaSection.empty) { - ++count; - salsaSection = salsa20Exp(key, nonce ~ littleEndianInv(count)); + ubyte[16] n; + n[0..8] = nonce; + n[8..16] = littleEndianInv(0UL); + salsaSection = salsa20Exp(key, n).dup; } range.popFront(); } @@ -48,7 +50,10 @@ auto salsa20Cipher(R)(R range, ubyte[32] key, ubyte[8] nonce) } } } - return rangeResult(0UL, range, salsa20Exp(key, nonce ~ littleEndianInv(0UL))); + ubyte[16] n; + n[0..8] = nonce; + n[8..16] = littleEndianInv(0UL); + return rangeResult(0UL, range, salsa20Exp(key, n).dup); } private: @@ -176,11 +181,11 @@ unittest 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]; + immutable 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]; + immutable uint[] test1 = [x00, x01, x02, x03, x04, x05, x06, x07, + x08, x09, x10, x11, x12, x13, x14, x15]; assert(test0 == [0x10090288, 0x00000000, 0x00000000, 0x00000000, 0x00000101, 0x00000000, 0x00000000, 0x00000000, @@ -193,8 +198,8 @@ unittest 0x481c2027, 0x53a8e4b5, 0x4c1f89c5, 0x3f78c9c8]); } -ubyte[] salsa20(in ubyte[] input) pure nothrow @safe -in +ubyte[64] salsa20(in ubyte[64] input) pure nothrow @safe +/*in { assert(input.length == 64); } @@ -202,7 +207,7 @@ out(result) { assert(result.length == 64); } -body +body*/ { auto x00 = littleEndian(input[00..04]), x01 = littleEndian(input[04..08]), x02 = littleEndian(input[08..12]), x03 = littleEndian(input[12..16]), @@ -227,37 +232,55 @@ body x08, x09, x10, x11, x12, x13, x14, x15)); } - return littleEndianInv(x00 + y00) ~ littleEndianInv(x01 + y01) ~ + ubyte[64] buf; + buf[00..04] = littleEndianInv(x00 + y00); + buf[04..08] = littleEndianInv(x01 + y01); + buf[08..12] = littleEndianInv(x02 + y02); + buf[12..16] = littleEndianInv(x03 + y03); + buf[16..20] = littleEndianInv(x04 + y04); + buf[20..24] = littleEndianInv(x05 + y05); + buf[24..28] = littleEndianInv(x06 + y06); + buf[28..32] = littleEndianInv(x07 + y07); + buf[32..36] = littleEndianInv(x08 + y08); + buf[36..40] = littleEndianInv(x09 + y09); + buf[40..44] = littleEndianInv(x10 + y10); + buf[44..48] = littleEndianInv(x11 + y11); + buf[48..52] = littleEndianInv(x12 + y12); + buf[52..56] = littleEndianInv(x13 + y13); + buf[56..60] = littleEndianInv(x14 + y14); + buf[60..64] = littleEndianInv(x15 + y15); + return buf; +/* 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); + 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, + ubyte[64] 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 = salsa20(test0); - ubyte[] test1 = [211,159, 13,115, 76, 55, 82,183, 3,117,222, 37,191,187,234,136, + ubyte[64] 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 = salsa20(test1); - ubyte[] test2 = [ 88,118,104, 54, 79,201,235, 79, 3, 81,156, 47,203, 26,244,243, + ubyte[64] 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 = salsa20(test2); - ubyte[] test3 = [ 6,124, 83,146, 38,191, 9, 50, 4,161, 47,222,122,182,223,185, + ubyte[64] 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]; @@ -285,13 +308,24 @@ unittest 122,127,195,185,185,204,188, 90,245, 9,183,248,226, 85,245,104]); } -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]; +static immutable ubyte[4] sigma0 = [101, 120, 112, 97]; +static immutable ubyte[4] sigma1 = [110, 100, 32, 51]; +static immutable ubyte[4] sigma2 = [ 50, 45, 98, 121]; +static immutable ubyte[4] sigma3 = [116, 101, 32, 107]; -ubyte[] salsa20Exp(in ubyte[] key, in ubyte[] n) @safe pure nothrow -in +import std.range; +import std.algorithm; +import std.typetuple; + +template expand(alias A, alias C="A[I]") { + auto ref M(alias I)() @property { return mixin(C); } + mixin(q{alias expand = TypeTuple!(} + ~ iota(A.length).map!q{"M!"~text(a)}().join(",") + ~ q{);}); +} + +ubyte[64] salsa20Exp(in ubyte[32] key, in ubyte[16] n) @safe pure nothrow +/*in { assert(key.length == 32); assert(n.length == 16); @@ -300,24 +334,41 @@ out(result) { assert(result.length == 64); } -body +body*/ { - return salsa20(σ0 ~ key[0..16] ~ σ1 ~ n ~ σ2 ~ key[16..$] ~ σ3); + ubyte[64] buf = [ + expand!sigma0, + expand!key[0..16], + expand!sigma1, + expand!n, + expand!sigma2, + expand!key[16..$], + expand!sigma3 + ]; +/* buf[0..4] = sigma0; + buf[4..20] = key[0..16]; + buf[20..24] = sigma1; + buf[24..40] = n; + buf[40..44] = sigma2; + buf[44..60] = key[16..$]; + buf[60..64] = sigma3;*/ + return salsa20(buf); + //return salsa20(σ0 ~ key[0..16] ~ σ1 ~ n ~ σ2 ~ key[16..$] ~ σ3); } unittest { - ubyte[] key; - ubyte[] n; - key.length = 32; - n.length = 16; + ubyte[32] key; + ubyte[16] 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); + n[i] = cast(ubyte)(i + 1 + 100); assert(salsa20Exp(key, n) == [ 69, 37, 68, 39, 41, 15,107,193,255,139,122, 6,170,233,217, 98, 89,144,182,106, 21, 51,200, 65,239, 49,222, 34,215,114, 40,126,