How to use crypto.timingSafeEqual with strings
Node’s crypto.timingSafeEqual
only works with buffers. To make it work with strings, you should convert the strings to UTF-16 buffers and then pass them to crypto.timingSafeEqual
.
Here’s the code:
import { Buffer } from "node:buffer";
import * as crypto from "node:crypto";
function stringTimingSafeEqual(a, b) {
const bufferA = Buffer.from(a, "utf16le");
const bufferB = Buffer.from(b, "utf16le");
return crypto.timingSafeEqual(bufferA, bufferB);
}
It’s important that you use UTF-16 because it’s the only encoding supported by Node that doesn’t lose some information along the way:
utf8
encoding normalizes lone surrogates. For example,"\udc69"
and"\udc6a"
get normalized to the same buffer, which would mean that they’re considered equal (which is wrong).latin1
encoding will truncate multi-byte code points. For example, the"I"
(U+0049) and"汉"
(U+6C49) would be considered equivalent.ascii
andbinary
are aliases forlatin1
in this situation, so they have the same issue.base64
andbase64url
drop whitespace, so" "
and"\t"
would be considered equivalent.hex
only works if the string is hex, so"XX"
and"YY"
would be considered equivalent.
The only one that doesn’t lose data is utf16le
, which is why you should use it here. (You could also use its legacy alias, ucs2
, but I think this is less clear.)