1 module dcrypt.crypto.engines.salsa20; 2 3 public import dcrypt.crypto.streamcipher; 4 public import dcrypt.crypto.params.keyparameter; 5 6 import dcrypt.util.bitmanip: rotl=rotateLeft; 7 8 import dcrypt.util.pack; 9 import dcrypt.exceptions; 10 11 // test different keys, ivs and plain texts 12 unittest { 13 14 // test vectors generated with bouncycastle Salsa20 implementation 15 string[] keys = [ 16 x"00000000000000000000000000000000", 17 x"01010101010101010101010101010101", 18 x"02020202020202020202020202020202", 19 x"03030303030303030303030303030303", 20 x"04040404040404040404040404040404", 21 x"05050505050505050505050505050505", 22 x"0606060606060606060606060606060606060606060606060606060606060606", 23 x"0707070707070707070707070707070707070707070707070707070707070707", 24 x"0808080808080808080808080808080808080808080808080808080808080808", 25 x"0909090909090909090909090909090909090909090909090909090909090909", 26 x"0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", 27 x"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", 28 ]; 29 30 string[] ivs = [ 31 x"0101010101010101", 32 x"0202020202020202", 33 x"0303030303030303", 34 x"0404040404040404", 35 x"0505050505050505", 36 x"0606060606060606", 37 x"0707070707070707", 38 x"0808080808080808", 39 x"0909090909090909", 40 x"0a0a0a0a0a0a0a0a", 41 x"0b0b0b0b0b0b0b0b", 42 x"0c0c0c0c0c0c0c0c", 43 ]; 44 45 string[] plains = [ 46 x"0202020202020202020202020202020202020202020202020202020202020202", 47 x"0303030303030303030303030303030303030303030303030303030303030303", 48 x"0404040404040404040404040404040404040404040404040404040404040404", 49 x"0505050505050505050505050505050505050505050505050505050505050505", 50 x"0606060606060606060606060606060606060606060606060606060606060606", 51 x"0707070707070707070707070707070707070707070707070707070707070707", 52 x"0808080808080808080808080808080808080808080808080808080808080808", 53 x"0909090909090909090909090909090909090909090909090909090909090909", 54 x"0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", 55 x"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", 56 x"0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", 57 x"0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d", 58 ]; 59 60 string[] ciphers = [ 61 x"4280c83f61325a4b1c2ba27d5693c016a08b4eb5afe070991307a89a4f6787e7", 62 x"3f1d4e1227cd3483068269ed5914a61dedce4e0fe2736d29e9dac4404a994423", 63 x"364dc45c17c762f274fcfb1ec04a2c56105e1af3f487f3e72f8dd7d6a9f4d625", 64 x"75f0241f020912c83572024d36fa27a97e357de3177e0b66c5855d0492262145", 65 x"1a336b436f2fbc172944cb70309b5b683f0348e7dba08536fa7b452bb54ba8b9", 66 x"e850390fd95ac10048e85ac9443b2247f72e55215a865f21742ff1a62645d64d", 67 x"002d2a162c7143b5c74c4c928101d1479791c1d475beec08d361afdf92e82416", 68 x"6955d342676abe02a858d4b0edba48b5a015987643c5bedce850670f7fb5f0bc", 69 x"9034ddf6d47e56c8d1b29283794464485390d22669569a58e271b85fe75f62e7", 70 x"d39db03f960056879dcc76169e165cb49e9be7c73abead205ec9eeaf0997896e", 71 x"b9b544b4299c75723ddaee3f8781748ccf89cfef17a8468000e0e7d2d5359c22", 72 x"27760fc0a53dae75e8df893f0281e1ab7f8740bdc52d0527db2fa0d2d4273b0c", 73 ]; 74 75 streamCipherTest(new Salsa20Engine, keys, plains, ciphers, ivs); 76 } 77 78 alias StreamCipherWrapper!Salsa20 Salsa20Engine; 79 80 static assert(isStreamCipher!Salsa20, "Salsa20 is not a stream cipher!"); 81 82 /// 83 /// implementation of the Salsa20/20 stream cipher 84 /// 85 @safe 86 public struct Salsa20 { 87 88 public enum stateSize = 16; // 16, 32 bit ints = 64 bytes 89 public enum name = "Salsa20/20"; 90 91 private { 92 // constants 93 94 immutable static { 95 ubyte[] 96 sigma = cast(immutable ubyte[])"expand 32-byte k", 97 tau = cast(immutable ubyte[])"expand 16-byte k"; 98 } 99 100 /* 101 * variables to hold the state of the engine 102 * during encryption and decryption 103 */ 104 uint index = 0; 105 uint[stateSize] engineState; /// state 106 uint[stateSize] x ; /// internal buffer 107 ubyte[stateSize*4] keyStream; /// expanded state, 64 bytes 108 ubyte[32] workingKey; 109 size_t workingKeyLength = 32; 110 ubyte[8] workingIV; 111 bool initialized = false; 112 113 /* 114 * internal counter 115 */ 116 uint cW0, cW1, cW2; 117 118 invariant { 119 assert(workingKeyLength == 16 || workingKeyLength == 32, "invalid workingKey length: must be 16 or 32 bytes"); 120 } 121 122 } 123 124 @safe @nogc nothrow 125 ~this() { 126 import dcrypt.util.util: wipe; 127 128 wipe(engineState); 129 wipe(x); 130 wipe(keyStream); 131 wipe(workingKey); 132 wipe(workingIV); 133 } 134 135 /// Initialize the cipher. 136 /// 137 /// Params: 138 /// forEncryption = Not used because encryption and decryption is actually the same. 139 /// key = secret key 140 /// iv = Use a unique nonce per key. 141 public void start(bool forEncryption, in ubyte[] key, in ubyte[] iv) nothrow @nogc 142 in { 143 assert(key.length == 16 || key.length == 32, "Salsa20 needs 128 or 256 bit keys."); 144 assert(iv.length == 8, "Salsa20 needs a 8 byte IV."); 145 } 146 body { 147 setKey(key, iv); 148 } 149 150 /// 151 /// Throws: MaxBytesExceededException = if limit of 2^70 bytes is exceeded 152 /// 153 public ubyte returnByte(ubyte input) 154 { 155 if (limitExceeded()) 156 { 157 throw new MaxBytesExceededException("2^70 byte limit per IV. Change IV"); 158 } 159 160 if (index == 0) 161 { 162 generateKeyStream(keyStream); 163 164 if (++engineState[8] == 0) 165 { 166 ++engineState[9]; 167 } 168 } 169 170 ubyte output = keyStream[index]^input; 171 index = (index + 1) & 63; 172 173 return output; 174 } 175 176 /// 177 /// encrypt or decrypt input bytes but no more than 2^70! 178 /// 179 /// Params: 180 /// input = input bytes 181 /// output = buffer for output bytes. length must match input length. 182 /// 183 /// Throws: MaxBytesExceededException = if limit of 2^70 bytes is exceeded 184 /// 185 public void processBytes(in ubyte[] input, ubyte[] output) 186 in { 187 assert(output.length >= input.length, "output buffer too short"); 188 assert(initialized, "Salsa20Engine not initialized!"); 189 } 190 body { 191 192 // can't encrypt more than 2^70 bytes per iv 193 if (limitExceeded(input.length)) 194 { 195 throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded. Change IV!"); 196 } 197 198 for (size_t i = 0; i < input.length; i++) 199 { 200 if (index == 0) 201 { 202 generateKeyStream(keyStream); 203 204 if (++engineState[8] == 0) 205 { 206 ++engineState[9]; 207 } 208 } 209 210 output[i] = (keyStream[index]^input[i]); 211 index = (index + 1) & 63; 212 } 213 } 214 215 /// reset the cipher to its initial state 216 public void reset() nothrow @nogc 217 in { 218 assert(initialized, "not yet initialized"); 219 } 220 body { 221 setKey(workingKey, workingIV); 222 } 223 224 225 /** 226 * Salsa20 function 227 * 228 * Params: 229 * rounds = number of rounds (20 in default implementation) 230 * input = input data 231 * x = output buffer where keystream gets written to 232 */ 233 public final static void salsaCore(uint rounds)(in uint[] input, uint[] x) pure nothrow @nogc 234 in { 235 assert(input.length == stateSize, "invalid input length"); 236 assert(x.length == stateSize, "x: invalid length"); 237 238 } 239 body { 240 241 static assert(rounds % 2 == 0 || rounds > 0, "rounds must be a even number and > 0"); 242 243 x[] = input[]; 244 245 for (int i = rounds; i > 0; i -= 2) 246 { 247 x[ 4] ^= rotl((x[ 0]+x[12]), 7); 248 x[ 8] ^= rotl((x[ 4]+x[ 0]), 9); 249 x[12] ^= rotl((x[ 8]+x[ 4]),13); 250 x[ 0] ^= rotl((x[12]+x[ 8]),18); 251 x[ 9] ^= rotl((x[ 5]+x[ 1]), 7); 252 x[13] ^= rotl((x[ 9]+x[ 5]), 9); 253 x[ 1] ^= rotl((x[13]+x[ 9]),13); 254 x[ 5] ^= rotl((x[ 1]+x[13]),18); 255 x[14] ^= rotl((x[10]+x[ 6]), 7); 256 x[ 2] ^= rotl((x[14]+x[10]), 9); 257 x[ 6] ^= rotl((x[ 2]+x[14]),13); 258 x[10] ^= rotl((x[ 6]+x[ 2]),18); 259 x[ 3] ^= rotl((x[15]+x[11]), 7); 260 x[ 7] ^= rotl((x[ 3]+x[15]), 9); 261 x[11] ^= rotl((x[ 7]+x[ 3]),13); 262 x[15] ^= rotl((x[11]+x[ 7]),18); 263 x[ 1] ^= rotl((x[ 0]+x[ 3]), 7); 264 x[ 2] ^= rotl((x[ 1]+x[ 0]), 9); 265 x[ 3] ^= rotl((x[ 2]+x[ 1]),13); 266 x[ 0] ^= rotl((x[ 3]+x[ 2]),18); 267 x[ 6] ^= rotl((x[ 5]+x[ 4]), 7); 268 x[ 7] ^= rotl((x[ 6]+x[ 5]), 9); 269 x[ 4] ^= rotl((x[ 7]+x[ 6]),13); 270 x[ 5] ^= rotl((x[ 4]+x[ 7]),18); 271 x[11] ^= rotl((x[10]+x[ 9]), 7); 272 x[ 8] ^= rotl((x[11]+x[10]), 9); 273 x[ 9] ^= rotl((x[ 8]+x[11]),13); 274 x[10] ^= rotl((x[ 9]+x[ 8]),18); 275 x[12] ^= rotl((x[15]+x[14]), 7); 276 x[13] ^= rotl((x[12]+x[15]), 9); 277 x[14] ^= rotl((x[13]+x[12]),13); 278 x[15] ^= rotl((x[14]+x[13]),18); 279 } 280 281 // element wise addition 282 x[] += input[]; 283 284 } 285 286 // 287 // Private implementation 288 // 289 290 private: 291 292 /// Params: 293 /// keyBytes = key, 16 or 32 bytes 294 /// ivBytes = iv, exactly 8 bytes 295 void setKey(in ubyte[] keyBytes, in ubyte[] ivBytes) nothrow @nogc 296 in { 297 assert(keyBytes.length == 16 || keyBytes.length == 32, "invalid key length"); 298 assert(ivBytes.length == 8, "invalid iv length"); 299 } 300 body { 301 workingKeyLength = keyBytes.length; 302 workingKey[0..workingKeyLength] = keyBytes[]; 303 workingIV[] = ivBytes[]; 304 305 index = 0; 306 resetCounter(); 307 uint offset = 0; 308 ubyte[sigma.length] constants; 309 310 // Key 311 fromLittleEndian(workingKey[0..16], engineState[1..5]); 312 313 if (workingKeyLength == 32) 314 { 315 constants[] = sigma[]; 316 offset = 16; 317 } 318 else 319 { 320 constants[] = tau[]; 321 } 322 323 fromLittleEndian(workingKey[offset..offset+16], engineState[11..11+4]); 324 325 engineState[0] = fromLittleEndian!uint(constants[0..$]); 326 engineState[5] = fromLittleEndian!uint(constants[4..$]); 327 engineState[10] = fromLittleEndian!uint(constants[8..$]); 328 engineState[15] = fromLittleEndian!uint(constants[12..$]); 329 330 // IV 331 332 engineState[6] = fromLittleEndian!uint(workingIV[0..$]); 333 engineState[7] = fromLittleEndian!uint(workingIV[4..$]); 334 engineState[8] = 0; 335 engineState[9] = 0; 336 337 initialized = true; 338 } 339 340 /// generate a block (64 bytes) of keystream 341 void generateKeyStream(ubyte[] output) nothrow @nogc 342 in { 343 assert(output.length == stateSize*4, "invalid length of output buffer: 64 bytes required"); 344 } 345 body { 346 salsaCore!20(engineState, x); 347 toLittleEndian(x, output); 348 } 349 350 void resetCounter() nothrow @nogc 351 { 352 cW0 = 0; 353 cW1 = 0; 354 cW2 = 0; 355 } 356 357 bool limitExceeded() nothrow @nogc 358 { 359 if (++cW0 == 0) 360 { 361 if (++cW1 == 0) 362 { 363 return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) 364 } 365 } 366 367 return false; 368 } 369 370 /* 371 * test if limit will be exceeded for input of size len 372 */ 373 bool limitExceeded(size_t len) nothrow @nogc 374 { 375 cW0 += len; 376 if (cW0 < len && cW0 >= 0) 377 { 378 if (++cW1 == 0) 379 { 380 return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) 381 } 382 } 383 384 return false; 385 } 386 } 387