std/crypto/timing_safe_equal.ts

62 lines
1.9 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
function toDataView(
value: ArrayBufferView | ArrayBufferLike | DataView,
): DataView {
if (value instanceof DataView) {
return value;
}
return ArrayBuffer.isView(value)
? new DataView(value.buffer, value.byteOffset, value.byteLength)
: new DataView(value);
}
/**
* When checking the values of cryptographic hashes are equal, default
* comparisons can be susceptible to timing based attacks, where attacker is
* able to find out information about the host system by repeatedly checking
* response times to equality comparisons of values.
*
* It is likely some form of timing safe equality will make its way to the
* WebCrypto standard (see:
* {@link https://github.com/w3c/webcrypto/issues/270 | w3c/webcrypto#270}), but until
* that time, `timingSafeEqual()` is provided:
*
* @example Usage
* ```ts
* import { timingSafeEqual } from "@std/crypto/timing-safe-equal";
* import { assert } from "@std/assert";
*
* const a = await crypto.subtle.digest(
* "SHA-384",
* new TextEncoder().encode("hello world"),
* );
* const b = await crypto.subtle.digest(
* "SHA-384",
* new TextEncoder().encode("hello world"),
* );
*
* assert(timingSafeEqual(a, b));
* ```
*
* @param a The first value to compare.
* @param b The second value to compare.
* @returns `true` if the values are equal, otherwise `false`.
*/
export function timingSafeEqual(
a: ArrayBufferView | ArrayBufferLike | DataView,
b: ArrayBufferView | ArrayBufferLike | DataView,
): boolean {
if (a.byteLength !== b.byteLength) return false;
const dataViewA = toDataView(a);
const dataViewB = toDataView(b);
const length = a.byteLength;
let out = 0;
let i = -1;
while (++i < length) {
out |= dataViewA.getUint8(i) ^ dataViewB.getUint8(i);
}
return out === 0;
}