HMAC Generator — C++ source
Generate a keyed-hash HMAC (SHA-1/256/384/512) for a message and secret. Runs entirely in your browser via Web Crypto, with a shareable link to your exact input.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// hmac-generator — RFC 2104 keyed-hash HMAC of a UTF-8 message, hex output.
//
// Language: C++ (C++17, standard library only)
// Source: CosmoDev polyglot showcase port of the `hmac-generator` tool,
// ported from src/lib/hmac.ts (the canonical TypeScript lib).
// License: display source — part of CosmoDev's polyglot tool pages.
//
// The TypeScript reference delegates to `crypto.subtle.sign` with an HMAC
// key, and the Rust port uses the RustCrypto `hmac`/`sha1`/`sha2` crates.
// The C++ standard library ships no cryptographic primitives, so the
// ecosystem equivalent would be OpenSSL's libcrypto (`HMAC()`) or Botan;
// to stay standard-library-only, the four SHA cores are implemented below
// from FIPS 180-4 — mirroring the C++ port of the `hash` tool — with HMAC
// layered on top from RFC 2104:
//
// K0 = secret, hashed when longer than the block size, zero-padded
// tag = H((K0 ^ opad) || H((K0 ^ ipad) || message))
//
// A std::string carries no encoding, so callers pass the text's UTF-8 bytes —
// the same byte sequence a browser hands to crypto.subtle.sign. An empty
// algorithm name selects SHA-256, standing in for the optional parameter
// default the TypeScript reference declares; an empty secret and an unknown
// algorithm name both throw std::invalid_argument — the C++ standing of the
// Python port's ValueError and the two arms of the Rust port's HmacError
// enum (SubtleCrypto refuses a zero-length key; the ports keep parity).
//
// SHA-1 is offered because the tool lists it for legacy compatibility; it
// is not collision-resistant and must not authenticate anything.
//
// Build: c++ -std=c++17 hmac.cpp
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
namespace cosmodev::hmac_tool {
// ----------------------------------------------------------- algorithm set ---
/// Canonical algorithm names. The spellings match the TypeScript union so the
/// same string works across every port.
inline constexpr std::string_view kSha1{"SHA-1"};
inline constexpr std::string_view kSha256{"SHA-256"}; // default algorithm
inline constexpr std::string_view kSha384{"SHA-384"};
inline constexpr std::string_view kSha512{"SHA-512"};
namespace detail {
// ----------------------------------------------------------------- helpers ---
constexpr std::uint32_t rotl32(std::uint32_t x, unsigned n) { return (x << n) | (x >> (32 - n)); }
constexpr std::uint32_t rotr32(std::uint32_t x, unsigned n) { return (x >> n) | (x << (32 - n)); }
constexpr std::uint64_t rotr64(std::uint64_t x, unsigned n) { return (x >> n) | (x << (64 - n)); }
/// Lowercase hexadecimal encoding of a byte sequence — the C++ shape of the
/// Rust port's `to_hex`. Lookup-table based, one allocation for the result.
inline std::string to_hex(const std::uint8_t* bytes, std::size_t n)
{
constexpr char HEX[] = "0123456789abcdef";
std::string out;
out.resize(n * 2);
for (std::size_t i = 0; i < n; i++) {
out[i * 2] = HEX[bytes[i] >> 4];
out[i * 2 + 1] = HEX[bytes[i] & 0x0f];
}
return out;
}
/// Big-endian store/load — the FIPS word order, independent of host
/// endianness and of the htonl family's platform quirks.
inline void store_be32(std::uint8_t* p, std::uint32_t v)
{
p[0] = static_cast<std::uint8_t>(v >> 24);
p[1] = static_cast<std::uint8_t>(v >> 16);
p[2] = static_cast<std::uint8_t>(v >> 8);
p[3] = static_cast<std::uint8_t>(v);
}
inline void store_be64(std::uint8_t* p, std::uint64_t v)
{
for (int i = 0; i < 8; i++)
p[i] = static_cast<std::uint8_t>(v >> (56 - 8 * i));
}
constexpr std::uint32_t load_be32(const std::uint8_t* p)
{
return (static_cast<std::uint32_t>(p[0]) << 24) | (static_cast<std::uint32_t>(p[1]) << 16) |
(static_cast<std::uint32_t>(p[2]) << 8) | static_cast<std::uint32_t>(p[3]);
}
inline std::uint64_t load_be64(const std::uint8_t* p)
{
std::uint64_t v = 0;
for (int i = 0; i < 8; i++)
v = (v << 8) | p[i];
return v;
}
// --------------------------------------------- SHA-1, FIPS 180-4 section 6.1 ---
inline void sha1_compress(std::array<std::uint32_t, 5>& h, const std::uint8_t* block)
{
std::array<std::uint32_t, 80> w{};
for (int i = 0; i < 16; i++)
w[static_cast<std::size_t>(i)] = load_be32(block + static_cast<std::size_t>(i) * 4);
for (int i = 16; i < 80; i++) {
const auto j = static_cast<std::size_t>(i);
w[j] = rotl32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
}
std::uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
for (int i = 0; i < 80; i++) {
std::uint32_t f, k;
if (i < 20) {
f = (b & c) | (~b & d); // Ch
k = 0x5a827999u;
} else if (i < 40) {
f = b ^ c ^ d; // Parity
k = 0x6ed9eba1u;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d); // Maj
k = 0x8f1bbcdcu;
} else {
f = b ^ c ^ d; // Parity
k = 0xca62c1d6u;
}
const std::uint32_t t = rotl32(a, 5) + f + e + k + w[static_cast<std::size_t>(i)];
e = d;
d = c;
c = rotl32(b, 30);
b = a;
a = t;
}
h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
}
/// One-shot SHA-1. Full blocks are compressed straight from the input; the
/// final one or two blocks carry the remainder plus the FIPS padding (0x80,
/// zeros, 64-bit big-endian bit length). No heap allocation.
inline void sha1_sum(const std::uint8_t* msg, std::size_t len, std::uint8_t* out /* 20 */)
{
std::array<std::uint32_t, 5> h{0x67452301u, 0xefcdab89u, 0x98badcfeu, 0x10325476u, 0xc3d2e1f0u};
std::size_t off = 0;
while (len - off >= 64) {
sha1_compress(h, msg + off);
off += 64;
}
std::array<std::uint8_t, 128> last{};
const std::size_t rem = len - off;
std::memcpy(last.data(), msg + off, rem);
last[rem] = 0x80;
const std::size_t blocks = (rem + 9 <= 64) ? 1 : 2; // msg + 0x80 + 8 length bytes
store_be64(last.data() + blocks * 64 - 8, static_cast<std::uint64_t>(len) * 8);
sha1_compress(h, last.data());
if (blocks == 2)
sha1_compress(h, last.data() + 64);
for (int i = 0; i < 5; i++)
store_be32(out + static_cast<std::size_t>(i) * 4, h[static_cast<std::size_t>(i)]);
}
// ------------------------------------------- SHA-256, FIPS 180-4 section 6.2 ---
inline constexpr std::array<std::uint32_t, 64> kSha256Round{
0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u,
0x923f82a4u, 0xab1c5ed5u, 0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u,
0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u, 0xe49b69c1u, 0xefbe4786u,
0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u,
0x06ca6351u, 0x14292967u, 0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u,
0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u, 0xa2bfe8a1u, 0xa81a664bu,
0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au,
0x5b9cca4fu, 0x682e6ff3u, 0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u,
0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u,
};
inline void sha256_compress(std::array<std::uint32_t, 8>& h, const std::uint8_t* block)
{
std::array<std::uint32_t, 64> w{};
for (int i = 0; i < 16; i++)
w[static_cast<std::size_t>(i)] = load_be32(block + static_cast<std::size_t>(i) * 4);
for (int i = 16; i < 64; i++) {
const auto j = static_cast<std::size_t>(i);
const std::uint32_t s0 = rotr32(w[j - 15], 7) ^ rotr32(w[j - 15], 18) ^ (w[j - 15] >> 3);
const std::uint32_t s1 = rotr32(w[j - 2], 17) ^ rotr32(w[j - 2], 19) ^ (w[j - 2] >> 10);
w[j] = w[j - 16] + s0 + w[j - 7] + s1;
}
std::uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
std::uint32_t e = h[4], f = h[5], g = h[6], hh = h[7];
for (int i = 0; i < 64; i++) {
const auto j = static_cast<std::size_t>(i);
const std::uint32_t S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);
const std::uint32_t ch = (e & f) ^ (~e & g);
const std::uint32_t t1 = hh + S1 + ch + kSha256Round[j] + w[j];
const std::uint32_t S0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22);
const std::uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
const std::uint32_t t2 = S0 + maj;
hh = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
h[5] += f;
h[6] += g;
h[7] += hh;
}
/// One-shot SHA-256 — same padding shape as SHA-1 (64-byte blocks, 64-bit
/// big-endian bit length), different round function.
inline void sha256_sum(const std::uint8_t* msg, std::size_t len, std::uint8_t* out /* 32 */)
{
std::array<std::uint32_t, 8> h{0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u};
std::size_t off = 0;
while (len - off >= 64) {
sha256_compress(h, msg + off);
off += 64;
}
std::array<std::uint8_t, 128> last{};
const std::size_t rem = len - off;
std::memcpy(last.data(), msg + off, rem);
last[rem] = 0x80;
const std::size_t blocks = (rem + 9 <= 64) ? 1 : 2; // msg + 0x80 + 8 length bytes
store_be64(last.data() + blocks * 64 - 8, static_cast<std::uint64_t>(len) * 8);
sha256_compress(h, last.data());
if (blocks == 2)
sha256_compress(h, last.data() + 64);
for (int i = 0; i < 8; i++)
store_be32(out + static_cast<std::size_t>(i) * 4, h[static_cast<std::size_t>(i)]);
}
// ------------------------- SHA-384 / SHA-512, FIPS 180-4 sections 6.3 and 6.4 ---
inline constexpr std::array<std::uint64_t, 80> kSha512Round{
0x428a2f98d728ae22ull, 0x7137449123ef65cdull, 0xb5c0fbcfec4d3b2full, 0xe9b5dba58189dbbcull,
0x3956c25bf348b538ull, 0x59f111f1b605d019ull, 0x923f82a4af194f9bull, 0xab1c5ed5da6d8118ull,
0xd807aa98a3030242ull, 0x12835b0145706fbeull, 0x243185be4ee4b28cull, 0x550c7dc3d5ffb4e2ull,
0x72be5d74f27b896full, 0x80deb1fe3b1696b1ull, 0x9bdc06a725c71235ull, 0xc19bf174cf692694ull,
0xe49b69c19ef14ad2ull, 0xefbe4786384f25e3ull, 0x0fc19dc68b8cd5b5ull, 0x240ca1cc77ac9c65ull,
0x2de92c6f592b0275ull, 0x4a7484aa6ea6e483ull, 0x5cb0a9dcbd41fbd4ull, 0x76f988da831153b5ull,
0x983e5152ee66dfabull, 0xa831c66d2db43210ull, 0xb00327c898fb213full, 0xbf597fc7beef0ee4ull,
0xc6e00bf33da88fc2ull, 0xd5a79147930aa725ull, 0x06ca6351e003826full, 0x142929670a0e6e70ull,
0x27b70a8546d22ffcull, 0x2e1b21385c26c926ull, 0x4d2c6dfc5ac42aedull, 0x53380d139d95b3dfull,
0x650a73548baf63deull, 0x766a0abb3c77b2a8ull, 0x81c2c92e47edaee6ull, 0x92722c851482353bull,
0xa2bfe8a14cf10364ull, 0xa81a664bbc423001ull, 0xc24b8b70d0f89791ull, 0xc76c51a30654be30ull,
0xd192e819d6ef5218ull, 0xd69906245565a910ull, 0xf40e35855771202aull, 0x106aa07032bbd1b8ull,
0x19a4c116b8d2d0c8ull, 0x1e376c085141ab53ull, 0x2748774cdf8eeb99ull, 0x34b0bcb5e19b48a8ull,
0x391c0cb3c5c95a63ull, 0x4ed8aa4ae3418acbull, 0x5b9cca4f7763e373ull, 0x682e6ff3d6b2b8a3ull,
0x748f82ee5defb2fcull, 0x78a5636f43172f60ull, 0x84c87814a1f0ab72ull, 0x8cc702081a6439ecull,
0x90befffa23631e28ull, 0xa4506cebde82bde9ull, 0xbef9a3f7b2c67915ull, 0xc67178f2e372532bull,
0xca273eceea26619cull, 0xd186b8c721c0c207ull, 0xeada7dd6cde0eb1eull, 0xf57d4f7fee6ed178ull,
0x06f067aa72176fbaull, 0x0a637dc5a2c898a6ull, 0x113f9804bef90daeull, 0x1b710b35131c471bull,
0x28db77f523047d84ull, 0x32caab7b40c72493ull, 0x3c9ebe0a15c9bebcull, 0x431d67c49c100d4cull,
0x4cc5d4becb3e42b6ull, 0x597f299cfc657e2aull, 0x5fcb6fab3ad6faecull, 0x6c44198c4a475817ull,
};
inline void sha512_compress(std::array<std::uint64_t, 8>& h, const std::uint8_t* block /* 128 */)
{
std::array<std::uint64_t, 80> w{};
for (int i = 0; i < 16; i++)
w[static_cast<std::size_t>(i)] = load_be64(block + static_cast<std::size_t>(i) * 8);
for (int i = 16; i < 80; i++) {
const auto j = static_cast<std::size_t>(i);
const std::uint64_t s0 = rotr64(w[j - 15], 1) ^ rotr64(w[j - 15], 8) ^ (w[j - 15] >> 7);
const std::uint64_t s1 = rotr64(w[j - 2], 19) ^ rotr64(w[j - 2], 61) ^ (w[j - 2] >> 6);
w[j] = w[j - 16] + s0 + w[j - 7] + s1;
}
std::uint64_t a = h[0], b = h[1], c = h[2], d = h[3];
std::uint64_t e = h[4], f = h[5], g = h[6], hh = h[7];
for (int i = 0; i < 80; i++) {
const auto j = static_cast<std::size_t>(i);
const std::uint64_t S1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41);
const std::uint64_t ch = (e & f) ^ (~e & g);
const std::uint64_t t1 = hh + S1 + ch + kSha512Round[j] + w[j];
const std::uint64_t S0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39);
const std::uint64_t maj = (a & b) ^ (a & c) ^ (b & c);
const std::uint64_t t2 = S0 + maj;
hh = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
h[5] += f;
h[6] += g;
h[7] += hh;
}
/// One-shot core shared by SHA-384 and SHA-512: identical compression, 128-byte
/// blocks, and a 128-bit big-endian bit-length field (the high half is the top
/// three bits of `len`, exactly zero below the 2^61-byte FIPS ceiling). The
/// algorithms differ only in the IV and in how many words are output — SHA-384
/// truncates to the first six words (FIPS 180-4 section 6.3.3).
inline void sha512_family_sum(const std::array<std::uint64_t, 8>& iv, unsigned out_words,
const std::uint8_t* msg, std::size_t len, std::uint8_t* out)
{
std::array<std::uint64_t, 8> h{iv};
std::size_t off = 0;
while (len - off >= 128) {
sha512_compress(h, msg + off);
off += 128;
}
std::array<std::uint8_t, 256> last{};
const std::size_t rem = len - off;
std::memcpy(last.data(), msg + off, rem);
last[rem] = 0x80;
const std::size_t blocks = (rem + 17 <= 128) ? 1 : 2; // msg + 0x80 + 16 length bytes
store_be64(last.data() + blocks * 128 - 16, static_cast<std::uint64_t>(len >> 61)); // bit length, high
store_be64(last.data() + blocks * 128 - 8, static_cast<std::uint64_t>(len) << 3); // bit length, low
sha512_compress(h, last.data());
if (blocks == 2)
sha512_compress(h, last.data() + 128);
for (unsigned i = 0; i < out_words; i++)
store_be64(out + static_cast<std::size_t>(i) * 8, h[i]);
}
inline void sha384_sum(const std::uint8_t* msg, std::size_t len, std::uint8_t* out /* 48 */)
{
constexpr std::array<std::uint64_t, 8> kIv{
0xcbbb9d5dc1059ed8ull, 0x629a292a367cd507ull, 0x9159015a3070dd17ull, 0x152fecd8f70e5939ull,
0x67332667ffc00b31ull, 0x8eb44a8768581511ull, 0xdb0c2e0d64f98fa7ull, 0x47b5481dbefa4fa4ull,
};
sha512_family_sum(kIv, 6, msg, len, out);
}
inline void sha512_sum(const std::uint8_t* msg, std::size_t len, std::uint8_t* out /* 64 */)
{
constexpr std::array<std::uint64_t, 8> kIv{
0x6a09e667f3bcc908ull, 0xbb67ae8584caa73bull, 0x3c6ef372fe94f82bull, 0xa54ff53a5f1d36f1ull,
0x510e527fade682d1ull, 0x9b05688c2b3e6c1full, 0x1f83d9abfb41bd6bull, 0x5be0cd19137e2179ull,
};
sha512_family_sum(kIv, 8, msg, len, out);
}
// ---------------------------------------------------- HMAC core, RFC 2104 ---
/// One-shot hash function shape shared by all four cores above.
using SumFn = void (*)(const std::uint8_t*, std::size_t, std::uint8_t*);
/// Longest HMAC key block, in bytes — SHA-384/SHA-512 use 128 (RFC 2104
/// section 2 + FIPS 180-4).
inline constexpr std::size_t kMaxBlockLen = 128;
/// Longest digest, in bytes — size raw tag buffers with this.
inline constexpr std::size_t kMaxDigestLen = 64;
/**
* RFC 2104 HMAC over the one-shot hash function `sum`:
*
* K0 secret, hashed when longer than the block size, zero-padded
* tag H((K0 ^ opad) || H((K0 ^ ipad) || message))
*
* Each pass concatenates pad + payload into one vector so the one-shot SHA
* cores above stay untouched — display-source clarity over the streaming
* shape a production HMAC would use. Returns the tag right-aligned space for
* `kMaxDigestLen` bytes, of which the first `digest_len` are meaningful.
*/
inline std::array<std::uint8_t, kMaxDigestLen> hmac_sum(SumFn sum, std::size_t block_len,
std::size_t digest_len,
const std::uint8_t* msg, std::size_t msg_len,
const std::uint8_t* key, std::size_t key_len)
{
// K0: a digest never exceeds its own block size, so hashing an
// over-long key always leaves room for the zero padding.
std::array<std::uint8_t, kMaxBlockLen> k0{};
if (key_len > block_len)
sum(key, key_len, k0.data());
else
std::memcpy(k0.data(), key, key_len);
std::vector<std::uint8_t> inner(block_len + msg_len);
std::vector<std::uint8_t> outer(block_len + digest_len);
for (std::size_t i = 0; i < block_len; i++) {
inner[i] = static_cast<std::uint8_t>(k0[i] ^ 0x36); // ipad
outer[i] = static_cast<std::uint8_t>(k0[i] ^ 0x5c); // opad
}
std::array<std::uint8_t, kMaxDigestLen> inner_digest{};
std::memcpy(inner.data() + block_len, msg, msg_len);
sum(inner.data(), inner.size(), inner_digest.data());
std::memcpy(outer.data() + block_len, inner_digest.data(), digest_len);
std::array<std::uint8_t, kMaxDigestLen> tag{};
sum(outer.data(), outer.size(), tag.data());
return tag;
}
} // namespace detail
// --------------------------------------------------------------- public API ---
/**
* Compute HMAC(`message`, `secret`) under the named algorithm and return it
* as lowercase hex.
*
* Both inputs are the strings' UTF-8 byte sequences (std::string_view over
* UTF-8 content needs no encoding step). An empty `algorithm` selects
* SHA-256, standing in for the optional-parameter default the TypeScript
* reference declares; an empty `secret` or an unknown algorithm name throws
* std::invalid_argument.
*/
[[nodiscard]] inline std::string hmac_hex(std::string_view message, std::string_view secret,
std::string_view algorithm)
{
if (algorithm.empty())
algorithm = kSha256;
if (secret.empty())
throw std::invalid_argument("HMAC secret must not be empty");
const auto* msg = reinterpret_cast<const std::uint8_t*>(message.data());
const auto* key = reinterpret_cast<const std::uint8_t*>(secret.data());
// Each arm fixes the hash function and its RFC 2104 parameters at compile
// time: 64-byte key blocks for the 32-bit SHA family, 128-byte for
// SHA-384/SHA-512.
if (algorithm == kSha1)
return detail::to_hex(detail::hmac_sum(detail::sha1_sum, 64, 20, msg, message.size(), key, secret.size()).data(), 20);
if (algorithm == kSha256)
return detail::to_hex(detail::hmac_sum(detail::sha256_sum, 64, 32, msg, message.size(), key, secret.size()).data(), 32);
if (algorithm == kSha384)
return detail::to_hex(detail::hmac_sum(detail::sha384_sum, 128, 48, msg, message.size(), key, secret.size()).data(), 48);
if (algorithm == kSha512)
return detail::to_hex(detail::hmac_sum(detail::sha512_sum, 128, 64, msg, message.size(), key, secret.size()).data(), 64);
throw std::invalid_argument("unsupported HMAC algorithm: " + std::string(algorithm));
}
} // namespace cosmodev::hmac_tool
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →