1 module dcrypt.crypto.digests.keccak; 2 3 import dcrypt.crypto.digest; 4 import std.conv:text; 5 import dcrypt.exceptions; 6 import dcrypt.errors; 7 import std.exception: enforce; 8 9 alias WrapperDigest!Keccak224 Keccak224Digest; 10 alias WrapperDigest!Keccak256 Keccak256Digest; 11 alias WrapperDigest!Keccak288 Keccak288Digest; 12 alias WrapperDigest!Keccak384 Keccak384Digest; 13 alias WrapperDigest!Keccak512 Keccak512Digest; 14 15 alias Keccak!224 Keccak224; 16 alias Keccak!256 Keccak256; 17 alias Keccak!288 Keccak288; 18 alias Keccak!384 Keccak384; 19 alias Keccak!512 Keccak512; 20 21 22 static assert(isDigest!Keccak224); 23 static assert(isDigest!Keccak256); 24 static assert(isDigest!Keccak288); 25 static assert(isDigest!Keccak384); 26 static assert(isDigest!Keccak512); 27 28 /// Test Keccak 29 unittest { 30 import dcrypt.util.encoders.hex; 31 32 33 immutable string[] plaintexts = ["","","","", 34 "00", 35 "0000000000000000000000000000000000000000000000000000000000000000", 36 "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 37 ]; 38 immutable uint[] bitLen = [224,256,384,512, 256, 256,512]; 39 40 41 immutable string[] hexHashes = [ 42 "f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd", 43 "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", 44 "2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff", 45 "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", 46 "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", // 00 47 "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", // 32x0 48 "a8620b2ebeca41fbc773bb837b5e724d6eb2de570d99858df0d7d97067fb8103b21757873b735097b35d3bea8fd1c359a9e8a63c1540c76c9784cf8d975e995c", 49 ]; 50 51 Digest[] digests = [ 52 new Keccak224Digest, 53 new Keccak256Digest, 54 new Keccak384Digest, 55 new Keccak512Digest, 56 new Keccak256Digest, 57 new Keccak256Digest, 58 new Keccak512Digest 59 ]; 60 61 for(size_t i = 0; i < plaintexts.length; ++i) { 62 Digest digest = digests[i]; 63 ubyte[] plain = hexDecode(plaintexts[i]); 64 ubyte[] expectedHash = hexDecode(hexHashes[i]); 65 66 digest.start(); 67 digest.put(plain); 68 69 ubyte[] actualHash = digest.doFinal(); 70 71 assert(expectedHash == actualHash, "produced wrong hash: " ~ toHexStr(actualHash) 72 ~ " instead of " ~ toHexStr(expectedHash)); 73 } 74 } 75 76 77 /** 78 * implementation of SHA-3 based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ 79 * Following the naming conventions used in the C source code to enable easy review of the implementation. 80 */ 81 @safe 82 public struct Keccak(uint bitLength) 83 if(bitLength == 224 || bitLength == 256 || bitLength == 288 || bitLength == 384 || bitLength == 512) 84 { 85 86 alias put update; 87 88 public { 89 90 enum name = text("Keccak", bitLength); 91 enum digestLength = bitLength / 8; 92 enum byteLength = rate / 8; /// size of block that the compression function is applied to in bytes 93 enum blockSize = 0; 94 95 @nogc 96 void put(in ubyte[] input...) nothrow 97 { 98 doUpdate(input, input.length*8); 99 } 100 101 /// Calculate the final hash value. 102 /// Params: 103 /// output = buffer for hash value. 104 /// Returns: length of hash value in bytes. 105 uint doFinal(ubyte[] output) nothrow @nogc { 106 squeeze(output, bitLength); 107 start(); 108 return bitLength/8; 109 } 110 111 /// Calculate the final hash value. 112 /// Returns: the hash value 113 ubyte[digestLength] finish() nothrow @nogc { 114 ubyte[digestLength] buf; 115 doFinal(buf); 116 return buf; 117 } 118 119 void start() nothrow @nogc 120 { 121 initSponge!(rate, capacity)(); 122 } 123 } 124 125 private: 126 127 enum capacity = bitLength*2; 128 enum rate = 1600 - capacity; 129 130 uint bitsInQueue; 131 bool squeezing; 132 uint bitsAvailableForSqueezing; 133 ubyte[1600 / 8] state; 134 ubyte[1536 / 8] dataQueue; 135 ubyte[rate / 8] chunk; 136 137 private nothrow @nogc: 138 139 void clearDataQueueSection(uint off, uint len) { 140 dataQueue[off..off+len] = 0; 141 } 142 143 void doUpdate(in ubyte[] data, ulong databitlen) 144 { 145 if ((databitlen % 8) == 0) 146 { 147 absorb(data, databitlen); 148 } 149 else 150 { 151 absorb(data, databitlen - (databitlen % 8)); 152 153 ubyte[1] lastByte; 154 155 lastByte[0] = cast(ubyte)(data[(databitlen / 8)] >>> (8 - (databitlen % 8))); 156 absorb(lastByte, databitlen % 8); 157 } 158 } 159 160 void initSponge(uint rate, uint capacity)() 161 if(rate + capacity == 1600 && rate % 64 == 0) 162 { 163 static assert(chunk.length == rate / 8); 164 165 state[] = 0; 166 dataQueue[] = 0; 167 bitsInQueue = 0; 168 squeezing = false; 169 bitsAvailableForSqueezing = 0; 170 } 171 172 void absorbQueue() 173 { 174 KeccakAbsorb(state, dataQueue[0..rate / 8]); 175 176 bitsInQueue = 0; 177 } 178 179 void absorb(in ubyte[] data, ulong databitlen) 180 in { 181 assert ((bitsInQueue % 8) == 0, "attempt to absorb with odd length queue."); 182 assert(!squeezing, "attempt to absorb while squeezing."); 183 } 184 body { 185 ulong i, j, wholeBlocks; 186 187 i = 0; 188 while (i < databitlen) 189 { 190 if ((bitsInQueue == 0) && (databitlen >= rate) && (i <= (databitlen - rate))) 191 { 192 wholeBlocks = (databitlen - i) / rate; 193 194 for (j = 0; j < wholeBlocks; j++) 195 { 196 chunk[0..$] = data[(i / 8) + (j * chunk.length)..$]; 197 KeccakAbsorb(state, chunk); 198 } 199 200 i += wholeBlocks * rate; 201 } 202 else 203 { 204 uint partialBlock = cast(uint)(databitlen - i); 205 if (partialBlock + bitsInQueue > rate) 206 { 207 partialBlock = rate - bitsInQueue; 208 } 209 uint partialByte = partialBlock % 8; 210 partialBlock -= partialByte; 211 212 dataQueue[bitsInQueue / 8 .. bitsInQueue / 8 + partialBlock / 8] 213 = data[i / 8 .. i / 8 + partialBlock / 8]; 214 215 bitsInQueue += partialBlock; 216 i += partialBlock; 217 if (bitsInQueue == rate) 218 { 219 absorbQueue(); 220 } 221 if (partialByte > 0) 222 { 223 uint mask = (1 << partialByte) - 1; 224 dataQueue[bitsInQueue / 8] = cast(ubyte)(data[(i / 8)] & mask); 225 bitsInQueue += partialByte; 226 i += partialByte; 227 } 228 } 229 } 230 } 231 232 void padAndSwitchToSqueezingPhase() 233 { 234 if (bitsInQueue + 1 == rate) 235 { 236 dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8); 237 absorbQueue(); 238 clearDataQueueSection(0, rate / 8); 239 } 240 else 241 { 242 clearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8); 243 dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8); 244 } 245 dataQueue[(rate - 1) / 8] |= 1 << ((rate - 1) % 8); 246 absorbQueue(); 247 248 if (rate == 1024) 249 { 250 KeccakExtract1024bits(state, dataQueue); 251 bitsAvailableForSqueezing = 1024; 252 } 253 else 254 255 { 256 KeccakExtract(state, dataQueue, rate / 64); 257 bitsAvailableForSqueezing = rate; 258 } 259 squeezing = true; 260 } 261 262 263 void squeeze(ubyte[] output, ulong outputLength) 264 in { 265 assert(outputLength % 8 == 0, "outputLength not a multiple of 8"); 266 } 267 body { 268 ulong i; 269 uint partialBlock; 270 271 if (!squeezing) 272 { 273 padAndSwitchToSqueezingPhase(); 274 } 275 i = 0; 276 while (i < outputLength) 277 { 278 if (bitsAvailableForSqueezing == 0) 279 { 280 keccakPermutation(state); 281 282 if (rate == 1024) 283 { 284 KeccakExtract1024bits(state, dataQueue); 285 bitsAvailableForSqueezing = 1024; 286 } 287 else 288 289 { 290 KeccakExtract(state, dataQueue, rate / 64); 291 bitsAvailableForSqueezing = rate; 292 } 293 } 294 partialBlock = bitsAvailableForSqueezing; 295 if (cast(ulong)partialBlock > outputLength - i) 296 { 297 partialBlock = cast(uint)(outputLength - i); 298 } 299 300 output[i/8..i/8+partialBlock / 8] = dataQueue[(rate - bitsAvailableForSqueezing) / 8..(rate - bitsAvailableForSqueezing) / 8+partialBlock / 8]; 301 302 bitsAvailableForSqueezing -= partialBlock; 303 i += partialBlock; 304 } 305 } 306 307 void fromBytesToWords(ulong[] stateAsWords, in ubyte[] state) 308 { 309 for (uint i = 0; i < (1600 / 64); i++) 310 { 311 stateAsWords[i] = 0; 312 uint index = i * (64 / 8); 313 for (uint j = 0; j < (64 / 8); j++) 314 { 315 stateAsWords[i] |= (cast(ulong)state[index + j] & 0xff) << ((8 * j)); 316 } 317 } 318 } 319 320 void fromWordsToBytes(ubyte[] state, in ulong[] stateAsWords) 321 { 322 for (uint i = 0; i < (1600 / 64); i++) 323 { 324 uint index = i * (64 / 8); 325 for (uint j = 0; j < (64 / 8); j++) 326 { 327 state[index + j] = cast(ubyte)((stateAsWords[i] >>> ((8 * j))) & 0xFF); 328 } 329 } 330 } 331 332 333 void keccakPermutation(ubyte[] state) 334 { 335 ulong[this.state.length / 8] longState; 336 337 fromBytesToWords(longState, state); 338 keccakPermutationOnWords(longState); 339 fromWordsToBytes(state, longState); 340 } 341 342 void keccakPermutationAfterXor(ubyte[] state, ubyte[] data) 343 { 344 uint i; 345 346 state[0..data.length] ^= data[]; 347 348 keccakPermutation(state); 349 } 350 351 void keccakPermutationOnWords(ulong[] state) 352 { 353 foreach (uint i; 0..24) 354 { 355 theta(state); 356 rho(state); 357 pi(state); 358 chi(state); 359 iota(state, i); 360 } 361 } 362 363 void theta(ulong[] A) 364 { 365 ulong[5] C; 366 foreach (uint x; 0..5) 367 { 368 C[x] = 0; 369 foreach (uint y; 0..5) 370 { 371 C[x] ^= A[x + 5 * y]; 372 } 373 } 374 foreach (uint x; 0..5) 375 { 376 ulong dX = ((((C[(x + 1) % 5]) << 1) ^ ((C[(x + 1) % 5]) >>> (64 - 1)))) ^ C[(x + 4) % 5]; 377 foreach (uint y; 0..5) 378 { 379 A[x + 5 * y] ^= dX; 380 } 381 } 382 } 383 384 void rho(ulong[] A) 385 { 386 foreach (uint x; 0..5) 387 { 388 foreach (uint y; 0..5) 389 { 390 uint index = x + 5 * y; 391 A[index] = ((KeccakRhoOffsets[index] != 0) ? (((A[index]) << KeccakRhoOffsets[index]) ^ ((A[index]) >>> (64 - KeccakRhoOffsets[index]))) : A[index]); 392 } 393 } 394 } 395 396 397 void pi(ulong[] A) 398 { 399 ulong[25] tempA; 400 tempA[0..$] = A[0..$]; 401 402 foreach (uint x; 0..5) 403 { 404 foreach (uint y; 0..5) 405 { 406 A[y + 5 * ((2 * x + 3 * y) % 5)] = tempA[x + 5 * y]; 407 } 408 } 409 } 410 411 412 void chi(ulong[] A) 413 { 414 ulong[5] chiC; 415 foreach (uint y; 0..5) 416 { 417 foreach (uint x; 0..5) 418 { 419 chiC[x] = A[x + 5 * y] ^ ((~A[(((x + 1) % 5) + 5 * y)]) & A[(((x + 2) % 5) + 5 * y)]); 420 } 421 foreach (uint x; 0..5) 422 { 423 A[x + 5 * y] = chiC[x]; 424 } 425 } 426 } 427 428 void iota(ulong[] A, uint indexRound) 429 { 430 A[0] ^= KeccakRoundConstants[indexRound]; 431 } 432 433 void KeccakAbsorb(ubyte[] byteState, ubyte[] data) 434 { 435 keccakPermutationAfterXor(byteState, data); 436 } 437 438 void KeccakExtract1024bits(in ubyte[] byteState, ubyte[] data) 439 { 440 data[0..128] = byteState[0..128]; 441 } 442 443 void KeccakExtract(in ubyte[] byteState, ubyte[] data, uint laneCount) 444 { 445 data[0..laneCount*8] = byteState[0..laneCount*8]; 446 } 447 } 448 449 450 // Keccak constants 451 private @safe { 452 enum ulong[24] KeccakRoundConstants = keccakInitializeRoundConstants(); 453 enum uint[25] KeccakRhoOffsets = keccakInitializeRhoOffsets(); 454 455 ulong[24] keccakInitializeRoundConstants() pure nothrow @nogc 456 { 457 ulong[24] keccakRoundConstants; 458 ubyte[1] LFSRstate; 459 460 LFSRstate[0] = 0x01; 461 uint i, j, bitPosition; 462 463 for (i = 0; i < 24; i++) 464 { 465 keccakRoundConstants[i] = 0; 466 for (j = 0; j < 7; j++) 467 { 468 bitPosition = (1 << j) - 1; 469 if (LFSR86540(LFSRstate)) 470 { 471 keccakRoundConstants[i] ^= 1L << bitPosition; 472 } 473 } 474 } 475 476 return keccakRoundConstants; 477 } 478 479 bool LFSR86540(ubyte[] LFSR) pure nothrow @nogc 480 { 481 bool result = (((LFSR[0]) & 0x01) != 0); 482 if (((LFSR[0]) & 0x80) != 0) 483 { 484 LFSR[0] = cast(ubyte)(((LFSR[0]) << 1) ^ 0x71); 485 } 486 else 487 { 488 LFSR[0] <<= 1; 489 } 490 491 return result; 492 } 493 494 uint[25] keccakInitializeRhoOffsets() pure nothrow @nogc 495 { 496 uint[25] keccakRhoOffsets; 497 uint x, y, t, newX, newY; 498 499 keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = 0; 500 x = 1; 501 y = 0; 502 for (t = 0; t < 24; t++) 503 { 504 keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = ((t + 1) * (t + 2) / 2) % 64; 505 newX = (0 * x + 1 * y) % 5; 506 newY = (2 * x + 3 * y) % 5; 507 x = newX; 508 y = newY; 509 } 510 511 return keccakRhoOffsets; 512 } 513 }