first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
# hmac
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides HMAC (Hash-based Message Authentication Code) over SHA-2 variants.
HMAC proves that a message was produced by a party in possession of a shared
key, giving both data integrity and authenticity guarantees without asymmetric
cryptography. This module wraps the audited `@noble/hashes` HMAC primitive
and restricts the algorithm surface to the same `HashAlgorithm` union used
throughout `@suaveplan/crypto-hash`.
## Features
- `hmac(algo, key, data)` — one-shot HMAC-SHA-{256,384,512}
- Throws `HashError("HASH.ALGORITHM.UNSUPPORTED")` for unrecognised algorithm strings
- Passes through RFC 4231 HMAC-SHA-256 test vectors
- Deterministic: same `(algo, key, data)` triple always produces the same tag
- Zero runtime state; safe for concurrent use
## Usage: Basic Example
```ts
import { hmac } from "@suaveplan/crypto-hash/mac";
import { toHex } from "@suaveplan/crypto-hash/encoding";
const encoder = new TextEncoder();
const key = encoder.encode("super-secret-key");
const data = encoder.encode("payment:amount=100");
const tag = hmac("SHA-256", key, data);
console.log(toHex(tag)); // 32-byte hex string
```
## Usage: Advanced Example
```ts
import { hmac } from "@suaveplan/crypto-hash/mac";
import { toHex, fromHex } from "@suaveplan/crypto-hash/encoding";
// Verify a webhook signature in constant time
function verifyWebhookTag(
secret: Uint8Array,
payload: Uint8Array,
receivedTagHex: string
): boolean {
const expected = hmac("SHA-256", secret, payload);
const received = fromHex(receivedTagHex);
if (expected.length !== received.length) return false;
let diff = 0;
for (let i = 0; i < expected.length; i++) {
diff |= expected[i] ^ received[i];
}
return diff === 0;
}
const encoder = new TextEncoder();
const secret = encoder.encode("webhook-secret");
const body = encoder.encode('{"event":"charge.succeeded"}');
const tag = toHex(hmac("SHA-256", secret, body));
console.log(verifyWebhookTag(secret, body, tag)); // true
```
## API Reference
### `hmac(algo: HashAlgorithm, key: Uint8Array, data: Uint8Array): Uint8Array`
Computes the HMAC of `data` under `key` using the specified SHA-2 algorithm.
| Parameter | Type | Description |
|-----------|------|-------------|
| `algo` | `HashAlgorithm` | `"SHA-256"`, `"SHA-384"`, or `"SHA-512"` |
| `key` | `Uint8Array` | Shared secret key (any length ≥ 1 byte) |
| `data` | `Uint8Array` | Message to authenticate |
**Returns:** Tag bytes — 32 / 48 / 64 bytes for SHA-256 / SHA-384 / SHA-512
respectively.
**Throws:** `HashError("HASH.ALGORITHM.UNSUPPORTED")` if `algo` is not one of
the three supported variants.
## Implementation Notes
The internal `hasherFor` helper maps `HashAlgorithm` strings to the corresponding
`@noble/hashes` hash constructor. It is a private module function — not exported
from this file or from any barrel — so it cannot be called by consumers.
This design keeps the public API surface minimal while allowing `hkdf.ts` and
`pbkdf2.ts` to import `hasherFor` from this module directly for their own
internal use. `argon2id.ts` does not use `hasherFor` because Argon2id is a
password-hashing algorithm that accepts its own internal parameterisation rather
than a bare hash constructor.
HMAC output length equals the underlying hash output length. The `@noble/hashes`
implementation is constant-time within the hash primitive itself; callers are
responsible for constant-time comparison of the resulting tags.