denoland/deno · error · RangeError
ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH
ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH
Error message
Input buffers must have the same byte length
What it means
Constant-time comparison works by XOR-ing bytes, which inherently reveals length, so both inputs must have the same byte length. Deno's timingSafeEqual (like Node's) throws ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH before comparing when byteLengthOf(a) !== byteLengthOf(b). This is a safety guard, not a size limit.
Source
Thrown at ext/node/polyfills/internal_binding/_timingSafeEqual.ts:80
);
}
return new DataView(
TypedArrayPrototypeGetBuffer(ab),
TypedArrayPrototypeGetByteOffset(ab),
TypedArrayPrototypeGetByteLength(ab),
);
}
return new DataView(ab);
}
/** Compare to array buffers or data views in a way that timing based attacks
* cannot gain information about the platform. */
function stdTimingSafeEqual(
a: ArrayBufferView | ArrayBufferLike | DataView,
b: ArrayBufferView | ArrayBufferLike | DataView,
): boolean {
if (byteLengthOf(a) !== byteLengthOf(b)) {
throw new ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH();
}
if (!isDataView(a)) {
a = toDataView(a);
}
if (!isDataView(b)) {
b = toDataView(b);
}
const length = DataViewPrototypeGetByteLength(a);
let out = 0;
let i = -1;
while (++i < length) {
out |= DataViewPrototypeGetUint8(a, i) ^ DataViewPrototypeGetUint8(b, i);
}
return out === 0;
}
const timingSafeEqual = (
buf1: Buffer | DataView | ArrayBuffer,View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Compare fixed-length digests instead of raw inputs: hash both sides with SHA-256 first, then timingSafeEqual(hash(a), hash(b))
- Or length-check first and fail closed — length is not secret: if (a.length !== b.length) return false
- Make sure both sides are decoded to raw bytes with the same encoding before comparing
Example fix
// before const ok = crypto.timingSafeEqual( Buffer.from(userMacHex, 'utf8'), // 64 bytes of hex text computedMac, // 32 raw bytes -> throws ); // after const ok = Buffer.from(userMacHex, 'hex').length === computedMac.length && crypto.timingSafeEqual(Buffer.from(userMacHex, 'hex'), computedMac);
Defensive patterns
Strategy: validation
Validate before calling
function safeTimingSafeEqual(a: Buffer, b: Buffer): boolean {
if (a.length !== b.length) return false; // length is not secret
return crypto.timingSafeEqual(a, b);
}
// or compare digests to eliminate length dependence:
const sha256 = (v: Buffer) => crypto.createHash('sha256').update(v).digest();
const ok = crypto.timingSafeEqual(sha256(a), sha256(b)); Try / catch
try {
ok = crypto.timingSafeEqual(a, b);
} catch (e: any) {
if (e?.code === 'ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH') ok = false;
else throw e;
} Prevention
- Hash both inputs (SHA-256) before comparing when lengths can differ
- Strip prefixes ('Bearer ', 'sha256=') before decoding and comparing
- Decode hex/base64 to raw bytes on both sides so encodings match
When it happens
Trigger: Comparing a hex-encoded digest (64 chars/bytes) with the raw 32-byte digest; user-supplied token of different length than the stored one; one side still containing a prefix ('Bearer ', 'sha256='); hex vs base64 encodings of the same digest producing different lengths.
Common situations: Webhook signature verification (Stripe/GitHub style), session-token comparison, HMAC checks where one side is encoded and the other raw; attacker-controlled input of arbitrary length hitting the compare.
Related errors
- ERR_INVALID_ARG_TYPE
- ERR_INVALID_HTTP_TOKEN
- ERR_INVALID_CHAR
- ERR_INVALID_CHAR
- ERR_TLS_INVALID_PROTOCOL_METHOD
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/47b6ffbf1335bbe8.
Report an issue: GitHub.