All files / crypto / timing_safe_equal.ts

100.00% Branches 15/15
100.00% Functions 2/2
100.00% Lines 25/25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
 
 
 
x34
x34
 
x34
x20
x20
x34
x34
x34
x34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x5
x5
x5
 
x19
x2
x2
 
x2
x17
x17
x17
x17
x19
x96
x96
x17
x19




































































// Copyright 2018-2026 the Deno authors. MIT license.
// This module is browser compatible.

function toUint8Array(
  value: ArrayBufferView | ArrayBufferLike,
): Uint8Array {
  if (value instanceof Uint8Array) {
    return value;
  }
  return ArrayBuffer.isView(value)
    ? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
    : new Uint8Array(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.
 *
 * Note: This is a best-effort constant-time comparison implemented in
 * JavaScript. The V8 JIT compiler does not provide formal constant-time
 * guarantees, and inputs backed by `SharedArrayBuffer` are susceptible to
 * concurrent modification during comparison.
 *
 * @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`.
 * @throws {TypeError} If the byte lengths of the two buffers are not equal.
 */
export function timingSafeEqual(
  a: ArrayBufferView | ArrayBufferLike,
  b: ArrayBufferView | ArrayBufferLike,
): boolean {
  if (a.byteLength !== b.byteLength) {
    throw new TypeError(
      `Cannot compare buffers of different byte lengths (${a.byteLength} vs ${b.byteLength})`,
    );
  }
  const ua = toUint8Array(a);
  const ub = toUint8Array(b);
  const length = ua.length;
  let out = 0;
  for (let i = 0; i < length; i++) {
    out |= ua[i]! ^ ub[i]!;
  }
  return out === 0;
}