From 5015eda8cad1d470dfa4e1f12464f4bcd6634b03 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 24 Dec 2021 13:04:08 +0100 Subject: Add libkeccak_zerocopy_{update,digest} and in libkeccak_generalised_sum_fd (requires man pages) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- LICENSE | 2 +- Makefile | 3 +- digest.c | 147 +++++++++++++++++++++++++++++++++-------- libkeccak.h | 56 ++++++++++++++++ libkeccak_generalised_sum_fd.c | 37 +++++++++-- libkeccak_zerocopy_chunksize.c | 5 ++ 6 files changed, 216 insertions(+), 34 deletions(-) create mode 100644 libkeccak_zerocopy_chunksize.c diff --git a/LICENSE b/LICENSE index 025c491..d2e32ce 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ ISC License -© 2014, 2015, 2017, 2019 Mattias Andrée +© 2014, 2015, 2017, 2019, 2021 Mattias Andrée Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/Makefile b/Makefile index 299c2e7..ed35ab5 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,8 @@ OBJ =\ libkeccak_state_wipe.o\ libkeccak_state_wipe_message.o\ libkeccak_state_wipe_sponge.o\ - libkeccak_unhex.o + libkeccak_unhex.o\ + libkeccak_zerocopy_chunksize.o HDR =\ libkeccak.h\ diff --git a/digest.c b/digest.c index 902d549..c7302f6 100644 --- a/digest.c +++ b/digest.c @@ -232,51 +232,56 @@ libkeccak_to_lane64(register const unsigned char *restrict message, register siz /** - * pad 10*1 + * Right-pad message with a 10*1-pad * - * @param state The hashing state, `state->M` and `state->mptr` will be updated, - * `state->M` should have `state->r / 8` bytes left over at the end - * @param bits The number of bits in the end of the message that does not make a whole byte + * @param r Should be `state->r` where `state` is the hashing state + * @param msg The message to append padding to; should have `r / 8` + * extra bytes allocated at the end for the function to + * write the pad to + * @param msglen The length of the message to append padding to + * @param bits The number of bits in the end of the message that does not make a whole byte + * @return The length of the message after padding */ LIBKECCAK_GCC_ONLY(__attribute__((__nonnull__, __nothrow__, __gnu_inline__))) -static inline void -libkeccak_pad10star1(register struct libkeccak_state *restrict state, register size_t bits) +static inline size_t +libkeccak_pad10star1(register size_t r, unsigned char *msg, size_t msglen, register size_t bits) { - register size_t r = (size_t)(state->r); - register size_t nrf = state->mptr - !!bits; + register size_t nrf = msglen - !!bits; register size_t len = (nrf << 3) | bits; register size_t ll = len % r; - register unsigned char b = (unsigned char)(bits ? (state->M[nrf] | (1 << bits)) : 1); + register unsigned char b = (unsigned char)(bits ? (msg[nrf] | (1 << bits)) : 1); if (r - 8 <= ll && ll <= r - 2) { - state->M[nrf] = (unsigned char)(b ^ 0x80); - state->mptr = nrf + 1; + msg[nrf] = (unsigned char)(b ^ 0x80); + msglen = nrf + 1; } else { len = ++nrf << 3; len = (len - (len % r) + (r - 8)) >> 3; - state->mptr = len + 1; + msglen = len + 1; - state->M[nrf - 1] = b; - __builtin_memset(state->M + nrf, 0, (len - nrf) * sizeof(char)); - state->M[len] = (unsigned char)0x80; + msg[nrf - 1] = b; + __builtin_memset(&msg[nrf], 0, (len - nrf) * sizeof(char)); + msg[len] = (unsigned char)0x80; } + return msglen; } /** * Perform the absorption phase * - * @param state The hashing state - * @param len The number of bytes from `state->M` to absorb + * @param state The hashing state + * @param message The bytes to absorb + * @param len The number of bytes from `message` to absorb */ LIBKECCAK_GCC_ONLY(__attribute__((__nonnull__, __nothrow__))) static void -libkeccak_absorption_phase(register struct libkeccak_state *restrict state, register size_t len) +libkeccak_absorption_phase(register struct libkeccak_state *restrict state, + register const unsigned char *restrict message, register size_t len) { register long int rr = state->r >> 3; register long int ww = state->w >> 3; register long int n = (long)len / rr; - register const unsigned char *restrict message = state->M; if (__builtin_expect(ww >= 8, 1)) { /* ww > 8 is impossible, it is just for optimisation possibilities. */ while (n--) { #define X(N) state->S[N] ^= libkeccak_to_lane64(message, len, rr, (size_t)(LANE_TRANSPOSE_MAP[N] * 8)); @@ -333,6 +338,28 @@ libkeccak_squeezing_phase(register struct libkeccak_state *restrict state, long } +/** + * Absorb more of the message to the Keccak sponge + * without copying the data to an internal buffer + * + * It is safe run zero-copy functions before non-zero-copy + * functions for the same state, running zero-copy functions + * after non-zero-copy functions on the other hand can + * cause the message to be misread + * + * @param state The hashing state + * @param msg The partial message + * @param msglen The length of the partial message; must be a + * multiple of `libkeccak_zerocopy_chunksize(state)` + * (undefined behaviour otherwise) + */ +void +libkeccak_zerocopy_update(struct libkeccak_state *restrict state, const void *restrict msg, size_t msglen) +{ + libkeccak_absorption_phase(state, msg, msglen); +} + + /** * Absorb more of the message to the Keccak sponge * without wiping sensitive data when possible @@ -361,10 +388,10 @@ libkeccak_fast_update(struct libkeccak_state *restrict state, const void *restri __builtin_memcpy(state->M + state->mptr, msg, msglen * sizeof(char)); state->mptr += msglen; len = state->mptr; - len -= state->mptr % (size_t)((state->r * state->b) >> 3); + len -= state->mptr % (size_t)(state->r >> 3); state->mptr -= len; - libkeccak_absorption_phase(state, len); + libkeccak_absorption_phase(state, state->M, len); __builtin_memmove(state->M, state->M + len, state->mptr * sizeof(char)); return 0; @@ -401,16 +428,82 @@ libkeccak_update(struct libkeccak_state *restrict state, const void *restrict ms __builtin_memcpy(state->M + state->mptr, msg, msglen * sizeof(char)); state->mptr += msglen; len = state->mptr; - len -= state->mptr % (size_t)((state->r * state->b) >> 3); + len -= state->mptr % (size_t)(state->r >> 3); state->mptr -= len; - libkeccak_absorption_phase(state, len); + libkeccak_absorption_phase(state, state->M, len); __builtin_memmove(state->M, state->M + len, state->mptr * sizeof(char)); return 0; } +/** + * Absorb the last part of the message and squeeze the Keccak sponge + * without copying the data to an internal buffer + * + * It is safe run zero-copy functions before non-zero-copy + * functions for the same state, running zero-copy functions + * after non-zero-copy functions on the other hand can + * cause the message to be misread + * + * @param state The hashing state + * @param msg_ The rest of the message; will be edited; extra memory + * shall be allocated such that `suffix` and a 10*1 pad (which + * is at least 2 bits long) can be added in a why the makes it's + * length a multiple of `libkeccak_zerocopy_chunksize(state)` + * @param msglen The length of the partial message + * @param bits The number of bits at the end of the message not covered by `msglen` + * @param suffix The suffix concatenate to the message, only '1':s and '0':s, and NUL-termination + * @param hashsum Output parameter for the hashsum, may be `NULL` + */ +void +libkeccak_zerocopy_digest(struct libkeccak_state *restrict state, void *restrict msg_, size_t msglen, + size_t bits, const char *restrict suffix, void *restrict hashsum) +{ + unsigned char *restrict msg = msg_; + auto unsigned char *restrict new; + register long int rr = state->r >> 3; + auto size_t suffix_len = suffix ? __builtin_strlen(suffix) : 0; + register size_t ext; + register long int i; + + if (!msg) { + msglen = 0; + bits = 0; + } else { + msglen += bits >> 3; + bits &= 7; + } + + if (bits) + msg[msglen] = msg[msglen] & (unsigned char)((1 << bits) - 1); + if (__builtin_expect(!!suffix_len, 1)) { + if (!bits) + msg[msglen] = 0; + while (suffix_len--) { + msg[msglen] |= (unsigned char)((*suffix++ & 1) << bits++); + if (bits == 8) { + bits = 0; + msg[++msglen] = 0; + } + } + } + if (bits) + msglen++; + + msglen = libkeccak_pad10star1((size_t)state->r, msg, msglen, bits); + libkeccak_absorption_phase(state, msg, msglen); + + if (hashsum) { + libkeccak_squeezing_phase(state, rr, (state->n + 7) >> 3, state->w >> 3, hashsum); + } else { + for (i = (state->n - 1) / state->r; i--;) + libkeccak_f(state); + } +} + + /** * Absorb the last part of the message and squeeze the Keccak sponge * without wiping sensitive data when possible @@ -473,8 +566,8 @@ libkeccak_fast_digest(struct libkeccak_state *restrict state, const void *restri if (bits) state->mptr++; - libkeccak_pad10star1(state, bits); - libkeccak_absorption_phase(state, state->mptr); + state->mptr = libkeccak_pad10star1((size_t)state->r, state->M, state->mptr, bits); + libkeccak_absorption_phase(state, state->M, state->mptr); if (hashsum) { libkeccak_squeezing_phase(state, rr, (state->n + 7) >> 3, state->w >> 3, hashsum); @@ -551,8 +644,8 @@ libkeccak_digest(struct libkeccak_state *restrict state, const void *restrict ms if (bits) state->mptr++; - libkeccak_pad10star1(state, bits); - libkeccak_absorption_phase(state, state->mptr); + state->mptr = libkeccak_pad10star1((size_t)state->r, state->M, state->mptr, bits); + libkeccak_absorption_phase(state, state->M, state->mptr); if (hashsum) { libkeccak_squeezing_phase(state, rr, (state->n + 7) >> 3, state->w >> 3, hashsum); diff --git a/libkeccak.h b/libkeccak.h index 745dd2c..67a0ccd 100644 --- a/libkeccak.h +++ b/libkeccak.h @@ -537,6 +537,38 @@ size_t libkeccak_state_marshal(const struct libkeccak_state *restrict, void *res LIBKECCAK_GCC_ONLY(__attribute__((__leaf__, __nonnull__(2)))) size_t libkeccak_state_unmarshal(struct libkeccak_state *restrict, const void *restrict); +/** + * Get the number of bytes that are absorbed during + * one pass of the absorption phase + * + * @param state The hashing state + * @return The number of bytes absorbed during one pass + */ +LIBKECCAK_GCC_ONLY(__attribute__((__nonnull__, __nothrow__, __warn_unused_result__, __pure__))) +inline size_t +libkeccak_zerocopy_chunksize(struct libkeccak_state *restrict state) +{ + return state->r >> 3; +} + +/** + * Absorb more of the message to the Keccak sponge + * without copying the data to an internal buffer + * + * It is safe run zero-copy functions before non-zero-copy + * functions for the same state, running zero-copy functions + * after non-zero-copy functions on the other hand can + * cause the message to be misread + * + * @param state The hashing state + * @param msg The partial message + * @param msglen The length of the partial message; must be a + * multiple of `libkeccak_zerocopy_chunksize(state)` + * (undefined behaviour otherwise) + */ +LIBKECCAK_GCC_ONLY(__attribute__((__nonnull__, __nothrow__))) +void libkeccak_zerocopy_update(struct libkeccak_state *restrict, const void *restrict, size_t); + /** * Absorb more of the message to the Keccak sponge * without wiping sensitive data when possible @@ -561,6 +593,30 @@ int libkeccak_fast_update(struct libkeccak_state *restrict, const void *restrict LIBKECCAK_GCC_ONLY(__attribute__((__nonnull__))) int libkeccak_update(struct libkeccak_state *restrict, const void *restrict, size_t); + +/** + * Absorb the last part of the message and squeeze the Keccak sponge + * without copying the data to an internal buffer + * + * It is safe run zero-copy functions before non-zero-copy + * functions for the same state, running zero-copy functions + * after non-zero-copy functions on the other hand can + * cause the message to be misread + * + * @param state The hashing state + * @param msg The rest of the message; will be edited; extra memory + * shall be allocated such that `suffix` and a 10*1 pad (which + * is at least 2 bits long) can be added in a why the makes it's + * length a multiple of `libkeccak_zerocopy_chunksize(state)` + * @param msglen The length of the partial message + * @param bits The number of bits at the end of the message not covered by `msglen` + * @param suffix The suffix concatenate to the message, only '1':s and '0':s, and NUL-termination + * @param hashsum Output parameter for the hashsum, may be `NULL` + */ +LIBKECCAK_GCC_ONLY(__attribute__((__nonnull__(1, 2)))) +void libkeccak_zerocopy_digest(struct libkeccak_state *restrict, void *restrict, size_t, + size_t, const char *restrict, void *restrict); + /** * Absorb the last part of the message and squeeze the Keccak sponge * without wiping sensitive data when possible diff --git a/libkeccak_generalised_sum_fd.c b/libkeccak_generalised_sum_fd.c index bc81451..78890b4 100644 --- a/libkeccak_generalised_sum_fd.c +++ b/libkeccak_generalised_sum_fd.c @@ -19,11 +19,16 @@ libkeccak_generalised_sum_fd(int fd, struct libkeccak_state *restrict state, con const char *restrict suffix, void *restrict hashsum) { ssize_t got; + size_t offset; #ifndef _WIN32 struct stat attr; #endif size_t blksize = 4096; - void *restrict chunk; + unsigned char *restrict chunk; + size_t chunksize = libkeccak_zerocopy_chunksize(state); + size_t extrasize = (strlen(suffix) + 2 + 7) >> 3; + size_t extrachunks = (extrasize + (chunksize - 1)) / chunksize; + size_t chunks, chunkmod; if (libkeccak_state_initialise(state, spec) < 0) return -1; @@ -34,6 +39,16 @@ libkeccak_generalised_sum_fd(int fd, struct libkeccak_state *restrict state, con blksize = (size_t)attr.st_blksize; #endif + chunks = blksize / chunksize; + chunkmod = blksize % chunksize; + if (chunkmod) { + blksize -= chunkmod; + blksize += chunksize; + chunks += 1; + } + if (chunks < extrachunks + 1) + blksize = (extrachunks + 1) * chunksize; + #if ALLOCA_LIMIT > 0 if (blksize > (size_t)ALLOCA_LIMIT) blksize = (size_t)ALLOCA_LIMIT; @@ -53,8 +68,9 @@ libkeccak_generalised_sum_fd(int fd, struct libkeccak_state *restrict state, con return -1; #endif + offset = 0; for (;;) { - got = read(fd, chunk, blksize); + got = read(fd, &chunk[offset], blksize - offset); if (got <= 0) { if (!got) break; @@ -62,11 +78,22 @@ libkeccak_generalised_sum_fd(int fd, struct libkeccak_state *restrict state, con continue; goto fail; } - if (libkeccak_fast_update(state, chunk, (size_t)got) < 0) - goto fail; + offset += (size_t)got; + if (offset == blksize) { + libkeccak_zerocopy_update(state, chunk, blksize); + offset = 0; + } + } + + if (extrasize > blksize - offset) { + chunkmod = offset % chunksize; + libkeccak_zerocopy_update(state, chunk, offset - chunkmod); + __builtin_memcpy(chunk, &chunk[offset - chunkmod], chunkmod * sizeof(char)); + offset = chunkmod; } - return libkeccak_fast_digest(state, NULL, 0, 0, suffix, hashsum); + libkeccak_zerocopy_digest(state, chunk, offset, 0, suffix, hashsum); + return 0; fail: #if ALLOCA_LIMIT <= 0 diff --git a/libkeccak_zerocopy_chunksize.c b/libkeccak_zerocopy_chunksize.c new file mode 100644 index 0000000..ddf3164 --- /dev/null +++ b/libkeccak_zerocopy_chunksize.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline size_t libkeccak_zerocopy_chunksize(struct libkeccak_state *restrict); -- cgit v1.2.3-70-g09d2