1 // Written in the D programming language.
2 
3 /**
4 This class provides an implementation of the SipHash cryptographic function.
5 Implementation is based on the following code (by Károly Lőrentey): https://github.com/attaswift/SipHash/blob/master/SipHash/SipHasher.swift
6 
7 Copyright: LightHouse Software, 2021
8 License:   $(HTTP https://github.com/aquaratixc/ESL-License, Experimental Software License 1.0).
9 Authors:   Oleg Bakharev,
10 		   Ilya Pertsev
11 
12 See also: 
13 	https://131002.net/siphash, https://github.com/attaswift/SipHash/blob/master/SipHash/SipHasher.swift		   
14 */
15 module styx2000.extrautil.siphash;
16 
17 public import styx2000.lowlevel.endianness : BYTE_ORDER;
18 
19 /**
20 	The class provides functionality to initialize the internal state of the SipHash hash function and generate a hash for byte arrays.
21 	Params:
22 	NUMBER_OF_COMPRESS_ROUNDS = Number of compression rounds (default: 2).
23 	NUMBER_OF_FINALIZATION_ROUNDS = Number of finalization rounds (default: 4).
24 */
25 class SipHash(ubyte NUMBER_OF_COMPRESS_ROUNDS = 2, ubyte NUMBER_OF_FINALIZATION_ROUNDS = 4)
26 {
27     private
28     {
29         /// Secret key: 0 - low part of key, 1 - high of key
30         ulong key0, key1;
31         
32         /// Internal state
33         ulong v0 = 0x736f6d6570736575;
34         ulong v1 = 0x646f72616e646f6d;
35         ulong v2 = 0x6c7967656e657261;
36         ulong v3 = 0x7465646279746573;
37 
38         ulong pendingBytes = 0;
39         ulong pendingByteCount = 0;
40         long byteCount = 0;
41     }
42 
43     private
44     {
45 		/**
46 		Constructs (restores) a unsigned long value (little-endian order of bytes in value) from the passed pointer to ubyte array. 
47 		For internal use.
48 	    Params:
49 		ptr = Pointer to unsigned byte array.
50 	  
51 	    */
52         ulong _toLEUlong(ubyte* ptr) @system @nogc
53         {
54             ulong h = 0x0000000000000000;
55             
56             for (int i = 0; i < 64; i += 8)
57             {
58 				h |= (cast(ulong) ptr[i >> 3]) << i;
59 			}
60 
61             return h;
62         }
63 
64 		/**
65 		Rotated shift of unsigned long value. 
66 		For internal use.
67 	    Params:
68 		value = Unsigned long value.
69 		amount = Number of position to shift.
70 	  
71 	    */
72         ulong _rotateLeft(ulong value, ulong amount) @nogc
73         {
74             return (value << amount) | (value >> (64 - amount));
75         }
76 
77 		/**
78 		One SipHash round. 
79 		For internal use.
80 	  
81 	    */
82         void _round() @nogc
83         {
84             v0 += v1;
85             v1 = _rotateLeft(v1, 13);
86             v1 ^= v0;
87             v0 = _rotateLeft(v0, 32);
88             v2 += v3;
89             v3 = _rotateLeft(v3, 16);
90             v3 ^= v2;
91             v0 += v3;
92             v3 = _rotateLeft(v3, 21);
93             v3 ^= v0;
94             v2 += v1;
95             v1 = _rotateLeft(v1, 17);
96             v1 ^= v2;
97             v2 = _rotateLeft(v2, 32);
98         }
99 
100 
101 		/**
102 		Compress one word. 
103 		For internal use.
104 	    Params:
105 		m = Unsigned long value (word for compressing).
106 	  
107 	    */
108         void _compress(ulong m) @nogc
109         {
110             v3 ^= m;
111             
112             for (ubyte i = 0; i < NUMBER_OF_COMPRESS_ROUNDS; i++)
113             {
114                 _round;
115             }
116             
117             v0 ^= m;
118         }
119 
120 		
121 		/**
122 		Finalization procedure.Needed for ending of hashing. 
123 		For internal use.
124 
125 	    */
126         ulong _finalize() @nogc
127         {
128             pendingBytes |= (cast(ulong) byteCount) << 56;
129             byteCount = -1;
130 
131             _compress(pendingBytes);
132 
133             v2 ^= 0xff;
134 
135             for (ubyte i = 0; i < NUMBER_OF_FINALIZATION_ROUNDS; i++)
136             {
137                 _round;
138             }
139 
140             return v0 ^ v1 ^ v2 ^ v3;
141         }
142     }
143 
144 	/**
145 	Default constructor. 
146 	Initializes the internal state of the function with a random 128-bit key (split into two 64-bit parts).
147 
148     */
149     this()
150     {
151         import std.random : Random, uniform, unpredictableSeed;
152 
153         auto rng = Random(unpredictableSeed);
154 
155         this(uniform(0UL, ulong.max, rng), uniform(0UL, ulong.max, rng));
156     }
157 
158 	/**
159 	Basic constructor. 
160 	Initializes the internal state with a 128-bit key, split into two 64-bit parts - the low-order and high-order parts of the key.
161 	Params:
162 	key0 = low part of key.
163 	key1 = high part of key.
164 
165     */	
166     this(ulong key0, ulong key1)
167     {
168         key0 = key0;
169         key1 = key1;
170 
171         v0 ^= key0;
172         v1 ^= key1;
173         v2 ^= key0;
174         v3 ^= key1;
175     }
176     
177     
178     /**
179 	Basic constructor. 
180 	Initializes the internal state with a 128-bit key. If key length less than 16 bytes (i.e 128 bit), the key will padded with padding method 2.
181 	Params:
182 	key = key as array of unsigned bytes.
183 
184     */	
185     this(ubyte[] key)
186     {
187 		static join8to64(ubyte[] block)
188 		{
189 			ulong number = block[0];
190 	
191 			for (byte j = 1; j < 8; j++)
192 			{
193 				number <<= 8;
194 				number |= block[j];
195 			}
196 	
197 			return number;
198 		}
199 	
200         if (key.length < 16)
201         {
202 			key ~= 0x01;
203 			
204 			while (key.length != 16)
205 			{
206 				key ~= 0x00;
207 			}
208 		}
209 		
210 		key0 = join8to64(key[0..8]);
211         key1 = join8to64(key[8..16]);
212 
213         v0 ^= key0;
214         v1 ^= key1;
215         v2 ^= key0;
216         v3 ^= key1;
217     }
218 
219 	/**
220 	Append bytes to internal hash-function state.
221     Params:
222 	buffer = Unsigned byte array.
223   
224     */
225     void append(ubyte[] buffer...)
226     {
227         import std.algorithm : min;
228 
229         auto i = 0;
230         
231         if (pendingByteCount > 0)
232         {
233             ulong readCount = min(buffer.length, 8 - pendingByteCount);
234             ulong m = 0;
235             
236             switch (readCount)
237             {
238 	            case 7:
239 	                m |= cast(ulong)(buffer[6]) << 48;
240 	                goto case;
241 	            case 6:
242 	                m |= cast(ulong)(buffer[5]) << 40;
243 	                goto case;
244 	            case 5:
245 	                m |= cast(ulong)(buffer[4]) << 32;
246 	                goto case;
247 	            case 4:
248 	                m |= cast(ulong)(buffer[3]) << 24;
249 	                goto case;
250 	            case 3:
251 	                m |= cast(ulong)(buffer[2]) << 16;
252 	                goto case;
253 	            case 2:
254 	                m |= cast(ulong)(buffer[1]) << 8;
255 	                goto case;
256 	            case 1:
257 	                m |= cast(ulong)(buffer[0]);
258 	                break;
259 	            default:
260 	                break;
261             }
262             
263             pendingBytes |= m << cast(ulong)(pendingByteCount << 3);
264             pendingByteCount += readCount;
265             i += readCount;
266 
267             if (pendingByteCount == 8)
268             {
269                 _compress(pendingBytes);
270                 pendingBytes = 0;
271                 pendingByteCount = 0;
272             }
273         }
274 
275         ulong left = (buffer.length - i) & 7;
276         ulong end = (buffer.length - i) - left;
277 
278         while (i < end)
279         {
280             ulong m = 0;
281             auto ptr = buffer[i .. i + 8].ptr;
282             _compress(_toLEUlong(ptr));
283             i += 8;
284         }
285 
286         switch (left)
287         {
288 	        case 7:
289 	            pendingBytes |= cast(ulong)(buffer[i + 6]) << 48;
290 	            goto case;
291 	        case 6:
292 	            pendingBytes |= cast(ulong)(buffer[i + 5]) << 40;
293 	            goto case;
294 	        case 5:
295 	            pendingBytes |= cast(ulong)(buffer[i + 4]) << 32;
296 	            goto case;
297 	        case 4:
298 	            pendingBytes |= cast(ulong)(buffer[i + 3]) << 24;
299 	            goto case;
300 	        case 3:
301 	            pendingBytes |= cast(ulong)(buffer[i + 2]) << 16;
302 	            goto case;
303 	        case 2:
304 	            pendingBytes |= cast(ulong)(buffer[i + 1]) << 8;
305 	            goto case;
306 	        case 1:
307 	            pendingBytes |= cast(ulong)(buffer[i]);
308 	            break;
309 	        default:
310 	            break;
311         }
312 
313         pendingByteCount = left;
314 
315         byteCount += buffer.length;
316     }
317 
318 	/**
319 	Perform finalization of hash-function and returns hash.
320 	Returns:
321 	64-bit hash of passed bytes.
322   
323     */
324     ulong finalize()
325     {
326         return _finalize();
327     }
328 }
329 
330 /**
331 Hashes a byte stream with the specified cryptographic key.
332 Params:
333 bytes = Array of unsigned bytes.
334 key = Array of two ulong for low and high parts of 128-bit key (default: zero key).
335 Returns:
336 64-bit hash of passed bytes.
337 
338 Typical usage:
339 ----
340 auto hash = hash8([0x65, 0x67, 0x67, 0x00]);
341 ----
342 */
343 auto hash8(ubyte[] bytes, ulong[2] key = [0UL, 0UL])
344 {
345     SipHash!(2, 4) sh = new SipHash!(2, 4)(key[0], key[1]);
346 
347     sh.append(bytes);
348 
349     return sh.finalize;
350 }
351 
352 /**
353 Hashes a string with the specified cryptographic key.
354 Params:
355 string = String for hashing.
356 key = Array of two ulong for low and high parts of 128-bit key (default: zero key).
357 Returns:
358 64-bit hash of passed bytes.
359 
360 Typical usage:
361 ----
362 auto hash = hash8(`Sample`);
363 ----
364 */
365 auto hash8(string s, ulong[2] key = [0UL, 0UL])
366 {
367     SipHash!(2, 4) sh = new SipHash!(2, 4)(key[0], key[1]);
368 
369     sh.append(cast(ubyte[]) s);
370 
371     return sh.finalize;
372 }
373 
374 /**
375 Hashes a byte stream with the specified cryptographic key.
376 Params:
377 bytes = Array of unsigned bytes.
378 key = Array of unsigned bytes for 128-bit key (default: zero key).
379 Returns:
380 64-bit hash of passed bytes.
381 
382 Typical usage:
383 ----
384 auto hash = hash8([0x65, 0x67, 0x67, 0x00]);
385 ----
386 */
387 auto hash(ubyte[] bytes, ubyte[] key = [0x0])
388 {
389     SipHash!(2, 4) sh = new SipHash!(2, 4)(key);
390 
391     sh.append(bytes);
392 
393     return sh.finalize;
394 }
395 
396 /**
397 Create string representation of some value in hexadecimal view with specified byte order.
398 Params:
399 byteOrder = Order of bytes.
400 value = Some value of basic D's type.
401 Returns:
402 String representation (hexadecimal digits) of value.
403 
404 Typical usage:
405 ----
406 import std.stdio : writeln;
407 
408 auto value = 0xabcdef01;
409 value.asHexWithOrder!(BYTE_ORDER.BIG_ENDIAN).writeln;    // prints "abcdef01"
410 value.asHexWithOrder!(BYTE_ORDER.LITTLE_ENDIAN).writeln; // prints "01efcdab"
411 ----
412 */
413 template asHexWithOrder(BYTE_ORDER byteOrder)
414 {
415 	private import std.traits : isBasicType;
416 	
417 	private auto asHexByte(ubyte value) {
418 		enum DIGIT   = `0123456789abcdef`;
419 		string hexs;
420 		
421 		hexs ~= DIGIT[value >> 4];
422 		hexs ~= DIGIT[value & 0x0f];
423 		
424 		return hexs;
425 	}
426 	
427 	auto asHexWithOrder(T)(T value) if (isBasicType!T)
428 	{
429 		string hexs;
430 		
431 		enum T mask = T(0xff);
432 		enum T size = T.sizeof;
433 
434         foreach (i; 0..size)
435         {
436             static if (byteOrder == BYTE_ORDER.LITTLE_ENDIAN)
437 	            T shift = i << 3;
438 	        else
439 	            T shift = (size - i - 1) << 3;
440 	            
441             auto e = cast(ubyte) ((value & (mask << shift)) >> shift);
442             
443             hexs ~= asHexByte(e);
444         }
445 		
446 		return hexs;
447 	}
448 }
449 
450 /**
451 Create string representation of some value in hexadecimal view with big_endian order of digits.
452 Params:
453 value = Some value of basic D's type.
454 Returns:
455 String representation (hexadecimal digits) of value.
456 
457 Typical usage:
458 ----
459 import std.stdio : writeln;
460 
461 auto value = 0xabcdef01;
462 asHexBE(value).writeln; // prints "abcdef01"
463 ----
464 */
465 alias asHexBE = asHexWithOrder!(BYTE_ORDER.BIG_ENDIAN);
466 
467 
468 /**
469 Create string representation of some value in hexadecimal view with little_endian order of digits.
470 Params:
471 value = Some value of basic D's type.
472 Returns:
473 String representation (hexadecimal digits) of value
474 
475 Typical usage:
476 ----
477 import std.stdio : writeln;
478 
479 auto value = 0xabcdef01;
480 asHexBE(value).writeln; // prints "01efcdab"
481 ----
482 */
483 alias asHexLE = asHexWithOrder!(BYTE_ORDER.LITTLE_ENDIAN);