A JavaScript library for timing-safe comparison of strings using the "Double HMAC verification" pattern. The library works in Node.js, Edge and Browser environments and uses the Subtle WebCrypto API under the hood.
Node.js
If you need a timing safe comparison function for Node.js only, then consider using the nativecrypto.timingSafeEqual
implementation.
npm install @advena/web-timing-safe-equal
const { webTimingSafeEqual } = require("@advena/web-timing-safe-equal");
const left = "my secret value";
const right = "another secret value";
(async () => {
const isEqual = await webTimingSafeEqual(left, right);
console.log("Are the values equal?", isEqual); // false
})();
Node.JS has a native crypto.timingSafeEqual
function for time constant comparison, which has been available since version 6 (2016).
However, there is no similar function for time safe comparison of values in web or edge environments. Cloudflare Workers runtime, upon which the Next.js Edge runtime is built, recently added the Node.js timingSafeEqual
to their runtime as a non-standard extension. You can find more information in the Cloudflare Workers documentation. This means timingSafeEqual
might be available in the latest Edge runtime.
There is a feature request with the Web3 Consortium to add a similar native function to the WebCrypto API. However, even if it is implemented, it may take years for browsers to support it.
Several alternative libraries, such as scmp and buffer-equal-constant-time, use the "Bitwise comparison with XOR" pattern. While this should work in theory, it may not be effective in practice due to potential time leaks caused by the way bits are implemented in JavaScript. Time-safe comparison with "Bitwise comparison with XOR" in JavaScript is difficult to implement and test, and it cannot be guaranteed to be secure. The ideal solution would be to implement a safe function at a lower level, outside of JavaScript.
In the meantime, we can use the "Double HMAC Verification" pattern as a common alternative for time-constant comparison. This pattern compares two HMACs: hmac(key, left) === hmac(key, right)
. In this case, it doesn't matter if ===
leaks length information, because the HMAC hashes constantly change. Therefore, even if the first byte of hmac(key, left)
and hmac(key, right)
matches, an attacker cannot rely on the assumption that they have correctly guessed it. Moving to the next byte will change the hash, effectively turning the attack into a brute-force attempt instead of a time-based one.
The webTimingSafeEqual
function runs about 110 times slower than timingSafeEqual
method.
If you pre-generate a key or provide your own, then it's only 65 times slower than timingSafeEqual
method.
crypto.timingSafeEqual('a','b') // fastest
(async()=>{
await webTimingSafeEqual('a', 'b'); // 110 times slower
const secretKey = await generateSecretKey();
await webTimingSafeEqual('a', 'b', {secretKey}); // 65 times slower
})()
Asynchronous method
left
: (string | Uint8Array) - First value to compareright
: (string | Uint8Array) - Second value to compareoptions
: (object) - Optional configuration objectsecretKey
: (string | CryptoKey) - Optional HMAC key for comparison (default: auto-generated key)keyLength
: (integer) - Optional length in bytes of the auto-generated key (default: 64)keyAlgorithm
: ("SHA-1" | "SHA-256" | "SHA-384" | "SHA-512") - Optional hash algorithm for the key (default: "SHA-256")hmacAlgorithm
: ("SHA-1" | "SHA-256" | "SHA-384" | "SHA-512") - Optional hash algorithm for the HMAC (default: "SHA-256")
Returns a Promise that resolves to a boolean indicating whether the two values are equal.
Asynchronous method
options
: (object) - Optional configuration objectkeyLength
: (integer) - Length of key in bytes (default: 64)algorithm
: ("SHA-1" | "SHA-256" | "SHA-384" | "SHA-512") - Hash algorithm (default: "SHA-256")
Returns a Promise that resolves to a randomly generated HMAC secret key (CryptoKey).
Asynchronous method
secretKey
: (string | Uint8Array | CryptoKey) - Secret key for HMAC computationmessage
: (string | Uint8Array) - Message to compute the HMAC foralgorithm
: ("SHA-1" | "SHA-256" | "SHA-384" | "SHA-512") - Optional hash algorithm for HMAC (default: "SHA-256")
Returns a Promise that resolves to a hexadecimal digest of the HMAC computation.
This library implements the "Double HMAC verification" pattern to provide a timing-safe comparison of strings. However, it is crucial to understand that the security of your application depends on various factors, and using this library alone does not guarantee your application is secure from timing attacks. Please consult a security expert if you have concerns about the security of your application.
I've built this package for personal use. However, If it should ever gain traction then I would consider adding:
- tests
- CJS/ESM dual build
- rewrite in typescript and provide types
- ...?
Please note that I am not responsible for any errors or issues that may arise from using this package. By using this package, you acknowledge that you are using it at your own risk and that I cannot be held accountable for any problems or damages that may occur as a result of using this v. Please ensure that you have adequate backups and precautions in place before implementing or using this package in any production or critical environments.
I myself have used it on one large Next.js client project and have not seen any issues yet.