siphash.odin 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. package siphash
  2. /*
  3. Copyright 2022 zhibog
  4. Made available under the BSD-3 license.
  5. List of contributors:
  6. zhibog: Initial implementation.
  7. Implementation of the SipHash hashing algorithm, as defined at <https://github.com/veorq/SipHash> and <https://www.aumasson.jp/siphash/siphash.pdf>
  8. Use the specific procedures for a certain setup. The generic procdedures will default to Siphash 2-4
  9. */
  10. import "core:crypto"
  11. import "core:crypto/util"
  12. /*
  13. High level API
  14. */
  15. KEY_SIZE :: 16
  16. DIGEST_SIZE :: 8
  17. // sum_string_1_3 will hash the given message with the key and return
  18. // the computed hash as a u64
  19. sum_string_1_3 :: proc(msg, key: string) -> u64 {
  20. return sum_bytes_1_3(transmute([]byte)(msg), transmute([]byte)(key))
  21. }
  22. // sum_bytes_1_3 will hash the given message with the key and return
  23. // the computed hash as a u64
  24. sum_bytes_1_3 :: proc (msg, key: []byte) -> u64 {
  25. ctx: Context
  26. hash: u64
  27. init(&ctx, key, 1, 3)
  28. update(&ctx, msg)
  29. final(&ctx, &hash)
  30. return hash
  31. }
  32. // sum_string_to_buffer_1_3 will hash the given message with the key and write
  33. // the computed hash into the provided destination buffer
  34. sum_string_to_buffer_1_3 :: proc(msg, key: string, dst: []byte) {
  35. sum_bytes_to_buffer_1_3(transmute([]byte)(msg), transmute([]byte)(key), dst)
  36. }
  37. // sum_bytes_to_buffer_1_3 will hash the given message with the key and write
  38. // the computed hash into the provided destination buffer
  39. sum_bytes_to_buffer_1_3 :: proc(msg, key, dst: []byte) {
  40. assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8")
  41. hash := sum_bytes_1_3(msg, key)
  42. _collect_output(dst[:], hash)
  43. }
  44. sum_1_3 :: proc {
  45. sum_string_1_3,
  46. sum_bytes_1_3,
  47. sum_string_to_buffer_1_3,
  48. sum_bytes_to_buffer_1_3,
  49. }
  50. // verify_u64_1_3 will check if the supplied tag matches with the output you
  51. // will get from the provided message and key
  52. verify_u64_1_3 :: proc (tag: u64 msg, key: []byte) -> bool {
  53. return sum_bytes_1_3(msg, key) == tag
  54. }
  55. // verify_bytes will check if the supplied tag matches with the output you
  56. // will get from the provided message and key
  57. verify_bytes_1_3 :: proc (tag, msg, key: []byte) -> bool {
  58. derived_tag: [8]byte
  59. sum_bytes_to_buffer_1_3(msg, key, derived_tag[:])
  60. return crypto.compare_constant_time(derived_tag[:], tag) == 1
  61. }
  62. verify_1_3 :: proc {
  63. verify_bytes_1_3,
  64. verify_u64_1_3,
  65. }
  66. // sum_string_2_4 will hash the given message with the key and return
  67. // the computed hash as a u64
  68. sum_string_2_4 :: proc(msg, key: string) -> u64 {
  69. return sum_bytes_2_4(transmute([]byte)(msg), transmute([]byte)(key))
  70. }
  71. // sum_bytes_2_4 will hash the given message with the key and return
  72. // the computed hash as a u64
  73. sum_bytes_2_4 :: proc (msg, key: []byte) -> u64 {
  74. ctx: Context
  75. hash: u64
  76. init(&ctx, key, 2, 4)
  77. update(&ctx, msg)
  78. final(&ctx, &hash)
  79. return hash
  80. }
  81. // sum_string_to_buffer_2_4 will hash the given message with the key and write
  82. // the computed hash into the provided destination buffer
  83. sum_string_to_buffer_2_4 :: proc(msg, key: string, dst: []byte) {
  84. sum_bytes_to_buffer_2_4(transmute([]byte)(msg), transmute([]byte)(key), dst)
  85. }
  86. // sum_bytes_to_buffer_2_4 will hash the given message with the key and write
  87. // the computed hash into the provided destination buffer
  88. sum_bytes_to_buffer_2_4 :: proc(msg, key, dst: []byte) {
  89. assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8")
  90. hash := sum_bytes_2_4(msg, key)
  91. _collect_output(dst[:], hash)
  92. }
  93. sum_2_4 :: proc {
  94. sum_string_2_4,
  95. sum_bytes_2_4,
  96. sum_string_to_buffer_2_4,
  97. sum_bytes_to_buffer_2_4,
  98. }
  99. sum_string :: sum_string_2_4
  100. sum_bytes :: sum_bytes_2_4
  101. sum_string_to_buffer :: sum_string_to_buffer_2_4
  102. sum_bytes_to_buffer :: sum_bytes_to_buffer_2_4
  103. sum :: proc {
  104. sum_string,
  105. sum_bytes,
  106. sum_string_to_buffer,
  107. sum_bytes_to_buffer,
  108. }
  109. // verify_u64_2_4 will check if the supplied tag matches with the output you
  110. // will get from the provided message and key
  111. verify_u64_2_4 :: proc (tag: u64 msg, key: []byte) -> bool {
  112. return sum_bytes_2_4(msg, key) == tag
  113. }
  114. // verify_bytes will check if the supplied tag matches with the output you
  115. // will get from the provided message and key
  116. verify_bytes_2_4 :: proc (tag, msg, key: []byte) -> bool {
  117. derived_tag: [8]byte
  118. sum_bytes_to_buffer_2_4(msg, key, derived_tag[:])
  119. return crypto.compare_constant_time(derived_tag[:], tag) == 1
  120. }
  121. verify_2_4 :: proc {
  122. verify_bytes_2_4,
  123. verify_u64_2_4,
  124. }
  125. verify_bytes :: verify_bytes_2_4
  126. verify_u64 :: verify_u64_2_4
  127. verify :: proc {
  128. verify_bytes,
  129. verify_u64,
  130. }
  131. // sum_string_4_8 will hash the given message with the key and return
  132. // the computed hash as a u64
  133. sum_string_4_8 :: proc(msg, key: string) -> u64 {
  134. return sum_bytes_4_8(transmute([]byte)(msg), transmute([]byte)(key))
  135. }
  136. // sum_bytes_4_8 will hash the given message with the key and return
  137. // the computed hash as a u64
  138. sum_bytes_4_8 :: proc (msg, key: []byte) -> u64 {
  139. ctx: Context
  140. hash: u64
  141. init(&ctx, key, 4, 8)
  142. update(&ctx, msg)
  143. final(&ctx, &hash)
  144. return hash
  145. }
  146. // sum_string_to_buffer_4_8 will hash the given message with the key and write
  147. // the computed hash into the provided destination buffer
  148. sum_string_to_buffer_4_8 :: proc(msg, key: string, dst: []byte) {
  149. sum_bytes_to_buffer_4_8(transmute([]byte)(msg), transmute([]byte)(key), dst)
  150. }
  151. // sum_bytes_to_buffer_4_8 will hash the given message with the key and write
  152. // the computed hash into the provided destination buffer
  153. sum_bytes_to_buffer_4_8 :: proc(msg, key, dst: []byte) {
  154. assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8")
  155. hash := sum_bytes_4_8(msg, key)
  156. _collect_output(dst[:], hash)
  157. }
  158. sum_4_8 :: proc {
  159. sum_string_4_8,
  160. sum_bytes_4_8,
  161. sum_string_to_buffer_4_8,
  162. sum_bytes_to_buffer_4_8,
  163. }
  164. // verify_u64_4_8 will check if the supplied tag matches with the output you
  165. // will get from the provided message and key
  166. verify_u64_4_8 :: proc (tag: u64 msg, key: []byte) -> bool {
  167. return sum_bytes_4_8(msg, key) == tag
  168. }
  169. // verify_bytes will check if the supplied tag matches with the output you
  170. // will get from the provided message and key
  171. verify_bytes_4_8 :: proc (tag, msg, key: []byte) -> bool {
  172. derived_tag: [8]byte
  173. sum_bytes_to_buffer_4_8(msg, key, derived_tag[:])
  174. return crypto.compare_constant_time(derived_tag[:], tag) == 1
  175. }
  176. verify_4_8 :: proc {
  177. verify_bytes_4_8,
  178. verify_u64_4_8,
  179. }
  180. /*
  181. Low level API
  182. */
  183. init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) {
  184. assert(len(key) == KEY_SIZE, "crypto/siphash: Invalid key size, want 16")
  185. ctx.c_rounds = c_rounds
  186. ctx.d_rounds = d_rounds
  187. is_valid_setting := (ctx.c_rounds == 1 && ctx.d_rounds == 3) ||
  188. (ctx.c_rounds == 2 && ctx.d_rounds == 4) ||
  189. (ctx.c_rounds == 4 && ctx.d_rounds == 8)
  190. assert(is_valid_setting, "crypto/siphash: Incorrect rounds set up. Valid pairs are (1,3), (2,4) and (4,8)")
  191. ctx.k0 = util.U64_LE(key[:8])
  192. ctx.k1 = util.U64_LE(key[8:])
  193. ctx.v0 = 0x736f6d6570736575 ~ ctx.k0
  194. ctx.v1 = 0x646f72616e646f6d ~ ctx.k1
  195. ctx.v2 = 0x6c7967656e657261 ~ ctx.k0
  196. ctx.v3 = 0x7465646279746573 ~ ctx.k1
  197. ctx.is_initialized = true
  198. }
  199. update :: proc(ctx: ^Context, data: []byte) {
  200. assert(ctx.is_initialized, "crypto/siphash: Context is not initialized")
  201. ctx.last_block = len(data) / 8 * 8
  202. ctx.buf = data
  203. i := 0
  204. m: u64
  205. for i < ctx.last_block {
  206. m = u64(ctx.buf[i] & 0xff)
  207. i += 1
  208. for r in u64(1)..<8 {
  209. m |= u64(ctx.buf[i] & 0xff) << (r * 8)
  210. i += 1
  211. }
  212. ctx.v3 ~= m
  213. for _ in 0..<ctx.c_rounds {
  214. _compress(ctx)
  215. }
  216. ctx.v0 ~= m
  217. }
  218. }
  219. final :: proc(ctx: ^Context, dst: ^u64) {
  220. m: u64
  221. for i := len(ctx.buf) - 1; i >= ctx.last_block; i -= 1 {
  222. m <<= 8
  223. m |= u64(ctx.buf[i] & 0xff)
  224. }
  225. m |= u64(len(ctx.buf) << 56)
  226. ctx.v3 ~= m
  227. for _ in 0..<ctx.c_rounds {
  228. _compress(ctx)
  229. }
  230. ctx.v0 ~= m
  231. ctx.v2 ~= 0xff
  232. for _ in 0..<ctx.d_rounds {
  233. _compress(ctx)
  234. }
  235. dst^ = ctx.v0 ~ ctx.v1 ~ ctx.v2 ~ ctx.v3
  236. reset(ctx)
  237. }
  238. reset :: proc(ctx: ^Context) {
  239. ctx.k0, ctx.k1 = 0, 0
  240. ctx.v0, ctx.v1 = 0, 0
  241. ctx.v2, ctx.v3 = 0, 0
  242. ctx.last_block = 0
  243. ctx.c_rounds = 0
  244. ctx.d_rounds = 0
  245. ctx.is_initialized = false
  246. }
  247. Context :: struct {
  248. v0, v1, v2, v3: u64, // State values
  249. k0, k1: u64, // Split key
  250. c_rounds: int, // Number of message rounds
  251. d_rounds: int, // Number of finalization rounds
  252. buf: []byte, // Provided data
  253. last_block: int, // Offset from the last block
  254. is_initialized: bool,
  255. }
  256. _get_byte :: #force_inline proc "contextless" (byte_num: byte, into: u64) -> byte {
  257. return byte(into >> (((~byte_num) & (size_of(u64) - 1)) << 3))
  258. }
  259. _collect_output :: #force_inline proc "contextless" (dst: []byte, hash: u64) {
  260. dst[0] = _get_byte(7, hash)
  261. dst[1] = _get_byte(6, hash)
  262. dst[2] = _get_byte(5, hash)
  263. dst[3] = _get_byte(4, hash)
  264. dst[4] = _get_byte(3, hash)
  265. dst[5] = _get_byte(2, hash)
  266. dst[6] = _get_byte(1, hash)
  267. dst[7] = _get_byte(0, hash)
  268. }
  269. _compress :: #force_inline proc "contextless" (ctx: ^Context) {
  270. ctx.v0 += ctx.v1
  271. ctx.v1 = util.ROTL64(ctx.v1, 13)
  272. ctx.v1 ~= ctx.v0
  273. ctx.v0 = util.ROTL64(ctx.v0, 32)
  274. ctx.v2 += ctx.v3
  275. ctx.v3 = util.ROTL64(ctx.v3, 16)
  276. ctx.v3 ~= ctx.v2
  277. ctx.v0 += ctx.v3
  278. ctx.v3 = util.ROTL64(ctx.v3, 21)
  279. ctx.v3 ~= ctx.v0
  280. ctx.v2 += ctx.v1
  281. ctx.v1 = util.ROTL64(ctx.v1, 17)
  282. ctx.v1 ~= ctx.v2
  283. ctx.v2 = util.ROTL64(ctx.v2, 32)
  284. }