Digest::HMAC
WorksKeyed message authentication (RFC 2104) over any digest you hand it — verify a webhook signature or sign an API request in one line, in pure Raku.
- Version
1.0.7zef:jjmerelo- Depends
Digest- License
- see the source
- Its own test suite
- 2 files, green
- Checked
- 2026-08-28 against Raku++ 3.20.1 and Rakudo 2026.08
Install it #
$ rakupp install Digest::HMACzef install Digest::HMAC writes the same store; either installer leaves the module usable by both engines.
What it is for #
A hash proves a message was not corrupted; an HMAC proves it was not corrupted and came from someone holding the key. That is the mechanism behind webhook signatures, signed cookies, and most API request signing. This module is the RFC 2104 construction in about twenty lines of Raku — and instead of hard-coding a hash, hmac takes the digest function as an argument, so one implementation serves them all:
use Digest::HMAC;
use Digest::SHA1;
say hmac-hex('key', 'The quick brown fox jumps over the lazy dog', &sha1);de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9That output is the published test vector for HMAC-SHA1 — you can check it against any other language's hmac library, and this page's build effectively does. The digest functions come from the Digest distribution, which is installed as a dependency: use Digest::SHA1, use Digest::SHA2 (for sha256, sha384, sha512), use Digest::MD5, and so on. Anything that maps a Blob to a Blob works, including a digest you wrote yourself.
Verifying a webhook #
The everyday use: a service POSTs you JSON and puts sha256=<hex> in a header. You recompute the tag over the raw body with the shared secret and compare:
use Digest::HMAC;
use Digest::SHA2;
my $secret = 'webhook-secret';
my $payload = '{"action":"opened","number":1}';
my $expected = 'sha256=' ~ hmac-hex($secret, $payload, &sha256);
say $expected;
my $header = 'sha256=b41572a08af38c59c7736ef55ac89a408ce86f4ac8474b077a7a8962ab87bd15';
say $header eq $expected;sha256=b41572a08af38c59c7736ef55ac89a408ce86f4ac8474b077a7a8962ab87bd15
TrueTwo production notes the module leaves to you. Compute over the raw request body, exactly as received — re-serialized JSON reorders keys and the tag dies. And eq bails out at the first differing character, which in principle leaks timing; for internet-facing verification, compare digests of the two strings, or use a constant-time comparison.
Raw bytes, and the blocksize trap #
hmac (without -hex) returns the tag as a Blob, for when the protocol wants base64 or raw bytes rather than hex. And both subs take a fourth argument, the hash's block size, defaulting to 64 — which is correct for MD5, SHA-1 and SHA-256, and silently wrong for SHA-384 and SHA-512, whose blocks are 128 bytes:
use Digest::HMAC;
use Digest::SHA2;
my $tag = hmac('key', 'message', &sha256);
say $tag ~~ Blob;
say $tag.elems;
say hmac-hex('key', 'message', &sha512, 128).substr(0, 16);True
32
e477384d7ca229ddWith the 128, the SHA-512 tag above matches every other HMAC implementation; leave it at the default and you get a well-formed value that nothing else on earth agrees with — the kind of bug that surfaces as "signature mismatch" in someone else's log. If you use sha512, write the 128.
Where the two engines differ #
One cosmetic thing, kept off the examples above: the tag's type prints as Blob[uint8] under Rakudo and plain Blob under Raku++ — the bytes, the elems, and every hex digit are identical, so smartmatching ~~ Blob is the portable spelling.
What was run to put this page here #
- Parse — every file of the distribution is parsed by Raku++ itself.
- Install —
rakupp install Digest::HMAC, which brings the pure-RakuDigestdistribution with it. - Test — the distribution's own suite: 2 files, green.
- Run — every example on this page, twice under each engine, as the site is built — and the three tags shown were additionally checked against an independent implementation (Python's
hmac), so the page agrees with the world, not just with itself.