Every language
12 langages, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
import crypto from 'node:crypto';
// webhook verify — hash to a FIXED 32 bytes, then compare:
function signatureValid(payload, receivedHex, secret) {
const expected = crypto.createHmac('sha256', secret)
.update(payload).digest(); // always 32 bytes
const received = Buffer.from(receivedHex, 'hex');
// timingSafeEqual THROWS on length mismatch — guard first:
return expected.length === received.length &&
crypto.timingSafeEqual(expected, received);
}crypto.timingSafeEqual throws RangeError on unequal buffer lengths — the length guard (or hashing both sides to a fixed digest size, the webhook pattern) is not optional. Comparing the hex strings with === is the bug this replaces: it returns at the first differing character.
TSTypeScript
import { createHash, timingSafeEqual } from 'node:crypto';
// the wrapper every codebase should have exactly once:
export function fixedTimeEqual(a: Buffer, b: Buffer): boolean {
return a.length === b.length && timingSafeEqual(a, b);
}
// strings arrive at arbitrary lengths — hash to 32 fixed bytes first:
export function tokensMatch(a: string, b: string): boolean {
const sha256 = (s: string) => createHash('sha256').update(s).digest();
return fixedTimeEqual(sha256(a), sha256(b));
}The Buffer types make the byte-level contract explicit. The && ordering is load-bearing — timingSafeEqual throws (it does not silently return false) on unequal lengths, so the length check must come first; hashing normalizes length without exposing it.
GoGo
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
)
// raw primitive: returns 1 (equal) or 0 (differ) — an int, not a bool:
func fixedTimeEqual(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}
// webhook shape: the MAC is a fixed 32 bytes — hash, then compare:
func signatureValid(payload, received, secret []byte) bool {
mac := hmac.New(sha256.New, secret)
mac.Write(payload)
return hmac.Equal(mac.Sum(nil), received)
}ConstantTimeCompare returns 0 immediately when lengths differ — safe because a digest's length is public; for MACs reach for hmac.Equal (the same primitive, named intent). subtle.ConstantTimeCopy copies conditionally — it is not a comparator; use it sparingly.
RsRust
use subtle::ConstantTimeEq;
fn fixed_time_equal(a: &[u8], b: &[u8]) -> bool {
a.ct_eq(b).into() // Choice(1)/Choice(0) -> bool
}
// MAC check — the fixed 32-byte type makes equal lengths a compile-time fact:
fn mac_is_valid(computed: &[u8; 32], received: &[u8; 32]) -> bool {
computed.ct_eq(received).into()
}subtle is the constant-time substrate under all of RustCrypto — hmac, ed25519, and friends are built on it. ct_eq returns Choice, not bool, so .into() is required; unequal-length slices yield Choice(0), which is safe exactly as long as lengths are public.
PHPPHP
// hash_equals(known_string, user_string) — the stored secret goes FIRST:
$valid = hash_equals($storedToken, $tokenFromRequest);
// signatures arrive hex-encoded at unknown length — hash to fixed size:
$valid = hash_equals(
hash_hmac('sha256', $payload, $secret),
$receivedHex
);Argument order is (known, user) — swapping them still returns the right answer but reads backwards in every audit. PHP strings carry their length, so length is never secret in PHP-land and hash_equals handles the rest internally (constant time since PHP 5.6). === on MACs is the leak.
PyPython
import hmac
# compare_digest lives in hmac (hashlib re-exports it); str inputs must be ASCII:
valid = hmac.compare_digest(expected_hex, received_hex)
# webhook shape — HMAC to a fixed hex length, then compare:
digest = hmac.new(secret, payload, 'sha256').hexdigest()
valid = hmac.compare_digest(digest, signature_header)compare_digest accepts str or bytes — str inputs must be ASCII-only or it raises TypeError — and its arguments are order-free. a == b on MACs is the leak it replaces: CPython's == returns at the first differing byte.
C#C#
using System.Security.Cryptography;
static bool FixedTimeEqual(byte[] a, byte[] b)
{
return a.Length == b.Length
&& CryptographicOperations.FixedTimeEquals(a, b);
}CryptographicOperations.FixedTimeEquals (.NET Core 2.1+) already returns false on unequal lengths — the guard exists for clarity, not safety. LINQ's SequenceEqual and string == short-circuit and leak; before 2.1 people abused MachineKey.Equals for this job.
JvJava
import java.security.MessageDigest;
static boolean signatureValid(byte[] expected, byte[] received) {
return MessageDigest.isEqual(expected, received);
}MessageDigest.isEqual has been constant-time since Java 6u17 and tolerates unequal lengths — it is the one JDK comparator that is safe for secrets. Arrays.equals and String.equals return at the first mismatch and leak the prefix.
SwSwift
import CryptoKit
// Swift has NO stdlib constant-time compare — the honest fallback:
func fixedTimeEqual(_ a: [UInt8], _ b: [UInt8]) -> Bool {
guard a.count == b.count else { return false } // length is public
var diff: UInt8 = 0
for i in a.indices {
diff |= a[i] ^ b[i] // fold ALL bytes, decide once at the end
}
return diff == 0
}
// the MAC use-case — Apple ships the real thing:
let ok = HMAC<SHA256>.isValidAuthenticationCode(
receivedMAC, authenticating: payload, using: key)Swift's stdlib has no constant-time compare — == on Data/Array short-circuits. The XOR-fold over every byte with a length guard is the honest fallback; for MACs prefer CryptoKit's HMAC.isValidAuthenticationCode (swift-crypto brings it to Linux). When a library primitive exists, prefer it — hand-rolled folds depend on the optimizer staying honest.
KtKotlin
import java.security.MessageDigest
import java.util.HexFormat
fun signatureValid(expectedHex: String, receivedHex: String): Boolean =
MessageDigest.isEqual(
HexFormat.of().parseHex(expectedHex), // hex string -> bytes (JDK 17+)
HexFormat.of().parseHex(receivedHex),
)Same JVM primitive as Java — MessageDigest.isEqual. Hex strings must be parsed to bytes before comparing: == on the hex strings themselves leaks the prefix. HexFormat.of().parseHex (JDK 17+) replaces the old clunky parse loops.
RbRuby
require 'openssl'
valid = OpenSSL.secure_compare(expected, received)
# Rails ships the same primitive under a longer name:
# require 'active_support/security_utils'
# valid = ActiveSupport::SecurityUtils.secure_compare(expected, received)OpenSSL.secure_compare SHA-256-hashes both sides to a fixed size before comparing — the digest-then-compare pattern, built in. Rails re-exposes it as ActiveSupport::SecurityUtils.secure_compare; plain == on secrets short-circuits at the first differing byte.
ZigZig
const std = @import("std");
const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
fn signatureValid(payload: []const u8, received: [32]u8, key: []const u8) bool {
var mac: [HmacSha256.mac_length]u8 = undefined; // fixed 32 bytes
HmacSha256.create(&mac, payload, key);
return std.crypto.timing_safe.eql([32]u8, mac, received);
}std.crypto.timing_safe.eql takes two values of the SAME fixed-size array type — equal lengths are enforced by the type system, which is exactly why you HMAC both sides to the fixed mac_length first. std.crypto.utils.secureZero wipes keys; it is not a comparator.