1 module dcrypt.streamcipher.salsa; 2 3 public import dcrypt.streamcipher.streamcipher; 4 5 import dcrypt.bitmanip: rotl=rotateLeft; 6 import dcrypt.util: wipe; 7 8 import dcrypt.bitmanip; 9 10 import std.algorithm: min; 11 12 // Test Salsa20 13 unittest { 14 15 // test vectors generated with bouncycastle Salsa20 implementation 16 string[] keys = [ 17 x"00000000000000000000000000000000", 18 x"01010101010101010101010101010101", 19 x"0202020202020202020202020202020202020202020202020202020202020202", 20 x"0303030303030303030303030303030303030303030303030303030303030303", 21 ]; 22 23 string[] ivs = [ 24 x"0101010101010101", 25 x"0202020202020202", 26 x"0303030303030303", 27 x"0404040404040404", 28 ]; 29 30 string[] plains = [ 31 x"0202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202", 32 x"0303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303", 33 x"0404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404", 34 x"0505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505", 35 ]; 36 37 string[] ciphers = [ 38 x"4280c83f61325a4b1c2ba27d5693c016a08b4eb5afe070991307a89a4f6787e734ef067bb35521ea44a4e07d90e64140959e10db4de238918956fc9a8b45245c6df266aed0f064f8be9d769380049b173f3ea0a665dcb46b85f5a9f01406e0cb7186fab2328ddc39051722caeaf2166c00a93e447df93eae9610b82af86c0ed9", 39 x"3f1d4e1227cd3483068269ed5914a61dedce4e0fe2736d29e9dac4404a994423feb51748ba99e1f94599c53ee97f58d5b002c9bf04f9a0c2cc4d186620b7a23e312bfaf3d90b76f1fabaaa7a1526160d38fe64acb4af703e2d084424efcdf975825f5fcf39f3286375532ff7206b64b81ae65a0ada0005a5af6d035fb6b38221", 40 x"e390de08ceb0eca5c373e7fbc62ae1a76eea14b3302bda97833801da174ef6464f1592d563c30b3da9c99c2b7850a6f93d036bdaf82788aa870a20e2fc384c61f33e45f52e03903a0ab55807b18920db7481bf5f4975faa4e0fc595dc3e8d23145b288e48ae4b895ce58198624b660867eb8226582f72879f4b90eb9a5e67067", 41 x"a64037f78e86e53cade29afa3533719ea5b7b71162911bd06e351f172692602cc5c90b022652292f0a78530e4961004cd38b14a22c8a483f386987293c1c24fe1882cb13221d4ec6fa63082c586aab1e2d9fed293b17b1159f1c2f4fdc007df5c9a8c1b026d254262c6e28c0d2f0c8b3cfa83be5b3dbc29166f74123c7dfc54c", 42 ]; 43 44 streamCipherTest(new Salsa20Engine, keys, plains, ciphers, ivs); 45 } 46 47 // Test XSalsa 48 unittest { 49 50 // test vectors generated with bouncycastle XSalsa20 implementation 51 string[] keys = [ 52 x"86e2f31305b14bc42caf3f9c7fb4112cc7ae64cf43e0d429a27fa63b70d0939e", 53 x"b96baefe0fa3144455926da4a8583643107bda7926b2ea4577776f9ca89a9d00", 54 x"a5f519c06ede84d97d0d6dc8a9cfa52cfe532908e0dfdb03a875a948866cd77b", 55 x"c758bac8a6a4a405aa057a9d90621afa9f23eeb5157f65474e0cdb11284bea77", 56 ]; 57 string[] ivs = [ 58 x"24eaeaac41c512e3eb77bc051c4e98ab565122ea4d274b4f", 59 x"17af8f7d0a89e15c6587d3a4f6bb20a75a7ea9d70c96e01c", 60 x"0a702d29a25362429c5a5f5e3dd86580c733fb94aff70037", 61 x"4201c53a16675ee4b95a85572c59c6e6cdc2faad8c77bc49", 62 ]; 63 string[] plains = [ 64 x"dfe73782b7b40e000084dcd8170f95549180c8b546b8cce7823a38b11fd78f0fb94fa0720cffae5411e5108c9bd186b9f6ab7630477d8b2ca4160c9deeda271ded4a6dd782962b68315c0b9220b122a70bfee4d426eda7f1a44b562659c525bf23a5c692c8c79bb37d11e4386e5a8a096c333038ac590598ef59b6dd7b8969d6", 65 x"1107c901f7330c80f24d02581bcd027c6a58cbc2809eebfd1c9182227875571da45c9db20e51421d2b970846e5c72ef1b5b8fdae6e7b59ad9d583dc87133c47686b123627b98e0422fb86495f73060c882302b9c20d310e4e0eddd5daca2f028952d925394afe04a9718f3e5fbe7879665c618a9e05c86bb286dc455898f53da", 66 x"a2262bc781d520f799b31a18ad51501072670fa33e6d1c799c7e9c97322485b44800f33f81d9c85c750045f8acedfd61b31f064c4c36771586ea86b2441273936af2644644c3aa8a521ee03ddabdcb6a05177ebab78143ea0dcfd98ef3301f5b76f1f847fb24464f41bba616feb75c2cbb8629807b3dc9fe63bb7b4abcf94a60", 67 x"a840111122d4db70f5fbfdea485a37f3ab621855ff44b29843b49d26499f6acbc809bd51d19f7ebab48d99265dddab8832795b526ab688048ce80a0f6b2e938e9568bdb7e90aae58f665f653a1c5b606b0cfac4a8fdb48e340a9c128a5aadea33c8258ea57d1660096cf0858c0e5bb98c7431121b7435e82c62df79ce11a99a1", 68 ]; 69 string[] ciphers = [ 70 x"7b64de1c7c6dec7eb70b79edf5c9d5812309a344e0f9ca53f18c922f03604d616a8008363bd82fc53341c32825ddb8ae371b242fa8eed90afdd38659a2304c13c774816e6c1b3022eebb8092971d3393406f8c70c8a02471146813906ac74e66751bb3dbb21a07913a69c1fcd8e9af0d3b23f13c74872da21eeef0a8578e5873", 71 x"80d4be693aa49d763ec1dffa251a6bb0f83402902a8175f5759d40046bc2e0ced8a8239f5a2d2caf28846f8b0e8e0b471ec6d61ed19b268c5d4ed2aff87bb1f07adf0297d305767b70eda08a29c16f04825b7edefdcbc77fcfbd2c9fad63e0d8409dc7a661add37babf814d76aec15ad435b8d9393793189c76f3e51cce31e3b", 72 x"8303327859df863abf0b932e3609b862b0e2399f277bbdc194fe19d9f6ad83685f0f2881db383677962d0ef5ae15e30c80cd03b994abc20a5e27a2b7c4c23ab2b045df862a315e5b5329e41183c98acfb2434ebccdf19005204b4d0c7541c3c517bbfc555c54c5d164be5b50ce22182dcb37b9e1a42a19390107683160e97c00", 73 x"b8c77c22f789d71679afd50aeb51dcbca26066fc55cee32e5ce3647d89de1bc664f9760ca6ae3037104387ffd1ae6aaae76f7ea1a3c2cac7dc5e5fedf581f8ba3c5025c163cfe7f03337a5ada2e34c573da2149994e805101f829e774e91338e730f07ad870b94bf71a575af3dd029fabe8e874eb655843d8f37bc01a5cfc818", 74 ]; 75 76 streamCipherTest(new XSalsa20Engine, keys, plains, ciphers, ivs); 77 } 78 79 alias Salsa!20 Salsa20; 80 alias StreamCipherWrapper!Salsa20 Salsa20Engine; 81 82 alias Salsa!(20, true) XSalsa20; 83 alias StreamCipherWrapper!XSalsa20 XSalsa20Engine; 84 85 static assert(isStreamCipher!Salsa20, "Salsa20 is not a stream cipher!"); 86 static assert(isStreamCipher!XSalsa20, "XSalsa20 is not a stream cipher!"); 87 88 /// 89 /// implementation of the Salsa20/20 stream cipher 90 /// 91 /// Params: 92 /// rounds = Number of rounds. 12 and 20 are allowed. Default is 20. 93 /// 94 @safe 95 public struct Salsa(uint rounds = 20, bool xsalsa = false) 96 if(rounds == 12 || rounds == 20) 97 { 98 99 @nogc nothrow: 100 101 static if(xsalsa) { 102 public enum name = "XSalsa20/"~rounds; 103 } else { 104 public enum name = "Salsa20/"~rounds; 105 } 106 107 private { 108 109 enum stateSize = 16; // 16, 32 bit ints = 64 bytes 110 111 /* 112 * variables to hold the state of the engine 113 * during encryption and decryption 114 */ 115 uint index = 0; 116 uint[stateSize] engineState; /// state 117 ubyte[stateSize*4] keyStream; /// expanded state, 64 bytes 118 bool initialized = false; 119 120 /* 121 * internal counter 122 */ 123 uint cW0, cW1, cW2; 124 125 } 126 127 @safe 128 ~this() { 129 wipe(engineState); 130 wipe(keyStream); 131 } 132 133 /// Initialize the cipher. 134 /// 135 /// Params: 136 /// forEncryption = Not used because encryption and decryption is actually the same. 137 /// key = secret key 138 /// iv = Use a unique nonce per key. 139 public void start(bool forEncryption, in ubyte[] key, in ubyte[] iv) nothrow @nogc 140 in { 141 static if(xsalsa) { 142 assert(key.length == 32, "XSalsa requires a 256 bit key."); 143 assert(iv.length == 24, "XSalsa needs a 192 bit nonce."); 144 } else { 145 assert(key.length == 16 || key.length == 32, "Salsa20 needs 128 or 256 bit keys."); 146 assert(iv.length == 8, "Salsa20 needs a 8 byte IV."); 147 } 148 } 149 body { 150 static if(xsalsa) { 151 // XSalsa 152 ubyte[32] xkey = HSalsa(key, iv[0..16]); 153 scope(exit) wipe(xkey); 154 initState(engineState, xkey, 0, iv[16..24]); 155 } else { 156 // Salsa 157 initState(engineState, key, 0, iv); 158 } 159 index = 0; 160 resetCounter(); 161 initialized = true; 162 } 163 164 /// Encrypt or decrypt input bytes (but no more than 2^70 bytes!). 165 /// 166 /// Params: 167 /// input = input bytes 168 /// output = buffer for output bytes. length must match input length. 169 /// 170 /// Returns: Slice pointing to processed data which might be smaller than `output`. 171 /// 172 /// Throws: Error if limit of 2^70 bytes is exceeded. 173 /// 174 public ubyte[] processBytes(in ubyte[] input, ubyte[] output) 175 in { 176 assert(output.length >= input.length, "output buffer too short"); 177 assert(initialized, "Salsa20Engine not initialized!"); 178 } 179 body { 180 181 // can't encrypt more than 2^70 bytes per iv 182 if (limitExceeded(input.length)) 183 { 184 assert(false, "2^70 byte limit per IV would be exceeded. Change IV!"); 185 } 186 187 ubyte[] initialOutputSlice = output; 188 const (ubyte)[] inp = input; 189 190 while(inp.length > 0) { 191 192 if (index == 0) 193 { 194 nextKeyStreamBlock(); 195 } 196 197 size_t len = min(keyStream.length-index, inp.length); 198 output[0..len] = inp[0..len]^keyStream[index..index+len]; 199 index = (index + len) % keyStream.length; 200 inp = inp[len..$]; 201 output = output[len..$]; 202 } 203 204 205 return initialOutputSlice[0..input.length]; 206 } 207 208 public ubyte processByte(in ubyte b) 209 in { 210 assert(initialized, "Salsa20Engine not initialized!"); 211 } 212 body { 213 214 // can't encrypt more than 2^70 bytes per iv 215 if (limitExceeded(1)) 216 { 217 assert(false, "2^70 byte limit per IV would be exceeded. Change IV!"); 218 } 219 220 if (index == 0) 221 { 222 nextKeyStreamBlock(); 223 } 224 enum len = 1; 225 226 ubyte o = b^keyStream[index]; 227 228 index = (index + len) % keyStream.length; 229 230 return o; 231 } 232 233 /// Salsa20/rounds function 234 /// 235 /// Params: 236 /// rounds = number of rounds (20 in default implementation) 237 /// input = input data 238 /// x = output buffer where keystream gets written to 239 public static void block(uint rounds = 20)(in uint[] input, uint[] output) pure nothrow @nogc 240 if(rounds % 2 == 0 || rounds > 0) 241 in { 242 assert(input.length == 16, "invalid input length"); 243 assert(output.length == 16, "invalid output buffer length"); 244 } body { 245 246 uint[16] x = input; 247 248 salsaDoubleRound!rounds(x); 249 250 // element wise addition 251 x[] += input[]; 252 output[] = x[]; 253 } 254 255 public static void block(uint rounds = 20)(in ref uint[16] input, ref uint[16] output) pure nothrow @nogc 256 if(rounds % 2 == 0 || rounds > 0) 257 in { 258 assert(input.length == 16, "invalid input length"); 259 assert(output.length == 16, "invalid output buffer length"); 260 } body { 261 262 uint[16] x = input; 263 264 salsaDoubleRound!rounds(x); 265 266 // element wise addition 267 x[] += input[]; 268 output[] = x[]; 269 } 270 271 /// Params: 272 /// keyBytes = key, 16 or 32 bytes. 273 /// ivBytes = iv, exactly 8 bytes. 274 public static void initState(ref uint[16] state, in ubyte[] keyBytes, in uint counter, in ubyte[] ivBytes) nothrow @nogc 275 in { 276 assert(keyBytes.length == 16 || keyBytes.length == 32, "invalid key length"); 277 assert(ivBytes.length == 8, "invalid iv length"); 278 } 279 body { 280 281 uint[4] constants; 282 283 // Key 284 fromLittleEndian(keyBytes[0..16], state[1..5]); 285 286 if (keyBytes.length == 32) 287 { 288 constants = sigma; 289 290 fromLittleEndian(keyBytes[16..32], state[11..15]); 291 } 292 else 293 { 294 // repeat the 128 bit key 295 constants = tau; 296 fromLittleEndian(keyBytes[0..16], state[11..15]); 297 } 298 299 state[0] = constants[0]; 300 state[5] = constants[1]; 301 state[10] = constants[2]; 302 state[15] = constants[3]; 303 304 // IV 305 fromLittleEndian!uint(ivBytes[0..$], state[6..8]); 306 307 // counter 308 state[8] = counter; 309 state[9] = 0; 310 } 311 312 313 // 314 // Private implementation 315 // 316 317 private: 318 319 /// generate a block (64 bytes) of keystream 320 void nextKeyStreamBlock() nothrow @nogc 321 { 322 uint[stateSize] x; 323 block!rounds(engineState, x); 324 toLittleEndian!uint(x, keyStream); 325 326 // increment counter 327 // engineState[9] += ++engineState[8] == 0; 328 if (++engineState[8] == 0) 329 { 330 ++engineState[9]; 331 } 332 } 333 334 void resetCounter() nothrow @nogc 335 { 336 cW0 = 0; 337 cW1 = 0; 338 cW2 = 0; 339 } 340 341 bool limitExceeded() nothrow @nogc 342 { 343 if (++cW0 == 0) 344 { 345 if (++cW1 == 0) 346 { 347 return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) 348 } 349 } 350 351 return false; 352 } 353 354 /* 355 * test if limit will be exceeded for input of size len 356 */ 357 bool limitExceeded(size_t len) nothrow @nogc 358 { 359 cW0 += len; 360 if (cW0 < len && cW0 >= 0) 361 { 362 if (++cW1 == 0) 363 { 364 return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) 365 } 366 } 367 368 return false; 369 } 370 } 371 372 private { 373 // constants 374 375 enum uint[4] sigma = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; //cast(ubyte[16])"expand 32-byte k"; 376 enum uint[4] tau = [0x61707865, 0x3120646e, 0x79622d36, 0x6b206574]; //cast(ubyte[16])"expand 16-byte k"; 377 378 } 379 380 /// Executes the double round function rounds/2 times. 381 /// 382 /// Params: 383 /// rounds = number of rounds. 384 /// x = the state. 385 private void salsaDoubleRound(uint rounds)(uint[] x) pure nothrow @nogc 386 if(rounds % 2 == 0 || rounds > 0) 387 in { 388 assert(x.length == 16, "invalid state length"); 389 } body { 390 foreach (i; 0..rounds/2) 391 { 392 x[ 4] ^= rotl((x[ 0]+x[12]), 7); 393 x[ 8] ^= rotl((x[ 4]+x[ 0]), 9); 394 x[12] ^= rotl((x[ 8]+x[ 4]),13); 395 x[ 0] ^= rotl((x[12]+x[ 8]),18); 396 x[ 9] ^= rotl((x[ 5]+x[ 1]), 7); 397 x[13] ^= rotl((x[ 9]+x[ 5]), 9); 398 x[ 1] ^= rotl((x[13]+x[ 9]),13); 399 x[ 5] ^= rotl((x[ 1]+x[13]),18); 400 x[14] ^= rotl((x[10]+x[ 6]), 7); 401 x[ 2] ^= rotl((x[14]+x[10]), 9); 402 x[ 6] ^= rotl((x[ 2]+x[14]),13); 403 x[10] ^= rotl((x[ 6]+x[ 2]),18); 404 x[ 3] ^= rotl((x[15]+x[11]), 7); 405 x[ 7] ^= rotl((x[ 3]+x[15]), 9); 406 x[11] ^= rotl((x[ 7]+x[ 3]),13); 407 x[15] ^= rotl((x[11]+x[ 7]),18); 408 x[ 1] ^= rotl((x[ 0]+x[ 3]), 7); 409 x[ 2] ^= rotl((x[ 1]+x[ 0]), 9); 410 x[ 3] ^= rotl((x[ 2]+x[ 1]),13); 411 x[ 0] ^= rotl((x[ 3]+x[ 2]),18); 412 x[ 6] ^= rotl((x[ 5]+x[ 4]), 7); 413 x[ 7] ^= rotl((x[ 6]+x[ 5]), 9); 414 x[ 4] ^= rotl((x[ 7]+x[ 6]),13); 415 x[ 5] ^= rotl((x[ 4]+x[ 7]),18); 416 x[11] ^= rotl((x[10]+x[ 9]), 7); 417 x[ 8] ^= rotl((x[11]+x[10]), 9); 418 x[ 9] ^= rotl((x[ 8]+x[11]),13); 419 x[10] ^= rotl((x[ 9]+x[ 8]),18); 420 x[12] ^= rotl((x[15]+x[14]), 7); 421 x[13] ^= rotl((x[12]+x[15]), 9); 422 x[14] ^= rotl((x[13]+x[12]),13); 423 x[15] ^= rotl((x[14]+x[13]),18); 424 } 425 } 426 427 /// HSalsa as defined in http://cr.yp.to/snuffle/xsalsa-20110204.pdf 428 /// Params: 429 /// key = 32 byte key. 430 /// nonce = 24 byte nonce. 431 /// 432 /// Returns: 256 bit value. 433 ubyte[32] HSalsa(uint rounds = 20)(in ubyte[] key, in ubyte[] nonce) pure nothrow @nogc 434 if(rounds == 12 || rounds == 20) 435 in { 436 assert(key.length == 32, "HSalsa requires 256 bit key."); 437 assert(nonce.length == 16, "HSalsa requires 128 bit nonce."); 438 } body { 439 uint[16] x; 440 uint[8] z; 441 442 scope(exit) { 443 wipe(x); 444 wipe(z); 445 } 446 447 x[0] = sigma[0]; 448 x[5] = sigma[1]; 449 x[10] = sigma[2]; 450 x[15] = sigma[3]; 451 452 fromLittleEndian!uint(key[0*4..4*4], x[1..5]); 453 fromLittleEndian!uint(key[4*4..8*4], x[11..15]); 454 455 fromLittleEndian!uint(nonce, x[6..10]); 456 457 salsaDoubleRound!rounds(x); 458 459 z[0] = x[0]; 460 z[1] = x[5]; 461 z[2] = x[10]; 462 z[3] = x[15]; 463 z[4..8] = x[6..10]; 464 465 ubyte[32] output; 466 toLittleEndian!uint(z, output); 467 return output; 468 }