[git commit] libbb: factor out HMAC code from TLS

Denys Vlasenko vda.linux at googlemail.com
Mon Jul 7 05:44:01 UTC 2025


commit: https://git.busybox.net/busybox/commit/?id=1a0913d57ce8287703cfe666d9240e3a147ea30d
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

function                                             old     new   delta
hmac_block                                             -      88     +88
hmac_peek_hash                                         -      61     +61
hmac_end                                               -      50     +50
hmac_begin                                           140     177     +37
hmac_hash_v                                            -      30     +30
.rodata                                           105799  105787     -12
hmac_sha_precomputed                                  54       -     -54
hmac_sha_precomputed_v                                69       -     -69
hmac                                                  83       -     -83
------------------------------------------------------------------------------
(add/remove: 5/3 grow/shrink: 1/1 up/down: 266/-218)           Total: 48 bytes

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 include/libbb.h   |  40 +++++++++++---
 libbb/hash_hmac.c |  99 +++++++++++++++++++++++++++++++++
 networking/tls.c  | 161 ++++++++++--------------------------------------------
 3 files changed, 161 insertions(+), 139 deletions(-)

diff --git a/include/libbb.h b/include/libbb.h
index b761b1091..3f60acaa0 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -2193,6 +2193,16 @@ int FAST_FUNC i2a64(int i);
 int FAST_FUNC a2i64(char c);
 char* FAST_FUNC num2str64_lsb_first(char *s, unsigned v, int n);
 
