commit 913cef345e46ea44aee5e08bb35d9d242ba98356
parent 12a3b1a4b12f4149dffb2660fa01515135981c03
Author: MikoĊaj Lenczewski <mikolaj@lenczewski.org>
Date: Sun, 20 Sep 2026 23:19:55 +0100
Add hkdf implementation
Diffstat:
| A | hkdf.c | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | hkdf.h | | | 23 | +++++++++++++++++++++++ |
| M | hmac.c | | | 32 | ++++++++++++++++++++++++-------- |
| M | hmac.h | | | 17 | +++++++++++------ |
| A | test/test-hkdf.c | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 192 insertions(+), 14 deletions(-)
diff --git a/hkdf.c b/hkdf.c
@@ -0,0 +1,62 @@
+#include "hkdf.h"
+
+#include <stdio.h>
+
+size_t
+hkdf_digest_size(struct hmac_hash const *hash, size_t rounds)
+{
+ return hash->digest_size * rounds;
+}
+
+size_t
+hkdf_max_digest_size(struct hmac_hash const *hash)
+{
+ return hkdf_digest_size(hash, HKDF_MAX_ROUNDS);
+}
+
+void
+hkdf(struct hmac_hash const *hash, void *restrict hash_ctx,
+ void const *restrict key, size_t key_len,
+ void const *salt, size_t salt_len,
+ void const *info, size_t info_len,
+ void *restrict digest, size_t rounds)
+{
+ // we must allow at least 1 round of hmac
+ assert(rounds);
+
+ // bound output key material length
+ if (HKDF_MAX_ROUNDS < rounds)
+ rounds = HKDF_MAX_ROUNDS;
+
+ struct hmac_ctx hmac;
+
+ // generate pseudorandom key
+ hmac_gen_iv_block(&hmac, hash, hash_ctx, salt, salt_len);
+ hmac_init(&hmac);
+ hmac_push(&hmac, key, key_len);
+ hmac_dgst(&hmac, digest);
+
+ // expand prk into outpt key material
+ hmac_gen_iv_block(&hmac, hash, hash_ctx, digest, hash->digest_size);
+
+ unsigned char *base = digest;
+ size_t block_len = hash->digest_size;
+
+ for (size_t i = 0; i < rounds; i++) {
+ unsigned char *dst = base + (i * block_len);
+ unsigned char *src = dst - block_len;
+
+ // hkdf expects ctr to start from 01
+ unsigned char ctr = (i + 1) & 0xff;
+
+ // merge zero previous material on round 1
+ size_t merged_len = i ? block_len : 0;
+
+ hmac_init(&hmac);
+ hmac_push(&hmac, src, merged_len);
+ hmac_push(&hmac, info, info_len);
+ hmac_push(&hmac, &ctr, 1);
+
+ hmac_dgst(&hmac, dst);
+ }
+}
diff --git a/hkdf.h b/hkdf.h
@@ -0,0 +1,23 @@
+// hkdf: https://www.rfc-editor.org/info/rfc5869/
+
+#ifndef HKDF_H
+#define HKDF_H
+
+#include "hmac.h"
+
+#define HKDF_MAX_ROUNDS 255
+
+size_t
+hkdf_digest_size(struct hmac_hash const *hash, size_t rounds);
+
+size_t
+hkdf_max_digest_size(struct hmac_hash const *hash);
+
+void
+hkdf(struct hmac_hash const *hash, void *restrict hash_ctx,
+ void const *restrict key, size_t key_len,
+ void const *salt, size_t salt_len,
+ void const *info, size_t info_len,
+ void *restrict digest, size_t rounds);
+
+#endif /* HKDF_H */
diff --git a/hmac.c b/hmac.c
@@ -1,9 +1,9 @@
#include "hmac.h"
void
-hmac_init(struct hmac_ctx *ctx,
- struct hmac_hash const *hash, void *restrict hash_ctx,
- void const *restrict key, size_t key_len)
+hmac_gen_iv_block(struct hmac_ctx *ctx,
+ struct hmac_hash const *hash, void *restrict hash_ctx,
+ void const *restrict key, size_t key_len)
{
memset(ctx, 0, sizeof *ctx);
@@ -27,15 +27,27 @@ hmac_init(struct hmac_ctx *ctx,
}
void
-hmac_hash(struct hmac_ctx *ctx,
- void const *restrict buf, size_t buf_len,
- void *restrict digest)
+hmac_init(struct hmac_ctx *ctx)
{
+ // prepare ipad material
ctx->hash->init(ctx->hash_ctx);
ctx->hash->push(ctx->hash_ctx, ctx->ipad, sizeof ctx->ipad);
+}
+
+void
+hmac_push(struct hmac_ctx *ctx, void const *buf, size_t buf_len)
+{
+ // accumulate ipad material
ctx->hash->push(ctx->hash_ctx, buf, buf_len);
+}
+
+void
+hmac_dgst(struct hmac_ctx *ctx, void *digest)
+{
+ // close ipad material
ctx->hash->dgst(ctx->hash_ctx, digest);
+ // final digest against opad
ctx->hash->init(ctx->hash_ctx);
ctx->hash->push(ctx->hash_ctx, ctx->opad, sizeof ctx->opad);
ctx->hash->push(ctx->hash_ctx, digest, ctx->hash->digest_size);
@@ -49,6 +61,10 @@ hmac(struct hmac_hash const *hash, void *restrict hash_ctx,
void *restrict digest)
{
struct hmac_ctx ctx;
- hmac_init(&ctx, hash, hash_ctx, key, key_len);
- hmac_hash(&ctx, buf, buf_len, digest);
+
+ hmac_gen_iv_block(&ctx, hash, hash_ctx, key, key_len);
+
+ hmac_init(&ctx);
+ hmac_push(&ctx, buf, buf_len);
+ hmac_dgst(&ctx, digest);
}
diff --git a/hmac.h b/hmac.h
@@ -28,14 +28,19 @@ struct hmac_ctx {
};
void
-hmac_init(struct hmac_ctx *ctx,
- struct hmac_hash const *hash, void *restrict hash_ctx,
- void const *restrict key, size_t key_len);
+hmac_gen_iv_block(struct hmac_ctx *ctx,
+ struct hmac_hash const *hash, void *restrict hash_ctx,
+ void const *restrict key, size_t key_len);
void
-hmac_hash(struct hmac_ctx *ctx,
- void const *restrict buf, size_t buf_len,
- void *restrict digest);
+hmac_init(struct hmac_ctx *ctx);
+
+void
+hmac_push(struct hmac_ctx *ctx, void const *buf, size_t buf_len);
+
+void
+hmac_dgst(struct hmac_ctx *ctx, void *digest);
+
void
hmac(struct hmac_hash const *hash, void *restrict hash_ctx,
void const *restrict key, size_t key_len,
diff --git a/test/test-hkdf.c b/test/test-hkdf.c
@@ -0,0 +1,72 @@
+#include "hkdf.h"
+#include "hmac-sha2.h"
+
+#include <stdio.h>
+
+#define HKDF_ROUNDS 2
+#define HKDF_DIGEST_SIZE (SHA256_DIGEST_SIZE * HKDF_ROUNDS)
+
+int
+main(void)
+{
+ char key[] = "test";
+ char salt[] = "salt";
+ char info[] = "info";
+
+ struct sha256_ctx sha256_ctx;
+ uint8_t dgst[HKDF_DIGEST_SIZE];
+ hkdf(&hmac_sha256, &sha256_ctx,
+ key, strlen(key),
+ salt, strlen(salt),
+ info, strlen(info),
+ dgst, HKDF_ROUNDS);
+
+
+ // generated with:
+ // --
+ // openssl kdf
+ // -keylen 64
+ // -kdfopt digest:sha2-256
+ // -kdfopt key:test
+ // -kdfopt salt:salt
+ // -kdfopt info:info
+ // hkdf
+ //
+ uint8_t expected[HKDF_DIGEST_SIZE] = {
+ 0x8a, 0x43, 0x59, 0x8a, 0xc9, 0x49, 0x78, 0x8d,
+ 0xab, 0x1a, 0xc0, 0xae, 0x0e, 0xd4, 0x2f, 0x10,
+ 0x0e, 0xcf, 0x6a, 0x98, 0x29, 0x2d, 0xd8, 0x89,
+ 0x51, 0xf8, 0x32, 0xd5, 0xdd, 0xf7, 0x63, 0x49,
+
+ 0xb0, 0x27, 0x5a, 0xfc, 0x2d, 0x0d, 0x8f, 0xe7,
+ 0x0c, 0x34, 0x46, 0x66, 0xcb, 0x97, 0xb1, 0x3e,
+ 0x85, 0x76, 0xa1, 0xc0, 0x13, 0x6c, 0x49, 0x84,
+ 0xe6, 0xd3, 0xf6, 0xee, 0x62, 0x55, 0x4d, 0x37,
+ };
+
+ printf("key: \"%.*s\" (%lu bytes)\n",
+ sizeof key, key, strlen(key));
+ printf("salt: \"%.*s\" (%lu bytes)\n",
+ sizeof salt, salt, strlen(salt));
+ printf("info: \"%.*s\" (%lu bytes)\n",
+ sizeof info, info, strlen(info));
+
+ printf("dgst: ");
+ for (size_t i = 0; i < sizeof dgst; i++) {
+ printf("%.2x", dgst[i]);
+ }
+ printf("\n");
+
+ printf("exp.: ");
+ for (size_t i = 0; i < sizeof expected; i++) {
+ printf("%.2x", expected[i]);
+ }
+ printf("\n");
+
+ return memcmp(expected, dgst, HKDF_DIGEST_SIZE);
+}
+
+#include "hkdf.c"
+#include "hmac.c"
+#include "hmac-sha2.c"
+#include "sha.c"