1 // Written in the D programming language. 2 3 /** 4 5 $(BOOKTABLE , 6 $(TR $(TH Category) $(TH Functions)) 7 $(TR $(TDNW Template API) $(TD $(MYREF SipHash))) 8 $(TR $(TDNW Helpers) $(TD $(MYREF siphash24Of))) 9 ) 10 11 * SipHash: a fast short-input PRF 12 * 13 * Example: 14 * ----- 15 * // Create key 16 * ubyte[16] key = cast(ubyte[])"To be|not to be!"; 17 * // Compute hash with key and arbitrary message 18 * ulong hashed = siphash24Of(key, cast(ubyte[])"that is the question."); 19 * assert(hashed == 17352353082512417190); 20 * ----- 21 * 22 * See_Also: 23 * $(LINK2 https://www.131002.net/siphash/, SipHash: a fast short-input PRF) 24 * 25 * Copyright: Copyright Masahiro Nakagawa 2012-. 26 * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>. 27 * Authors: Masahiro Nakagawa 28 */ 29 module siphash; 30 31 import std.bitmanip : littleEndianToNative, nativeToLittleEndian; 32 33 /** 34 * siphash template, which takes SipRound C and D parameters 35 */ 36 template siphash(size_t C, size_t D) 37 { 38 /** 39 * Computes SipHash hashes of arbitrary data. 40 * 41 * Params: 42 * key = 16 byte key to hash 43 * message = an arbitrary message 44 * 45 * Returns: 46 * a 8 byte hash value. 47 */ 48 @safe pure nothrow 49 ulong siphashOf(in ubyte[16] key, in ubyte[] message) 50 { 51 return siphashOf(u8to64_le(key.ptr), u8to64_le(key.ptr, BlockSize), message); 52 } 53 54 /// ditto 55 @safe pure nothrow 56 ulong siphashOf(in ulong k0, in ulong k1, in ubyte[] message) 57 { 58 ulong v0 = k0 ^ 0x736f6d6570736575UL; 59 ulong v1 = k1 ^ 0x646f72616e646f6dUL; 60 ulong v2 = k0 ^ 0x6c7967656e657261UL; 61 ulong v3 = k1 ^ 0x7465646279746573UL; 62 63 size_t index; 64 for (size_t blocks = message.length & ~7; index < blocks; index += BlockSize) { 65 immutable mi = u8to64_le(message.ptr, index); 66 v3 ^= mi; 67 foreach (Unused; 0..C) 68 mixin(SipRound); 69 v0 ^= mi; 70 } 71 72 ulong tail = cast(ulong)(message.length & 0xff) << 56; 73 switch (message.length % BlockSize) { 74 case 7: tail |= cast(ulong)message[index + 6] << 48; goto case 6; 75 case 6: tail |= cast(ulong)message[index + 5] << 40; goto case 5; 76 case 5: tail |= cast(ulong)message[index + 4] << 32; goto case 4; 77 case 4: tail |= cast(ulong)message[index + 3] << 24; goto case 3; 78 case 3: tail |= cast(ulong)message[index + 2] << 16; goto case 2; 79 case 2: tail |= cast(ulong)message[index + 1] << 8; goto case 1; 80 case 1: tail |= cast(ulong)message[index]; break; 81 default: 82 break; 83 } 84 85 v3 ^= tail; 86 foreach (Unused; 0..C) 87 mixin(SipRound); 88 v0 ^= tail; 89 90 v2 ^= 0xff; 91 foreach (Unused; 0..D) 92 mixin(SipRound); 93 94 return v0 ^ v1 ^ v2 ^ v3; 95 } 96 } 97 98 alias siphash!(2, 4).siphashOf siphash24Of; 99 100 /** 101 * SipHash object implements std.digest like API for supporting streaming update. 102 * 103 * Example: 104 * ----- 105 * ubyte[16] key = cast(ubyte[])"To be|not to be!"; 106 * auto sh = SipHash!(2, 4)(key); 107 * 108 * sh.start(); 109 * foreach (chunk; chunks(cast(ubyte[])"that is the question.", 2)) 110 * sh.put(chunk); 111 * auto hashed = sh.finish(); 112 * ----- 113 */ 114 struct SipHash(size_t C, size_t D) 115 { 116 private: 117 immutable ulong k0, k1; 118 ulong v0, v1, v2, v3; 119 120 size_t processedLength; 121 const(ubyte)[] message; 122 123 124 public: 125 @safe pure nothrow 126 { 127 /** 128 * Constructs SipHash with 16 byte key. 129 */ 130 this(in ubyte[16] key) 131 { 132 this(u8to64_le(key.ptr), u8to64_le(key.ptr, BlockSize)); 133 } 134 135 /** 136 * Constructs SipHash with two 8 byte key numbers. 137 */ 138 this(in ulong key0, in ulong key1) 139 { 140 k0 = key0; 141 k1 = key1; 142 } 143 144 /** 145 * Used to initialize the SipHash. 146 */ 147 void start() 148 { 149 v0 = k0 ^ 0x736f6d6570736575UL; 150 v1 = k1 ^ 0x646f72616e646f6dUL; 151 v2 = k0 ^ 0x6c7967656e657261UL; 152 v3 = k1 ^ 0x7465646279746573UL; 153 processedLength = 0; 154 message = message.init; 155 } 156 157 /** 158 * Use this to feed the digest with data. 159 * Also implements the $(XREF range, OutputRange) interface for $(D ubyte) and $(D const(ubyte)[]). 160 */ 161 void put(scope const(ubyte)[] data...) 162 { 163 message ~= data; 164 165 size_t index; 166 for (size_t blocks = message.length & ~7; index < blocks; 167 index += BlockSize, processedLength += BlockSize) { 168 immutable mi = u8to64_le(message.ptr, index); 169 v3 ^= mi; 170 foreach (Unused; 0..C) 171 mixin(SipRound); 172 v0 ^= mi; 173 } 174 175 if (index != 0) 176 message = message[index..$]; 177 } 178 179 /** 180 * Returns the finished SipHash hash as ubyte[8], not ulong. 181 * This also calls $(LREF start) to reset the internal state. 182 */ 183 ubyte[8] finish() 184 { 185 ulong tail = cast(ulong)((processedLength + message.length) & 0xff) << 56; 186 switch (message.length % BlockSize) { 187 case 7: tail |= cast(ulong)message[6] << 48; goto case 6; 188 case 6: tail |= cast(ulong)message[5] << 40; goto case 5; 189 case 5: tail |= cast(ulong)message[4] << 32; goto case 4; 190 case 4: tail |= cast(ulong)message[3] << 24; goto case 3; 191 case 3: tail |= cast(ulong)message[2] << 16; goto case 2; 192 case 2: tail |= cast(ulong)message[1] << 8; goto case 1; 193 case 1: tail |= cast(ulong)message[0]; break; 194 default: 195 break; 196 } 197 198 v3 ^= tail; 199 foreach (Unused; 0..C) 200 mixin(SipRound); 201 v0 ^= tail; 202 203 v2 ^= 0xff; 204 foreach (Unused; 0..D) 205 mixin(SipRound); 206 207 ubyte[8] result = nativeToLittleEndian(v0 ^ v1 ^ v2 ^ v3); 208 209 start(); 210 211 return result; 212 } 213 } 214 } 215 216 private: 217 218 enum BlockSize = ulong.sizeof; 219 220 enum SipRound = " 221 v0 += v1; 222 v1 = rotl(v1, 13); 223 v1 ^= v0; 224 v0 = rotl(v0, 32); 225 226 v2 += v3; 227 v3 = rotl(v3, 16); 228 v3 ^= v2; 229 230 v2 += v1; 231 v1 = rotl(v1, 17); 232 v1 ^= v2; 233 v2 = rotl(v2, 32); 234 235 v0 += v3; 236 v3 = rotl(v3, 21); 237 v3 ^= v0; 238 "; 239 240 @safe pure nothrow 241 ulong rotl(in ulong u, in uint s) 242 { 243 return (u << s) | (u >> (64 - s)); 244 } 245 246 @trusted pure nothrow 247 ulong u8to64_le(in ubyte* ptr, in size_t i = 0) 248 { 249 return *cast(ulong*)(ptr + i); 250 } 251 252 unittest 253 { 254 import std.conv; 255 import std.range : chunks; 256 257 /* 258 SipHash-2-4 output with 259 key = 00 01 02 ... 260 and 261 message = (empty string) 262 message = 00 (1 byte) 263 message = 00 01 (2 bytes) 264 message = 00 01 02 (3 bytes) 265 ... 266 message = 00 01 02 ... 3e (63 bytes) 267 */ 268 ulong[64] testVectors = [ 269 0x726fdb47dd0e0e31UL, 0x74f839c593dc67fdUL, 0x0d6c8009d9a94f5aUL, 0x85676696d7fb7e2dUL, 270 0xcf2794e0277187b7UL, 0x18765564cd99a68dUL, 0xcbc9466e58fee3ceUL, 0xab0200f58b01d137UL, 271 0x93f5f5799a932462UL, 0x9e0082df0ba9e4b0UL, 0x7a5dbbc594ddb9f3UL, 0xf4b32f46226bada7UL, 272 0x751e8fbc860ee5fbUL, 0x14ea5627c0843d90UL, 0xf723ca908e7af2eeUL, 0xa129ca6149be45e5UL, 273 0x3f2acc7f57c29bdbUL, 0x699ae9f52cbe4794UL, 0x4bc1b3f0968dd39cUL, 0xbb6dc91da77961bdUL, 274 0xbed65cf21aa2ee98UL, 0xd0f2cbb02e3b67c7UL, 0x93536795e3a33e88UL, 0xa80c038ccd5ccec8UL, 275 0xb8ad50c6f649af94UL, 0xbce192de8a85b8eaUL, 0x17d835b85bbb15f3UL, 0x2f2e6163076bcfadUL, 276 0xde4daaaca71dc9a5UL, 0xa6a2506687956571UL, 0xad87a3535c49ef28UL, 0x32d892fad841c342UL, 277 0x7127512f72f27cceUL, 0xa7f32346f95978e3UL, 0x12e0b01abb051238UL, 0x15e034d40fa197aeUL, 278 0x314dffbe0815a3b4UL, 0x027990f029623981UL, 0xcadcd4e59ef40c4dUL, 0x9abfd8766a33735cUL, 279 0x0e3ea96b5304a7d0UL, 0xad0c42d6fc585992UL, 0x187306c89bc215a9UL, 0xd4a60abcf3792b95UL, 280 0xf935451de4f21df2UL, 0xa9538f0419755787UL, 0xdb9acddff56ca510UL, 0xd06c98cd5c0975ebUL, 281 0xe612a3cb9ecba951UL, 0xc766e62cfcadaf96UL, 0xee64435a9752fe72UL, 0xa192d576b245165aUL, 282 0x0a8787bf8ecb74b2UL, 0x81b3e73d20b49b6fUL, 0x7fa8220ba3b2eceaUL, 0x245731c13ca42499UL, 283 0xb78dbfaf3a8d83bdUL, 0xea1ad565322a1a0bUL, 0x60e61c23a3795013UL, 0x6606d7e446282b93UL, 284 0x6ca4ecb15c5f91e1UL, 0x9f626da15c9625f3UL, 0xe51b38608ef25f57UL, 0x958a324ceb064572UL 285 ]; 286 287 ubyte[16] key; 288 foreach (ubyte i; 0..16) 289 key[i] = i; 290 291 auto sh = SipHash!(2, 4)(key); 292 ulong calcViaStreaming(ubyte[] message) 293 { 294 sh.start(); 295 foreach (chunk; chunks(message, 3)) 296 sh.put(chunk); 297 return littleEndianToNative!ulong(sh.finish()); 298 } 299 300 ubyte[] message; 301 foreach (ubyte i; 0..64) { 302 auto result = siphash24Of(key, message); 303 assert(result == testVectors[i], "test vector failed for " ~ to!string(i)); 304 assert(calcViaStreaming(message) == testVectors[i], 305 "test vector failed for " ~ to!string(i) ~ " in streaming"); 306 307 message ~= i; 308 } 309 }