+enum {
+	/* how many bytes XYZ_end() fills */
+	MD5_OUTSIZE    = 16,
+	SHA1_OUTSIZE   = 20,
+	SHA256_OUTSIZE = 32,
+	SHA512_OUTSIZE = 64,
+	SHA3_OUTSIZE   = 28,
+	/* size of input block */
+	SHA2_INSIZE     = 64,
+};
 typedef struct md5_ctx_t {
 	uint8_t wbuffer[64]; /* always correctly aligned for uint64_t */
 	void (*process_block)(struct md5_ctx_t*) FAST_FUNC;
@@ -2226,18 +2236,32 @@ unsigned sha512_end(sha512_ctx_t *ctx, void *resbuf) FAST_FUNC;
 void sha3_begin(sha3_ctx_t *ctx) FAST_FUNC;
 void sha3_hash(sha3_ctx_t *ctx, const void *buffer, size_t len) FAST_FUNC;
 unsigned sha3_end(sha3_ctx_t *ctx, void *resbuf) FAST_FUNC;
+void FAST_FUNC sha256_block(const void *in, size_t len, uint8_t hash[32]);
 /* TLS benefits from knowing that sha1 and sha256 share these. Give them "agnostic" names too */
 typedef struct md5_ctx_t md5sha_ctx_t;
 #define md5sha_hash md5_hash
 #define sha_end sha1_end
-enum {
-	MD5_OUTSIZE    = 16,
-	SHA1_OUTSIZE   = 20,
-	SHA256_OUTSIZE = 32,
-	SHA512_OUTSIZE = 64,
-	SHA3_OUTSIZE   = 28,
-};
-void FAST_FUNC sha256_block(const void *in, size_t len, uint8_t hash[32]);
+
+/* RFC 2104 HMAC (hash-based message authentication code) */
+typedef struct hmac_ctx {
+	md5sha_ctx_t hashed_key_xor_ipad;
+	md5sha_ctx_t hashed_key_xor_opad;
+} hmac_ctx_t;
+#define HMAC_ONLY_SHA256 (!ENABLE_FEATURE_TLS_SHA1)
+typedef void md5sha_begin_func(md5sha_ctx_t *ctx) FAST_FUNC;
+#if HMAC_ONLY_SHA256
+#define hmac_begin(ctx,key,key_size,begin) \
+	hmac_begin(ctx,key,key_size)
+#endif
+void FAST_FUNC hmac_begin(hmac_ctx_t *ctx, uint8_t *key, unsigned key_size, md5sha_begin_func *begin);
+static ALWAYS_INLINE void hmac_hash(hmac_ctx_t *ctx, const void *in, size_t len)
+{
+	md5sha_hash(&ctx->hashed_key_xor_ipad, in, len);
+}
+unsigned FAST_FUNC hmac_end(hmac_ctx_t *ctx, uint8_t *out);
+/* HMAC helpers for TLS: */
+void FAST_FUNC hmac_hash_v(hmac_ctx_t *ctx, va_list va);
+unsigned FAST_FUNC hmac_peek_hash(hmac_ctx_t *ctx, uint8_t *out, ...);
 
 extern uint32_t *global_crc32_table;
 uint32_t *crc32_filltable(uint32_t *tbl256, int endian) FAST_FUNC;
diff --git a/libbb/hash_hmac.c b/libbb/hash_hmac.c
new file mode 100644
index 000000000..8cf936949
--- /dev/null
+++ b/libbb/hash_hmac.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+//kbuild:lib-$(CONFIG_TLS) += hash_hmac.o
+//kbuild:lib-$(CONFIG_USE_BB_CRYPT_YES) += hash_hmac.o
+
+#include "libbb.h"
+
+// RFC 2104:
+// HMAC(key, text) based on a hash H (say, sha256) is:
+// ipad = [0x36 x INSIZE]
+// opad = [0x5c x INSIZE]
+// HMAC(key, text) = H((key XOR opad) + H((key XOR ipad) + text))
+//
+// H(key XOR opad) and H(key XOR ipad) can be precomputed
+// if we often need HMAC hmac with the same key.
+//
+// text is often given in disjoint pieces.
+void FAST_FUNC hmac_begin(hmac_ctx_t *ctx, uint8_t *key, unsigned key_size, md5sha_begin_func *begin)
+{
+#if HMAC_ONLY_SHA256
+#define begin sha256_begin
+#endif
+	uint8_t key_xor_ipad[SHA2_INSIZE];
+	uint8_t key_xor_opad[SHA2_INSIZE];
+	unsigned i;
+
+	// "The authentication key can be of any length up to INSIZE, the
+	// block length of the hash function.  Applications that use keys longer
+	// than INSIZE bytes will first hash the key using H and then use the
+	// resultant OUTSIZE byte string as the actual key to HMAC."
+	if (key_size > SHA2_INSIZE) {
+		uint8_t tempkey[SHA1_OUTSIZE < SHA256_OUTSIZE ? SHA256_OUTSIZE : SHA1_OUTSIZE];
+		/* use ctx->hashed_key_xor_ipad as scratch ctx */
+		begin(&ctx->hashed_key_xor_ipad);
+		md5sha_hash(&ctx->hashed_key_xor_ipad, key, key_size);
+		key_size = sha_end(&ctx->hashed_key_xor_ipad, tempkey);
+		key = tempkey;
+	}
+
+	for (i = 0; i < key_size; i++) {
+		key_xor_ipad[i] = key[i] ^ 0x36;
+		key_xor_opad[i] = key[i] ^ 0x5c;
+	}
+	for (; i < SHA2_INSIZE; i++) {
+		key_xor_ipad[i] = 0x36;
+		key_xor_opad[i] = 0x5c;
+	}
+
+	begin(&ctx->hashed_key_xor_ipad);
+	begin(&ctx->hashed_key_xor_opad);
+	md5sha_hash(&ctx->hashed_key_xor_ipad, key_xor_ipad, SHA2_INSIZE);
+	md5sha_hash(&ctx->hashed_key_xor_opad, key_xor_opad, SHA2_INSIZE);
+}
+#undef begin
+
+unsigned FAST_FUNC hmac_end(hmac_ctx_t *ctx, uint8_t *out)
+{
+	unsigned len = sha_end(&ctx->hashed_key_xor_ipad, out);
+	/* out = H((key XOR opad) + out) */
+	md5sha_hash(&ctx->hashed_key_xor_opad, out, len);
+	return sha_end(&ctx->hashed_key_xor_opad, out);
+}
+
+/* TLS helpers */
+
+void FAST_FUNC hmac_hash_v(
+		hmac_ctx_t *ctx,
+		va_list va)
+{
+	uint8_t *in;
+
+	/* ctx->hashed_key_xor_ipad contains unclosed "H((key XOR ipad) +" state */
+	/* ctx->hashed_key_xor_opad contains unclosed "H((key XOR opad) +" state */
+
+	/* calculate out = H((key XOR ipad) + text) */
+	while ((in = va_arg(va, uint8_t*)) != NULL) {
+		unsigned size = va_arg(va, unsigned);
+		md5sha_hash(&ctx->hashed_key_xor_ipad, in, size);
+	}
+}
+
+/* Using HMAC state, make a copy of it (IOW: without affecting this state!)
+ * hash in the list of (ptr,size) blocks, and finish the HMAC to out[] buffer.
+ * This function is useful for TLS PRF.
+ */
+unsigned FAST_FUNC hmac_peek_hash(hmac_ctx_t *ctx, uint8_t *out, ...)
+{
+	hmac_ctx_t tmpctx = *ctx; /* struct copy */
+	va_list va;
+
+	va_start(va, out);
+	hmac_hash_v(&tmpctx, va);
+	va_end(va);
+
+	return hmac_end(&tmpctx, out);
+}
diff --git a/networking/tls.c b/networking/tls.c
index 8d074c058..b8caf1e4b 100644
--- a/networking/tls.c
+++ b/networking/tls.c
@@ -188,8 +188,6 @@
 #define TLS_MAX_OUTBUF          (1 << 14)
 
 enum {
-	SHA_INSIZE     = 64,
-
 	AES128_KEYSIZE = 16,
 	AES256_KEYSIZE = 32,
 
@@ -393,128 +391,6 @@ static void hash_handshake(tls_state_t *tls, const char *fmt, const void *buffer
 # define TLS_MAC_SIZE(tls) (tls)->MAC_size
 #endif
 
-// RFC 2104:
-// HMAC(key, text) based on a hash H (say, sha256) is:
-// ipad = [0x36 x INSIZE]
-// opad = [0x5c x INSIZE]
-// HMAC(key, text) = H((key XOR opad) + H((key XOR ipad) + text))
-//
-// H(key XOR opad) and H(key XOR ipad) can be precomputed
-// if we often need HMAC hmac with the same key.
-//
-// text is often given in disjoint pieces.
-typedef struct hmac_precomputed {
-	md5sha_ctx_t hashed_key_xor_ipad;
-	md5sha_ctx_t hashed_key_xor_opad;
-} hmac_precomputed_t;
-
-typedef void md5sha_begin_func(md5sha_ctx_t *ctx) FAST_FUNC;
-#if !ENABLE_FEATURE_TLS_SHA1
-#define hmac_begin(pre,key,key_size,begin) \
-	hmac_begin(pre,key,key_size)
-#define begin sha256_begin
-#endif
-static void hmac_begin(hmac_precomputed_t *pre, uint8_t *key, unsigned key_size, md5sha_begin_func *begin)
-{
-	uint8_t key_xor_ipad[SHA_INSIZE];
-	uint8_t key_xor_opad[SHA_INSIZE];
-//	uint8_t tempkey[SHA1_OUTSIZE < SHA256_OUTSIZE ? SHA256_OUTSIZE : SHA1_OUTSIZE];
-	unsigned i;
-
-	// "The authentication key can be of any length up to INSIZE, the
-	// block length of the hash function.  Applications that use keys longer
-	// than INSIZE bytes will first hash the key using H and then use the
-	// resultant OUTSIZE byte string as the actual key to HMAC."
-	if (key_size > SHA_INSIZE) {
-		bb_simple_error_msg_and_die("HMAC key>64"); //does not happen (yet?)
-//		md5sha_ctx_t ctx;
-//		begin(&ctx);
-//		md5sha_hash(&ctx, key, key_size);
-//		key_size = sha_end(&ctx, tempkey);
-//		//key = tempkey; - right? RIGHT? why does it work without this?
-//		// because SHA_INSIZE is 64, but hmac() is always called with
-//		// key_size = tls->MAC_size = SHA1/256_OUTSIZE (20 or 32),
-//		// and prf_hmac_sha256() -> hmac_sha256() key sizes are:
-//		// - RSA_PREMASTER_SIZE is 48
-//		// - CURVE25519_KEYSIZE is 32
-//		// - master_secret[] is 48
-	}
-
-	for (i = 0; i < key_size; i++) {
-		key_xor_ipad[i] = key[i] ^ 0x36;
-		key_xor_opad[i] = key[i] ^ 0x5c;
-	}
-	for (; i < SHA_INSIZE; i++) {
-		key_xor_ipad[i] = 0x36;
-		key_xor_opad[i] = 0x5c;
-	}
-
-	begin(&pre->hashed_key_xor_ipad);
-	begin(&pre->hashed_key_xor_opad);
-	md5sha_hash(&pre->hashed_key_xor_ipad, key_xor_ipad, SHA_INSIZE);
-	md5sha_hash(&pre->hashed_key_xor_opad, key_xor_opad, SHA_INSIZE);
-}
-#undef begin
-
-static unsigned hmac_sha_precomputed_v(
-		hmac_precomputed_t *pre,
-		uint8_t *out,
-		va_list va)
-{
-	uint8_t *text;
-	unsigned len;
-
-	/* pre->hashed_key_xor_ipad contains unclosed "H((key XOR ipad) +" state */
-	/* pre->hashed_key_xor_opad contains unclosed "H((key XOR opad) +" state */
-
-	/* calculate out = H((key XOR ipad) + text) */
-	while ((text = va_arg(va, uint8_t*)) != NULL) {
-		unsigned text_size = va_arg(va, unsigned);
-		md5sha_hash(&pre->hashed_key_xor_ipad, text, text_size);
-	}
-	len = sha_end(&pre->hashed_key_xor_ipad, out);
-
-	/* out = H((key XOR opad) + out) */
-	md5sha_hash(&pre->hashed_key_xor_opad, out, len);
-	return sha_end(&pre->hashed_key_xor_opad, out);
-}
-
-static unsigned hmac_sha_precomputed(hmac_precomputed_t *pre_init, uint8_t *out, ...)
-{
-	hmac_precomputed_t pre;
-	va_list va;
-	unsigned len;
-
-	va_start(va, out);
-	pre = *pre_init; /* struct copy */
-	len = hmac_sha_precomputed_v(&pre, out, va);
-	va_end(va);
-	return len;
-}
-
-#if !ENABLE_FEATURE_TLS_SHA1
-#define hmac(tls,out,key,key_size,...) \
-	hmac(out,key,key_size, __VA_ARGS__)
-#endif
-static unsigned hmac(tls_state_t *tls, uint8_t *out, uint8_t *key, unsigned key_size, ...)
-{
-	hmac_precomputed_t pre;
-	va_list va;
-	unsigned len;
-
-	va_start(va, key_size);
-
-	hmac_begin(&pre, key, key_size,
-			(ENABLE_FEATURE_TLS_SHA1 && tls->MAC_size == SHA1_OUTSIZE)
-				? sha1_begin
-				: sha256_begin
-	);
-	len = hmac_sha_precomputed_v(&pre, out, va);
-
-	va_end(va);
-	return len;
-}
-
 // RFC 5246:
 // 5.  HMAC and the Pseudorandom Function
 //...
@@ -559,7 +435,7 @@ static void prf_hmac_sha256(/*tls_state_t *tls,*/
 		const char *label,
 		uint8_t *seed, unsigned seed_size)
 {
-	hmac_precomputed_t pre;
+	hmac_ctx_t ctx;
 	uint8_t a[TLS_MAX_MAC_SIZE];
 	uint8_t *out_p = outbuf;
 	unsigned label_size = strlen(label);
@@ -569,26 +445,26 @@ static void prf_hmac_sha256(/*tls_state_t *tls,*/
 #define SEED   label, label_size, seed, seed_size
 #define A      a, MAC_size
 
-	hmac_begin(&pre, secret, secret_size, sha256_begin);
+	hmac_begin(&ctx, secret, secret_size, sha256_begin);
 
 	/* A(1) = HMAC_hash(secret, seed) */
-	hmac_sha_precomputed(&pre, a, SEED, NULL);
+	hmac_peek_hash(&ctx, a, SEED, NULL);
 
 	for (;;) {
 		/* HMAC_hash(secret, A(1) + seed) */
 		if (outbuf_size <= MAC_size) {
 			/* Last, possibly incomplete, block */
 			/* (use a[] as temp buffer) */
-			hmac_sha_precomputed(&pre, a, A, SEED, NULL);
+			hmac_peek_hash(&ctx, a, A, SEED, NULL);
 			memcpy(out_p, a, outbuf_size);
 			return;
 		}
 		/* Not last block. Store directly to result buffer */
-		hmac_sha_precomputed(&pre, out_p, A, SEED, NULL);
+		hmac_peek_hash(&ctx, out_p, A, SEED, NULL);
 		out_p += MAC_size;
 		outbuf_size -= MAC_size;
 		/* A(2) = HMAC_hash(secret, A(1)) */
-		hmac_sha_precomputed(&pre, a, A, NULL);
+		hmac_peek_hash(&ctx, a, A, NULL);
 	}
 #undef A
 #undef SECRET
@@ -655,6 +531,29 @@ static void *tls_get_zeroed_outbuf(tls_state_t *tls, int len)
 	return record;
 }
 
+/* Calculate the HMAC over the list of blocks */
+#if !ENABLE_FEATURE_TLS_SHA1
+#define hmac_block(tls,out,key,key_size,...) \
+	hmac_block(out,key,key_size, __VA_ARGS__)
+#endif
+static unsigned hmac_block(tls_state_t *tls, uint8_t *out, uint8_t *key, unsigned key_size, ...)
+{
+	hmac_ctx_t ctx;
+	va_list va;
+
+	hmac_begin(&ctx, key, key_size,
+			(ENABLE_FEATURE_TLS_SHA1 && tls->MAC_size == SHA1_OUTSIZE)
+				? sha1_begin
+				: sha256_begin
+	);
+
+	va_start(va, key_size);
+	hmac_hash_v(&ctx, va);
+	va_end(va);
+
+	return hmac_end(&ctx, out);
+}
+
 static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, unsigned type)
 {
 	uint8_t *buf = tls->outbuf + OUTBUF_PFX;
@@ -676,7 +575,7 @@ static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, un
 	xhdr->len16_lo = size & 0xff;
 
 	/* Calculate MAC signature */
-	hmac(tls, buf + size, /* result */
+	hmac_block(tls, buf + size, /* result */
 		tls->client_write_MAC_key, TLS_MAC_SIZE(tls),
 		&tls->write_seq64_be, sizeof(tls->write_seq64_be),
 		xhdr, RECHDR_LEN,


More information about the busybox-cvs mailing list