From 29440eedac7968580e35e751c6cdf94af337a95a Mon Sep 17 00:00:00 2001 From: Collin Funk Date: Mon, 23 Feb 2026 00:20:46 -0800 Subject: [PATCH] Add SHA-3 support to secure-hash * admin/merge-gnulib (GNULIB_MODULES): Add crypto/sha3-buffer. * lib/sha3.c: New file, imported by running admin/merge-gnulib. * lib/sha3.h: Likewise. * m4/sha3.m4: Likewise. * lib/gnulib.mk.in: Updated by admin/merge-gnulib. * m4/gnulib-comp.m4: Likewise. * src/fns.c: Include sha3.h (Fsecure_hash_algorithms): Add Qsha3_224, Qsha3_256, Qsha3_384, and Qsha3_512. (secure_hash): Likewise. (Fsecure_hash): List the SHA-3 algorithms in the docstring. (syms_of_fns): Define Qsha3_224, Qsha3_256, Qsha3_384, and Qsha3_512. * test/lisp/net/gnutls-tests.el (gnutls-tests-internal-macs-upcased): Filter out the new SHA-3 algorithms since they are currently not implemented in gnutls. * test/src/fns-tests.el (test-secure-hash): Add test cases for the new algorithms. * doc/lispref/text.texi (Checksum/Hash): List the SHA-3 algorithms. Mention that they are considered secure. * etc/NEWS: Mention the new feature. --- admin/merge-gnulib | 1 + doc/lispref/text.texi | 35 ++- etc/NEWS | 7 + lib/gnulib.mk.in | 11 + lib/sha3.c | 442 ++++++++++++++++++++++++++++++++++ lib/sha3.h | 132 ++++++++++ m4/gnulib-comp.m4 | 6 + m4/sha3.m4 | 16 ++ src/fns.c | 58 +++-- test/lisp/net/gnutls-tests.el | 7 +- test/src/fns-tests.el | 12 + 11 files changed, 701 insertions(+), 26 deletions(-) create mode 100644 lib/sha3.c create mode 100644 lib/sha3.h create mode 100644 m4/sha3.m4 diff --git a/admin/merge-gnulib b/admin/merge-gnulib index 247fca96df2..6d60dcd687a 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -35,6 +35,7 @@ GNULIB_MODULES=' careadlinkat close-stream copy-file-range crypto/md5 crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha512-buffer + crypto/sha3-buffer d-type diffseq double-slash-root dtoastr dtotimespec dup2 environ execinfo faccessat fchmodat fcntl fcntl-h fdopendir file-has-acl diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index a313480944b..f1adc7fc722 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -4983,15 +4983,15 @@ that you have an unaltered copy of that data. @cindex message digest Emacs supports several common cryptographic hash algorithms: MD5, -SHA-1, SHA-2, SHA-224, SHA-256, SHA-384 and SHA-512. MD5 is the -oldest of these algorithms, and is commonly used in @dfn{message -digests} to check the integrity of messages transmitted over a -network. MD5 and SHA-1 are not collision resistant (i.e., it is -possible to deliberately design different pieces of data which have -the same MD5 or SHA-1 hash), so you should not use them for anything -security-related. For security-related applications you should use -the other hash types, such as SHA-2 (e.g., @code{sha256} or -@code{sha512}). +SHA-1, SHA-2, SHA-224, SHA-256, SHA-384, SHA-512, SHA3-224, SHA3-256, +SHA3-384, and SHA3-512. MD5 is the oldest of these algorithms, and is +commonly used in @dfn{message digests} to check the integrity of +messages transmitted over a network. MD5 and SHA-1 are not collision +resistant (i.e., it is possible to deliberately design different pieces +of data which have the same MD5 or SHA-1 hash), so you should not use +them for anything security-related. For security-related applications +you should use the other hash types, such as SHA-2 (e.g., @code{sha256} +or @code{sha512}) or SHA-3 (e.g., @code{sha3-256} or @code{sha3-512}). @defun secure-hash-algorithms This function returns a list of symbols representing algorithms that @@ -5001,8 +5001,9 @@ This function returns a list of symbols representing algorithms that @defun secure-hash algorithm object &optional start end binary This function returns a hash for @var{object}. The argument @var{algorithm} is a symbol stating which hash to compute: one of -@code{md5}, @code{sha1}, @code{sha224}, @code{sha256}, @code{sha384} -or @code{sha512}. The argument @var{object} should be a buffer or a +@code{md5}, @code{sha1}, @code{sha224}, @code{sha256}, @code{sha384}, +@code{sha512}, @code{sha3-224}, @code{sha3-256}, @code{sha3-384}, or +@code{sha3-512}. The argument @var{object} should be a buffer or a string. The optional arguments @var{start} and @var{end} are character @@ -5035,6 +5036,18 @@ non-@code{nil}). @item For @code{sha512}: 128 characters (64 bytes if @var{binary} is non-@code{nil}). +@item +For @code{sha3-224}: 56 characters (28 bytes if @var{binary} is +non-@code{nil}). +@item +For @code{sha3-256}: 64 characters (32 bytes if @var{binary} is +non-@code{nil}). +@item +For @code{sha3-384}: 96 characters (48 bytes if @var{binary} is +non-@code{nil}). +@item +For @code{sha3-512}: 128 characters (64 bytes if @var{binary} is +non-@code{nil}). @end itemize This function does not compute the hash directly from the internal diff --git a/etc/NEWS b/etc/NEWS index ac4aebd406c..8cf1abf8ea7 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3955,6 +3955,13 @@ that will provide an Xref backend when used. * Lisp Changes in Emacs 31.1 ++++ +** 'secure-hash' now supports generating SHA-3 message digests. +The list returned by 'secure-hash-algorithms' now contains the symbols +'sha3-224', 'sha3-256', 'sha3-384', and 'sha3-512'. These symbols can +be used as the ALGORITHM argument of 'secure-hash' to generate SHA-3 +hashes. + +++ ** New function 'garbage-collect-heapsize'. Same as 'garbage-collect' but just returns the info from the last GC diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in index d7203c16fde..053d234a474 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in @@ -99,6 +99,7 @@ # crypto/md5-buffer \ # crypto/sha1-buffer \ # crypto/sha256-buffer \ +# crypto/sha3-buffer \ # crypto/sha512-buffer \ # d-type \ # diffseq \ @@ -1853,6 +1854,16 @@ EXTRA_DIST += gl_openssl.h sha256.h endif ## end gnulib module crypto/sha256-buffer +## begin gnulib module crypto/sha3-buffer +ifeq (,$(OMIT_GNULIB_MODULE_crypto/sha3-buffer)) + +libgnu_a_SOURCES += sha3.c + +EXTRA_DIST += sha3.h + +endif +## end gnulib module crypto/sha3-buffer + ## begin gnulib module crypto/sha512-buffer ifeq (,$(OMIT_GNULIB_MODULE_crypto/sha512-buffer)) diff --git a/lib/sha3.c b/lib/sha3.c new file mode 100644 index 00000000000..1bebbc76950 --- /dev/null +++ b/lib/sha3.c @@ -0,0 +1,442 @@ +/* sha3.c - Functions to calculate SHA-3 hashes as specified by FIPS-202. + Copyright (C) 2025-2026 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Collin Funk , 2025. */ + +#include + +/* Specification. */ +#include "sha3.h" + +#include +#include +#include + +#include +#ifdef WORDS_BIGENDIAN +# define SWAP(n) u64bswap (n) +#else +# define SWAP(n) (n) +#endif + +#if ! HAVE_OPENSSL_SHA3 + +static const u64 rc[] = { + u64init (0x00000000, 0x00000001), u64init (0x00000000, 0x00008082), + u64init (0x80000000, 0x0000808A), u64init (0x80000000, 0x80008000), + u64init (0x00000000, 0x0000808B), u64init (0x00000000, 0x80000001), + u64init (0x80000000, 0x80008081), u64init (0x80000000, 0x00008009), + u64init (0x00000000, 0x0000008A), u64init (0x00000000, 0x00000088), + u64init (0x00000000, 0x80008009), u64init (0x00000000, 0x8000000A), + u64init (0x00000000, 0x8000808B), u64init (0x80000000, 0x0000008B), + u64init (0x80000000, 0x00008089), u64init (0x80000000, 0x00008003), + u64init (0x80000000, 0x00008002), u64init (0x80000000, 0x00000080), + u64init (0x00000000, 0x0000800A), u64init (0x80000000, 0x8000000A), + u64init (0x80000000, 0x80008081), u64init (0x80000000, 0x00008080), + u64init (0x00000000, 0x80000001), u64init (0x80000000, 0x80008008) +}; + +#define DEFINE_SHA3_INIT_CTX(SIZE) \ + bool \ + sha3_##SIZE##_init_ctx (struct sha3_ctx *ctx) \ + { \ + memset (&ctx->state, '\0', sizeof ctx->state); \ + ctx->buflen = 0; \ + ctx->digestlen = SHA3_##SIZE##_DIGEST_SIZE; \ + ctx->blocklen = SHA3_##SIZE##_BLOCK_SIZE; \ + return true; \ + } + +DEFINE_SHA3_INIT_CTX (224) +DEFINE_SHA3_INIT_CTX (256) +DEFINE_SHA3_INIT_CTX (384) +DEFINE_SHA3_INIT_CTX (512) + +void +sha3_free_ctx (_GL_UNUSED struct sha3_ctx *ctx) +{ + /* Do nothing. */ +} + +/* Copy the value from V into the memory location pointed to by *CP, + If your architecture allows unaligned access, this is equivalent to + * (__typeof__ (v) *) cp = v */ +static void +set_uint64 (char *cp, u64 v) +{ + memcpy (cp, &v, sizeof v); +} + +void * +sha3_read_ctx (struct sha3_ctx const *restrict ctx, void *restrict resbuf) +{ + char *r = resbuf; + size_t words = ctx->digestlen / sizeof *ctx->state; + size_t bytes = ctx->digestlen % sizeof *ctx->state; + + int i; + for (i = 0; i < words; ++i, r += sizeof *ctx->state) + set_uint64 (r, SWAP (ctx->state[i])); + if (bytes) + { + u64 word = ctx->state[i]; + do + { + *r++ = u64getlo (word) & 0xFF; + word = u64shr (word, 8); + } + while (--bytes); + } + return resbuf; +} + +static void +sha3_conclude_ctx (struct sha3_ctx *ctx) +{ + ctx->buffer[ctx->buflen++] = 0x06; + memset (ctx->buffer + ctx->buflen, '\0', ctx->blocklen - ctx->buflen); + ctx->buffer[ctx->blocklen - 1] |= 0x80; + sha3_process_block (ctx->buffer, ctx->blocklen, ctx); +} + +void * +sha3_finish_ctx (struct sha3_ctx *restrict ctx, void *restrict resbuf) +{ + sha3_conclude_ctx (ctx); + return sha3_read_ctx (ctx, resbuf); +} + +#define DEFINE_SHA3_BUFFER(SIZE) \ + void * \ + sha3_##SIZE##_buffer (char const *restrict buffer, size_t len, \ + void *restrict resblock) \ + { \ + struct sha3_ctx ctx; \ + sha3_##SIZE##_init_ctx (&ctx); \ + sha3_process_bytes (buffer, len, &ctx); \ + return sha3_finish_ctx (&ctx, resblock); \ + } + +DEFINE_SHA3_BUFFER (224) +DEFINE_SHA3_BUFFER (256) +DEFINE_SHA3_BUFFER (384) +DEFINE_SHA3_BUFFER (512) + +bool +sha3_process_bytes (void const *restrict buffer, size_t len, + struct sha3_ctx *restrict ctx) +{ + char const *buf = buffer; + + if (0 < ctx->buflen) + { + size_t left = ctx->blocklen - ctx->buflen; + if (len < left) + { + /* Not enough to fill a full block. */ + memcpy (ctx->buffer + ctx->buflen, buf, len); + ctx->buflen += len; + return true; + } + /* Process the block that already had bytes buffered. */ + memcpy (ctx->buffer + ctx->buflen, buf, left); + buf += left; + len -= left; + sha3_process_block (ctx->buffer, ctx->blocklen, ctx); + } + + /* Process as many complete blocks as possible. */ + size_t full_len = len - len % ctx->blocklen; + sha3_process_block (buf, full_len, ctx); + buf += full_len; + len -= full_len; + + memcpy (ctx->buffer, buf, len); + ctx->buflen = len; + return true; +} + +bool +sha3_process_block (void const *restrict buffer, size_t len, + struct sha3_ctx *restrict ctx) +{ + u64 *a = ctx->state; + const u64 *words = buffer; + size_t nwords = len / sizeof *words; + const u64 *endp = words + nwords; + + while (words < endp) + { + for (size_t i = 0; i < ctx->blocklen / sizeof *ctx->state; ++i, ++words) + ctx->state[i] = u64xor (ctx->state[i], SWAP (*words)); + for (int i = 0; i < 24; ++i) + { + u64 c[5]; + u64 d[5]; + u64 t1; + u64 t2; + + /* Theta step 1. */ + c[0] = u64xor (u64xor (u64xor (u64xor (a[0], a[5]), a[10]), + a[15]), a[20]); + c[1] = u64xor (u64xor (u64xor (u64xor (a[1], a[6]), a[11]), + a[16]), a[21]); + c[2] = u64xor (u64xor (u64xor (u64xor (a[2], a[7]), a[12]), + a[17]), a[22]); + c[3] = u64xor (u64xor (u64xor (u64xor (a[3], a[8]), a[13]), + a[18]), a[23]); + c[4] = u64xor (u64xor (u64xor (u64xor (a[4], a[9]), a[14]), + a[19]), a[24]); + + /* Theta step 2. */ + d[0] = u64xor (c[4], u64rol (c[1], 1)); + d[1] = u64xor (c[0], u64rol (c[2], 1)); + d[2] = u64xor (c[1], u64rol (c[3], 1)); + d[3] = u64xor (c[2], u64rol (c[4], 1)); + d[4] = u64xor (c[3], u64rol (c[0], 1)); + + /* Theta step 3. */ + a[0] = u64xor (a[0], d[0]); + a[5] = u64xor (a[5], d[0]); + a[10] = u64xor (a[10], d[0]); + a[15] = u64xor (a[15], d[0]); + a[20] = u64xor (a[20], d[0]); + a[1] = u64xor (a[1], d[1]); + a[6] = u64xor (a[6], d[1]); + a[11] = u64xor (a[11], d[1]); + a[16] = u64xor (a[16], d[1]); + a[21] = u64xor (a[21], d[1]); + a[2] = u64xor (a[2], d[2]); + a[7] = u64xor (a[7], d[2]); + a[12] = u64xor (a[12], d[2]); + a[17] = u64xor (a[17], d[2]); + a[22] = u64xor (a[22], d[2]); + a[3] = u64xor (a[3], d[3]); + a[8] = u64xor (a[8], d[3]); + a[13] = u64xor (a[13], d[3]); + a[18] = u64xor (a[18], d[3]); + a[23] = u64xor (a[23], d[3]); + a[4] = u64xor (a[4], d[4]); + a[9] = u64xor (a[9], d[4]); + a[14] = u64xor (a[14], d[4]); + a[19] = u64xor (a[19], d[4]); + a[24] = u64xor (a[24], d[4]); + + /* Rho and Pi. */ + t1 = a[1]; + t2 = u64rol (t1, 1); + t1 = a[10]; + a[10] = t2; + t2 = u64rol (t1, 3); + t1 = a[7]; + a[7] = t2; + t2 = u64rol (t1, 6); + t1 = a[11]; + a[11] = t2; + t2 = u64rol (t1, 10); + t1 = a[17]; + a[17] = t2; + t2 = u64rol (t1, 15); + t1 = a[18]; + a[18] = t2; + t2 = u64rol (t1, 21); + t1 = a[3]; + a[3] = t2; + t2 = u64rol (t1, 28); + t1 = a[5]; + a[5] = t2; + t2 = u64rol (t1, 36); + t1 = a[16]; + a[16] = t2; + t2 = u64rol (t1, 45); + t1 = a[8]; + a[8] = t2; + t2 = u64rol (t1, 55); + t1 = a[21]; + a[21] = t2; + t2 = u64rol (t1, 2); + t1 = a[24]; + a[24] = t2; + t2 = u64rol (t1, 14); + t1 = a[4]; + a[4] = t2; + t2 = u64rol (t1, 27); + t1 = a[15]; + a[15] = t2; + t2 = u64rol (t1, 41); + t1 = a[23]; + a[23] = t2; + t2 = u64rol (t1, 56); + t1 = a[19]; + a[19] = t2; + t2 = u64rol (t1, 8); + t1 = a[13]; + a[13] = t2; + t2 = u64rol (t1, 25); + t1 = a[12]; + a[12] = t2; + t2 = u64rol (t1, 43); + t1 = a[2]; + a[2] = t2; + t2 = u64rol (t1, 62); + t1 = a[20]; + a[20] = t2; + t2 = u64rol (t1, 18); + t1 = a[14]; + a[14] = t2; + t2 = u64rol (t1, 39); + t1 = a[22]; + a[22] = t2; + t2 = u64rol (t1, 61); + t1 = a[9]; + a[9] = t2; + t2 = u64rol (t1, 20); + t1 = a[6]; + a[6] = t2; + t2 = u64rol (t1, 44); + t1 = a[1]; + a[1] = t2; + + /* Chi. */ + for (int j = 0; j < 25; j += 5) + { + t1 = a[j]; + t2 = a[j + 1]; + a[j] = u64xor (a[j], u64and (u64not (a[j + 1]), a[j + 2])); + a[j + 1] = u64xor (a[j + 1], u64and (u64not (a[j + 2]), + a[j + 3])); + a[j + 2] = u64xor (a[j + 2], u64and (u64not (a[j + 3]), + a[j + 4])); + a[j + 3] = u64xor (a[j + 3], u64and (u64not (a[j + 4]), t1)); + a[j + 4] = u64xor (a[j + 4], u64and (u64not (t1), t2)); + } + + /* Iota. */ + a[0] = u64xor (a[0], rc[i]); + } + } + return true; +} + +#else /* OpenSSL implementation. */ + +/* We avoid using all of EVP error strings. Just guess a reasonable errno. */ +#include + +#define DEFINE_SHA3_INIT_CTX(SIZE) \ + bool \ + sha3_##SIZE##_init_ctx (struct sha3_ctx *ctx) \ + { \ + EVP_MD_CTX *evp_ctx = EVP_MD_CTX_new (); \ + if (evp_ctx && ! EVP_DigestInit_ex (evp_ctx, EVP_sha3_##SIZE (), NULL)) \ + { \ + EVP_MD_CTX_free (evp_ctx); \ + evp_ctx = NULL; \ + } \ + ctx->evp_ctx = evp_ctx; \ + errno = ENOMEM; /* OK to set errno even if successful. */ \ + return !!evp_ctx; \ + } + +DEFINE_SHA3_INIT_CTX (224) +DEFINE_SHA3_INIT_CTX (256) +DEFINE_SHA3_INIT_CTX (384) +DEFINE_SHA3_INIT_CTX (512) + +void +sha3_free_ctx (struct sha3_ctx *ctx) +{ + if (ctx->evp_ctx != NULL) + { + int saved_errno = errno; + EVP_MD_CTX_free (ctx->evp_ctx); + ctx->evp_ctx = NULL; + errno = saved_errno; + } +} + +void * +sha3_read_ctx (struct sha3_ctx const *restrict ctx, void *restrict resbuf) +{ + void *result = NULL; + int err = ENOMEM; + EVP_MD_CTX *evp_ctx = EVP_MD_CTX_new (); + if (evp_ctx) + { + if (EVP_MD_CTX_copy_ex (evp_ctx, ctx->evp_ctx)) + { + if (EVP_DigestFinal_ex (evp_ctx, resbuf, 0)) + result = resbuf; + err = EINVAL; + } + EVP_MD_CTX_free (evp_ctx); + } + errno = err; /* OK to set errno even if successful. */ + return result; +} + +void * +sha3_finish_ctx (struct sha3_ctx *restrict ctx, void *restrict resbuf) +{ + int result = EVP_DigestFinal_ex (ctx->evp_ctx, resbuf, NULL); + if (result == 0) + { + errno = EINVAL; + return NULL; + } + return resbuf; +} + +#define DEFINE_SHA3_BUFFER(SIZE) \ + void * \ + sha3_##SIZE##_buffer (char const *restrict buffer, size_t len, \ + void *restrict resblock) \ + { \ + struct sha3_ctx ctx; \ + void *result = ((sha3_##SIZE##_init_ctx (&ctx) \ + && sha3_process_bytes (buffer, len, &ctx)) \ + ? sha3_finish_ctx (&ctx, resblock) \ + : NULL); \ + sha3_free_ctx (&ctx); \ + return result; \ + } + +DEFINE_SHA3_BUFFER (224) +DEFINE_SHA3_BUFFER (256) +DEFINE_SHA3_BUFFER (384) +DEFINE_SHA3_BUFFER (512) + +bool +sha3_process_bytes (void const *restrict buffer, size_t len, + struct sha3_ctx *restrict ctx) +{ + int result = EVP_DigestUpdate (ctx->evp_ctx, buffer, len); + if (result == 0) + { + errno = EINVAL; + return false; + } + return true; +} + +bool +sha3_process_block (void const *restrict buffer, size_t len, + struct sha3_ctx *restrict ctx) +{ + return sha3_process_bytes (buffer, len, ctx); +} + +#endif diff --git a/lib/sha3.h b/lib/sha3.h new file mode 100644 index 00000000000..a44922a0843 --- /dev/null +++ b/lib/sha3.h @@ -0,0 +1,132 @@ +/* sha3.h - Functions to calculate SHA-3 hashes as specified by FIPS-202. + Copyright (C) 2025-2026 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Collin Funk , 2025. */ + +#ifndef SHA3_H +# define SHA3_H 1 + +# include +# include +# include + +# include "u64.h" + +# ifdef __cplusplus +extern "C" { +# endif + +/* OpenSSL does not have the Init, Update, Final API for SHA-3. We must use + the EVP API. */ +# if HAVE_OPENSSL_SHA3 +# include +# endif + +/* Digest sizes in bytes. */ +enum { SHA3_224_DIGEST_SIZE = 224 / 8 }; +enum { SHA3_256_DIGEST_SIZE = 256 / 8 }; +enum { SHA3_384_DIGEST_SIZE = 384 / 8 }; +enum { SHA3_512_DIGEST_SIZE = 512 / 8 }; + +/* Block sizes in bytes. */ +enum { SHA3_224_BLOCK_SIZE = 1152 / 8 }; +enum { SHA3_256_BLOCK_SIZE = 1088 / 8 }; +enum { SHA3_384_BLOCK_SIZE = 832 / 8 }; +enum { SHA3_512_BLOCK_SIZE = 576 / 8 }; + +/* Structure to save state of computation between the single steps. */ +struct sha3_ctx +{ +# if HAVE_OPENSSL_SHA3 + /* EVP_MD_CTX is an incomplete type. It cannot be placed on the stack. */ + EVP_MD_CTX *evp_ctx; +# else + u64 state[25]; + uint8_t buffer[144]; /* Up to BLOCKLEN in use. */ + size_t buflen; /* ≥ 0, ≤ BLOCKLEN */ + size_t digestlen; /* One of SHA3_{224,256,384,512}_DIGEST_SIZE. */ + size_t blocklen; /* One of SHA3_{224,256,384,512}_BLOCK_SIZE. */ +# endif +}; + +/* Initialize structure containing state of computation. */ +extern bool sha3_224_init_ctx (struct sha3_ctx *ctx); +extern bool sha3_256_init_ctx (struct sha3_ctx *ctx); +extern bool sha3_384_init_ctx (struct sha3_ctx *ctx); +extern bool sha3_512_init_ctx (struct sha3_ctx *ctx); + +/* Free memory allocated by the init_structure. */ +extern void sha3_free_ctx (struct sha3_ctx *ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of the BLOCKLEN member of CTX!!! + Return false if an OpenSSL function fails. */ +extern bool sha3_process_block (void const *restrict buffer, size_t len, + struct sha3_ctx *restrict ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of the BLOCKLEN member of CTX. + Return false if an OpenSSL function fails. */ +extern bool sha3_process_bytes (void const *restrict buffer, size_t len, + struct sha3_ctx *restrict ctx); + +/* Process the remaining bytes in the buffer and put result from CTX in RESBUF. + The result is always in little endian byte order, so that a byte-wise output + yields to the wanted ASCII representation of the message digest. + Return NULL if an OpenSSL function fails. */ +extern void *sha3_finish_ctx (struct sha3_ctx *restrict ctx, + void *restrict resbuf); + +/* Put result from CTX in RESBUF. The result is always in little endian byte + order, so that a byte-wise output yields to the wanted ASCII representation + of the message digest. + Return NULL if an OpenSSL function fails. */ +extern void *sha3_read_ctx (struct sha3_ctx const *restrict ctx, + void *restrict resbuf); + +/* Compute a SHA-3 message digest for LEN bytes beginning at BUFFER. + The result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. + Return NULL if an OpenSSL function fails. */ +extern void *sha3_224_buffer (char const *restrict buffer, size_t len, + void *restrict resblock); +extern void *sha3_256_buffer (char const *restrict buffer, size_t len, + void *restrict resblock); +extern void *sha3_384_buffer (char const *restrict buffer, size_t len, + void *restrict resblock); +extern void *sha3_512_buffer (char const *restrict buffer, size_t len, + void *restrict resblock); + +/* Compute SHA-3 message digest for bytes read from STREAM. STREAM is an open + file stream. Regular files are handled more efficiently. The contents of + STREAM from its current position to its end will be read. The case that the + last operation on STREAM was an 'ungetc' is not supported. The resulting + message digest number will be written into RESBLOCK. */ +extern int sha3_224_stream (FILE *restrict stream, void *restrict resblock); +extern int sha3_256_stream (FILE *restrict stream, void *restrict resblock); +extern int sha3_384_stream (FILE *restrict stream, void *restrict resblock); +extern int sha3_512_stream (FILE *restrict stream, void *restrict resblock); + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4 index 5e5906949de..984f657e2ed 100644 --- a/m4/gnulib-comp.m4 +++ b/m4/gnulib-comp.m4 @@ -70,6 +70,7 @@ AC_DEFUN([gl_EARLY], # Code from module crypto/md5-buffer: # Code from module crypto/sha1-buffer: # Code from module crypto/sha256-buffer: + # Code from module crypto/sha3-buffer: # Code from module crypto/sha512-buffer: # Code from module d-type: # Code from module diffseq: @@ -286,6 +287,8 @@ AC_DEFUN([gl_INIT], AC_REQUIRE([AC_C_RESTRICT]) gl_SHA256 AC_REQUIRE([AC_C_RESTRICT]) + gl_SHA3 + AC_REQUIRE([AC_C_RESTRICT]) gl_SHA512 gl_CHECK_TYPE_STRUCT_DIRENT_D_TYPE gl_DIRENT_H @@ -1445,6 +1448,8 @@ AC_DEFUN([gl_FILE_LIST], [ lib/sha1.h lib/sha256.c lib/sha256.h + lib/sha3.c + lib/sha3.h lib/sha512.c lib/sha512.h lib/sig2str.c @@ -1609,6 +1614,7 @@ AC_DEFUN([gl_FILE_LIST], [ m4/selinux-selinux-h.m4 m4/sha1.m4 m4/sha256.m4 + m4/sha3.m4 m4/sha512.m4 m4/sig2str.m4 m4/sigdescr_np.m4 diff --git a/m4/sha3.m4 b/m4/sha3.m4 new file mode 100644 index 00000000000..4b282bcda44 --- /dev/null +++ b/m4/sha3.m4 @@ -0,0 +1,16 @@ +# sha3.m4 +# serial 1 +dnl Copyright (C) 2025-2026 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. +dnl This file is offered as-is, without any warranty. + +AC_DEFUN([gl_SHA3], +[ + dnl Prerequisites of lib/sha3.c. + AC_REQUIRE([gl_BIGENDIAN]) + + dnl Determine HAVE_OPENSSL_SHA3 and LIB_CRYPTO + gl_CRYPTO_CHECK([SHA3]) +]) diff --git a/src/fns.c b/src/fns.c index 71f3f01a91d..cf346182fd8 100644 --- a/src/fns.c +++ b/src/fns.c @@ -6014,13 +6014,14 @@ DEFUN ("internal--hash-table-index-size", /************************************************************************ - MD5, SHA-1, and SHA-2 + MD5, SHA-1, SHA-2, and SHA-3 ************************************************************************/ #include "md5.h" #include "sha1.h" #include "sha256.h" #include "sha512.h" +#include "sha3.h" /* Store into HEXBUF an unterminated hexadecimal character string representing DIGEST, which is binary data of size DIGEST_SIZE bytes. @@ -6051,7 +6052,8 @@ DEFUN ("secure-hash-algorithms", Fsecure_hash_algorithms, doc: /* Return a list of all the supported `secure-hash' algorithms. */) (void) { - return list (Qmd5, Qsha1, Qsha224, Qsha256, Qsha384, Qsha512); + return list (Qmd5, Qsha1, Qsha224, Qsha256, Qsha384, Qsha512, + Qsha3_224, Qsha3_256, Qsha3_384, Qsha3_512); } /* Extract data from a string or a buffer. SPEC is a list of @@ -6290,6 +6292,26 @@ secure_hash (Lisp_Object algorithm, Lisp_Object object, Lisp_Object start, digest_size = SHA512_DIGEST_SIZE; hash_func = sha512_buffer; } + else if (EQ (algorithm, Qsha3_224)) + { + digest_size = SHA3_224_DIGEST_SIZE; + hash_func = sha3_224_buffer; + } + else if (EQ (algorithm, Qsha3_256)) + { + digest_size = SHA3_256_DIGEST_SIZE; + hash_func = sha3_256_buffer; + } + else if (EQ (algorithm, Qsha3_384)) + { + digest_size = SHA3_384_DIGEST_SIZE; + hash_func = sha3_384_buffer; + } + else if (EQ (algorithm, Qsha3_512)) + { + digest_size = SHA3_512_DIGEST_SIZE; + hash_func = sha3_512_buffer; + } else error ("Invalid algorithm arg: %s", SDATA (Fsymbol_name (algorithm))); @@ -6351,12 +6373,16 @@ anything security-related. See `secure-hash' for alternatives. */) DEFUN ("secure-hash", Fsecure_hash, Ssecure_hash, 2, 5, 0, doc: /* Return the secure hash of OBJECT, a buffer or string. ALGORITHM is a symbol specifying the hash to use: -- md5 corresponds to MD5, produces a 32-character signature -- sha1 corresponds to SHA-1, produces a 40-character signature -- sha224 corresponds to SHA-2 (SHA-224), produces a 56-character signature -- sha256 corresponds to SHA-2 (SHA-256), produces a 64-character signature -- sha384 corresponds to SHA-2 (SHA-384), produces a 96-character signature -- sha512 corresponds to SHA-2 (SHA-512), produces a 128-character signature +- md5 corresponds to MD5, produces a 32-character signature +- sha1 corresponds to SHA-1, produces a 40-character signature +- sha224 corresponds to SHA-2 (SHA-224), produces a 56-character signature +- sha256 corresponds to SHA-2 (SHA-256), produces a 64-character signature +- sha384 corresponds to SHA-2 (SHA-384), produces a 96-character signature +- sha512 corresponds to SHA-2 (SHA-512), produces a 128-character signature +- sha3-224 corresponds to SHA-3 (SHA3-224), produces a 56-character signature +- sha3-256 corresponds to SHA-3 (SHA3-256), produces a 64-character signature +- sha3-384 corresponds to SHA-3 (SHA3-384), produces a 96-character signature +- sha3-512 corresponds to SHA-3 (SHA3-512), produces a 128-character signature The two optional arguments START and END are positions specifying for which part of OBJECT to compute the hash. If nil or omitted, uses the @@ -6718,12 +6744,16 @@ syms_of_fns (void) /* Crypto and hashing stuff. */ DEFSYM (Qiv_auto, "iv-auto"); - DEFSYM (Qmd5, "md5"); - DEFSYM (Qsha1, "sha1"); - DEFSYM (Qsha224, "sha224"); - DEFSYM (Qsha256, "sha256"); - DEFSYM (Qsha384, "sha384"); - DEFSYM (Qsha512, "sha512"); + DEFSYM (Qmd5, "md5"); + DEFSYM (Qsha1, "sha1"); + DEFSYM (Qsha224, "sha224"); + DEFSYM (Qsha256, "sha256"); + DEFSYM (Qsha384, "sha384"); + DEFSYM (Qsha512, "sha512"); + DEFSYM (Qsha3_224, "sha3-224"); + DEFSYM (Qsha3_256, "sha3-256"); + DEFSYM (Qsha3_384, "sha3-384"); + DEFSYM (Qsha3_512, "sha3-512"); /* Miscellaneous stuff. */ diff --git a/test/lisp/net/gnutls-tests.el b/test/lisp/net/gnutls-tests.el index aac23726bf7..b2b8cb61418 100644 --- a/test/lisp/net/gnutls-tests.el +++ b/test/lisp/net/gnutls-tests.el @@ -50,7 +50,12 @@ (defvar gnutls-tests-internal-macs-upcased (mapcar (lambda (sym) (cons sym (intern (upcase (symbol-name sym))))) - (secure-hash-algorithms))) + ;; Remove the SHA-3 algorithms. Currently, the GNUTLS_MAC_SHA3_* + ;; macros are reserved, but unimplemented. See: + ;; . + (seq-remove (lambda (sym) + (string-prefix-p "sha3-" (symbol-name sym))) + (secure-hash-algorithms)))) (defvar gnutls-tests-tested-macs (when (gnutls-available-p) diff --git a/test/src/fns-tests.el b/test/src/fns-tests.el index fa00cec6118..2d7d410318b 100644 --- a/test/src/fns-tests.el +++ b/test/src/fns-tests.el @@ -1402,6 +1402,18 @@ (concat "0a50261ebd1a390fed2bf326f2673c145582a6342d5" "23204973d0219337f81616a8069b012587cf5635f69" "25f1b56c360230c19b273500ee013e030601bf2425"))) + (should (equal (secure-hash 'sha3-224 "foobar") + "1ad852ba147a715fe5a3df39a741fad08186c303c7d21cefb7be763b")) + (should (equal (secure-hash 'sha3-256 "foobar") + (concat "09234807e4af85f17c66b48ee3bca89d" + "ffd1f1233659f9f940a2b17b0b8c6bc5"))) + (should (equal (secure-hash 'sha3-384 "foobar") + (concat "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac" + "8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"))) + (should (equal (secure-hash 'sha3-512 "foobar") + (concat "ff32a30c3af5012ea395827a3e99a13073c3a8d8410" + "a708568ff7e6eb85968fccfebaea039bc21411e9d43" + "fdb9a851b529b9960ffea8679199781b8f45ca85e2"))) ;; Test that a call to getrandom returns the right format. ;; This does not test randomness; it's merely a format check. (should (string-match "\\`[0-9a-f]\\{128\\}\\'"