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);