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 }