1/*
2 * Copyright 2017-2022 The OpenSSL Project Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License 2.0 (the "License").  You may not use
5 * this file except in compliance with the License.  You can obtain a copy
6 * in the file LICENSE in the source distribution or at
7 * https://www.openssl.org/source/license.html
8 */
9
10/* Based on https://131002.net/siphash C reference implementation */
11/*
12   SipHash reference C implementation
13
14   Copyright (c) 2012-2016 Jean-Philippe Aumasson
15   Copyright (c) 2012-2014 Daniel J. Bernstein
16
17   To the extent possible under law, the author(s) have dedicated all copyright
18   and related and neighboring rights to this software to the public domain
19   worldwide. This software is distributed without any warranty.
20
21   You should have received a copy of the CC0 Public Domain Dedication along
22   with this software. If not, see
23   <http://creativecommons.org/publicdomain/zero/1.0/>.
24 */
25
26#include <stdlib.h>
27#include <string.h>
28#include <openssl/crypto.h>
29
30#include "crypto/siphash.h"
31
32#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
33
34#define U32TO8_LE(p, v)                                                        \
35    (p)[0] = (uint8_t)((v));                                                   \
36    (p)[1] = (uint8_t)((v) >> 8);                                              \
37    (p)[2] = (uint8_t)((v) >> 16);                                             \
38    (p)[3] = (uint8_t)((v) >> 24);
39
40#define U64TO8_LE(p, v)                                                        \
41    U32TO8_LE((p), (uint32_t)((v)));                                           \
42    U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
43
44#define U8TO64_LE(p)                                                           \
45    (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) |                        \
46     ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) |                 \
47     ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) |                 \
48     ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
49
50#define SIPROUND                                                               \
51    do {                                                                       \
52        v0 += v1;                                                              \
53        v1 = ROTL(v1, 13);                                                     \
54        v1 ^= v0;                                                              \
55        v0 = ROTL(v0, 32);                                                     \
56        v2 += v3;                                                              \
57        v3 = ROTL(v3, 16);                                                     \
58        v3 ^= v2;                                                              \
59        v0 += v3;                                                              \
60        v3 = ROTL(v3, 21);                                                     \
61        v3 ^= v0;                                                              \
62        v2 += v1;                                                              \
63        v1 = ROTL(v1, 17);                                                     \
64        v1 ^= v2;                                                              \
65        v2 = ROTL(v2, 32);                                                     \
66    } while (0)
67
68size_t SipHash_ctx_size(void)
69{
70    return sizeof(SIPHASH);
71}
72
73size_t SipHash_hash_size(SIPHASH *ctx)
74{
75    return ctx->hash_size;
76}
77
78static size_t siphash_adjust_hash_size(size_t hash_size)
79{
80    if (hash_size == 0)
81        hash_size = SIPHASH_MAX_DIGEST_SIZE;
82    return hash_size;
83}
84
85int SipHash_set_hash_size(SIPHASH *ctx, size_t hash_size)
86{
87    hash_size = siphash_adjust_hash_size(hash_size);
88    if (hash_size != SIPHASH_MIN_DIGEST_SIZE
89        && hash_size != SIPHASH_MAX_DIGEST_SIZE)
90        return 0;
91
92    /*
93     * It's possible that the key was set first.  If the hash size changes,
94     * we need to adjust v1 (see SipHash_Init().
95     */
96
97    /* Start by adjusting the stored size, to make things easier */
98    ctx->hash_size = siphash_adjust_hash_size(ctx->hash_size);
99
100    /* Now, adjust ctx->v1 if the old and the new size differ */
101    if ((size_t)ctx->hash_size != hash_size) {
102        ctx->v1 ^= 0xee;
103        ctx->hash_size = hash_size;
104    }
105    return 1;
106}
107
108/* hash_size = crounds = drounds = 0 means SipHash24 with 16-byte output */
109int SipHash_Init(SIPHASH *ctx, const unsigned char *k, int crounds, int drounds)
110{
111    uint64_t k0 = U8TO64_LE(k);
112    uint64_t k1 = U8TO64_LE(k + 8);
113
114    /* If the hash size wasn't set, i.e. is zero */
115    ctx->hash_size = siphash_adjust_hash_size(ctx->hash_size);
116
117    if (drounds == 0)
118        drounds = SIPHASH_D_ROUNDS;
119    if (crounds == 0)
120        crounds = SIPHASH_C_ROUNDS;
121
122    ctx->crounds = crounds;
123    ctx->drounds = drounds;
124
125    ctx->len = 0;
126    ctx->total_inlen = 0;
127
128    ctx->v0 = 0x736f6d6570736575ULL ^ k0;
129    ctx->v1 = 0x646f72616e646f6dULL ^ k1;
130    ctx->v2 = 0x6c7967656e657261ULL ^ k0;
131    ctx->v3 = 0x7465646279746573ULL ^ k1;
132
133    if (ctx->hash_size == SIPHASH_MAX_DIGEST_SIZE)
134        ctx->v1 ^= 0xee;
135
136    return 1;
137}
138
139void SipHash_Update(SIPHASH *ctx, const unsigned char *in, size_t inlen)
140{
141    uint64_t m;
142    const uint8_t *end;
143    int left;
144    unsigned int i;
145    uint64_t v0 = ctx->v0;
146    uint64_t v1 = ctx->v1;
147    uint64_t v2 = ctx->v2;
148    uint64_t v3 = ctx->v3;
149
150    ctx->total_inlen += inlen;
151
152    if (ctx->len) {
153        /* deal with leavings */
154        size_t available = SIPHASH_BLOCK_SIZE - ctx->len;
155
156        /* not enough to fill leavings */
157        if (inlen < available) {
158            memcpy(&ctx->leavings[ctx->len], in, inlen);
159            ctx->len += inlen;
160            return;
161        }
162
163        /* copy data into leavings and reduce input */
164        memcpy(&ctx->leavings[ctx->len], in, available);
165        inlen -= available;
166        in += available;
167
168        /* process leavings */
169        m = U8TO64_LE(ctx->leavings);
170        v3 ^= m;
171        for (i = 0; i < ctx->crounds; ++i)
172            SIPROUND;
173        v0 ^= m;
174    }
175    left = inlen & (SIPHASH_BLOCK_SIZE-1); /* gets put into leavings */
176    end = in + inlen - left;
177
178    for (; in != end; in += 8) {
179        m = U8TO64_LE(in);
180        v3 ^= m;
181        for (i = 0; i < ctx->crounds; ++i)
182            SIPROUND;
183        v0 ^= m;
184    }
185
186    /* save leavings and other ctx */
187    if (left)
188        memcpy(ctx->leavings, end, left);
189    ctx->len = left;
190
191    ctx->v0 = v0;
192    ctx->v1 = v1;
193    ctx->v2 = v2;
194    ctx->v3 = v3;
195}
196
197int SipHash_Final(SIPHASH *ctx, unsigned char *out, size_t outlen)
198{
199    /* finalize hash */
200    unsigned int i;
201    uint64_t b = ctx->total_inlen << 56;
202    uint64_t v0 = ctx->v0;
203    uint64_t v1 = ctx->v1;
204    uint64_t v2 = ctx->v2;
205    uint64_t v3 = ctx->v3;
206
207    if (ctx->crounds == 0 || outlen == 0 || outlen != (size_t)ctx->hash_size)
208        return 0;
209
210    switch (ctx->len) {
211    case 7:
212        b |= ((uint64_t)ctx->leavings[6]) << 48;
213        /* fall thru */
214    case 6:
215        b |= ((uint64_t)ctx->leavings[5]) << 40;
216        /* fall thru */
217    case 5:
218        b |= ((uint64_t)ctx->leavings[4]) << 32;
219        /* fall thru */
220    case 4:
221        b |= ((uint64_t)ctx->leavings[3]) << 24;
222        /* fall thru */
223    case 3:
224        b |= ((uint64_t)ctx->leavings[2]) << 16;
225        /* fall thru */
226    case 2:
227        b |= ((uint64_t)ctx->leavings[1]) <<  8;
228        /* fall thru */
229    case 1:
230        b |= ((uint64_t)ctx->leavings[0]);
231    case 0:
232        break;
233    }
234
235    v3 ^= b;
236    for (i = 0; i < ctx->crounds; ++i)
237        SIPROUND;
238    v0 ^= b;
239    if (ctx->hash_size == SIPHASH_MAX_DIGEST_SIZE)
240        v2 ^= 0xee;
241    else
242        v2 ^= 0xff;
243    for (i = 0; i < ctx->drounds; ++i)
244        SIPROUND;
245    b = v0 ^ v1 ^ v2  ^ v3;
246    U64TO8_LE(out, b);
247    if (ctx->hash_size == SIPHASH_MIN_DIGEST_SIZE)
248        return 1;
249    v1 ^= 0xdd;
250    for (i = 0; i < ctx->drounds; ++i)
251        SIPROUND;
252    b = v0 ^ v1 ^ v2  ^ v3;
253    U64TO8_LE(out + 8, b);
254    return 1;
255}
256