Every language
12 languages, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode('secret'),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
);
const mac = await crypto.subtle.sign('HMAC', key, enc.encode('message'));
const hex = [...new Uint8Array(mac)]
.map((b) => b.toString(16).padStart(2, '0'))
.join('');Node's sync one-liner: crypto.createHmac('sha256', 'secret').update('message').digest('hex'). Verification in Node: crypto.timingSafeEqual — browsers have no constant-time compare, compare server-side.
TSTypeScript
async function hmacSha256Hex(secret: string, message: string): Promise<string> {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
);
const mac = await crypto.subtle.sign('HMAC', key, enc.encode(message));
return [...new Uint8Array(mac)]
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}Webhook verification in Node: crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received)) — a === on MACs is a timing leak.
GoGo
mac := hmac.New(sha256.New, []byte("secret"))
mac.Write([]byte("message"))
sum := mac.Sum(nil)
hexStr := hex.EncodeToString(sum)
// verify — constant time, never bytes.Equal or ==
expected, _ := hex.DecodeString(hexStr)
ok := hmac.Equal(sum, expected)
fmt.Println(hexStr, ok)hmac.Equal runs in constant time; bytes.Equal returns at the first mismatch and leaks the common prefix length through response timing.
RsRust
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
fn main() {
// sign
let mut mac = HmacSha256::new_from_slice(b"secret").unwrap();
mac.update(b"message");
let tag = mac.finalize().into_bytes();
// verify — constant-time inside verify_slice
let mut verifier = HmacSha256::new_from_slice(b"secret").unwrap();
verifier.update(b"message");
verifier.verify_slice(&tag).expect("tag mismatch");
}verify_slice returns Err on any mismatch without early exit — the RustCrypto crates bake the constant-time discipline in. new_from_slice accepts any key length.
PHPPHP
$hex = hash_hmac('sha256', 'message', 'secret');
// webhook-style verification
$expected = hash_hmac('sha256', $receivedPayload, $secret);
if (!hash_equals($expected, $receivedHex)) {
throw new RuntimeException('bad signature');
}hash_equals is the constant-time compare PHP ships for exactly this. Note the argument order: known value first.
PyPython
import hashlib
import hmac
mac = hmac.new(b'secret', b'message', hashlib.sha256).hexdigest()
# constant-time verification
ok = hmac.compare_digest(mac, expected_hex)Use the hmac module, never hashlib.sha256(key + msg) — raw concatenation falls to length-extension attacks. compare_digest accepts str or bytes.
C#C#
using System.Security.Cryptography;
byte[] Tag(string secret, string message) =>
HMACSHA256.HashData(
Encoding.UTF8.GetBytes(secret),
Encoding.UTF8.GetBytes(message));
bool Verify(string secret, string message, byte[] expected) =>
CryptographicOperations.FixedTimeEquals(
Tag(secret, message), expected);HMACSHA256.HashData (.NET 7+) is the one-shot sign. CryptographicOperations.FixedTimeEquals is the constant-time compare — LINQ's SequenceEqual leaks.
JvJava
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(
"secret".getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] tag = mac.doFinal("message".getBytes(StandardCharsets.UTF_8));
String hex = HexFormat.of().formatHex(tag);
// constant-time verify
boolean ok = MessageDigest.isEqual(tag, expectedBytes);The algorithm string is "HmacSHA256" — passing "SHA-256" throws a NoSuchAlgorithmException. MessageDigest.isEqual is the constant-time compare.
SwSwift
import CryptoKit
let key = SymmetricKey(data: Data("secret".utf8))
let mac = HMAC<SHA256>.authenticationCode(
for: Data("message".utf8), using: key)
let hex = mac.map { String(format: "%02x", $0) }.joined()
// constant-time verification built in
let ok = HMAC<SHA256>.isValidAuthenticationCode(
Data(hex: hex),
authenticating: Data("message".utf8),
using: key)SymmetricKey wraps the secret as bytes, not a string. isValidAuthenticationCode never early-exits — CryptoKit made the safe path the easy path.
KtKotlin
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest
import java.util.HexFormat
fun hmacSha256Hex(secret: String, message: String): String {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(secret.toByteArray(), "HmacSHA256"))
return HexFormat.of().formatHex(mac.doFinal(message.toByteArray()))
}
fun verify(secret: String, message: String, expectedHex: String): Boolean {
val expected = HexFormat.of().parseHex(expectedHex)
return MessageDigest.isEqual( // constant-time
HexFormat.of().parseHex(hmacSha256Hex(secret, message)), expected)
}Same JVM primitives as Java — the Kotlin win is the two clean functions instead of the initialized-once pattern.
RbRuby
require 'openssl'
mac = OpenSSL::HMAC.hexdigest('SHA256', 'secret', 'message')
# constant-time verification
ok = OpenSSL.secure_compare(mac, received_hex)OpenSSL.secure_compare (active_support adds ActiveSupport::SecurityUtils.secure_compare on top) — plain == leaks.
ZigZig
const std = @import("std");
pub fn main() !void {
const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
var out: [HmacSha256.mac_length]u8 = undefined;
HmacSha256.create(&out, "message", "secret"); // one-shot sign
std.debug.print("{s}\n", .{std.fmt.fmtSliceHexLower(&out)});
// verify — constant-time internally
const ok = HmacSha256.verify(&out, "message", "secret");
std.debug.print("verified: {}\n", .{ok});
}create/verify are the one-shot pair; init/update/final handle streaming. std.crypto is Monocypher-derived and always constant-time where it matters.