Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: add caveats and example to crypto.timingSafeEqual #41837

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -5335,6 +5335,58 @@ Use of `crypto.timingSafeEqual` does not guarantee that the _surrounding_ code
is timing-safe. Care should be taken to ensure that the surrounding code does
not introduce timing vulnerabilities.

Particular care must be taken if `crypto.timingSafeEqual` is used in scenarios
where one or both arguments may vary in length, as its algorithm is only
constant-time with respect to a byte length that is consistent. For example, if
you are comparing a known secret to an input of unknown length, do not truncate
whichever of the two happens to be longer to match the length of the shorter
one. Doing so may seem more efficient, but it can leak timing information. In
this case, you might instead choose to _always_ size `a` and `b` to the length
of the input, regardless of whether it's longer or shorter than the secret.
This would mean that `crypto.timingSafeEqual` might not always execute in the
same amount of time across calls, but its variance will be strictly dependent
on the attacker's input rather than on the length of your secret.

In the above scenario you'll also need to check that the original input is of
the same length as the secret, but this should be done _in addition_ to calling
`crypto.timingSafeEqual`, not as a precondition to it. If your code compares
the lengths of secret and input and rejects an input of different length
without calling `crypto.timingSafeEqual`, you may leak timing information.

```mjs
import { Buffer } from 'buffer';
const { timingSafeEqual } = await import('crypto');

function timingSafeStringCompare(input, secret) {
const inputBuffer = Buffer.from(input);
const secretBuffer = Buffer.alloc(inputBuffer.length);
secretBuffer.write(secret);

return timingSafeEqual(inputBuffer, secretBuffer) &&
inputBuffer.length === Buffer.byteLength(secret);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, operations such as Buffer.byteLength(secret) are generally not timing-safe.

}

// Perform a timing-safe comparison of two utf-8 strings.
console.log(timingSafeStringCompare('evil_hacker', 'verysecret'));
```

```cjs
const { Buffer } = require('buffer');
const { timingSafeEqual } = require('crypto');

function timingSafeStringCompare(input, secret) {
const inputBuffer = Buffer.from(input);
const secretBuffer = Buffer.alloc(inputBuffer.length);
secretBuffer.write(secret);

return timingSafeEqual(inputBuffer, secretBuffer) &&
inputBuffer.length === Buffer.byteLength(secret);
}

// Perform a timing-safe comparison of two utf-8 strings.
console.log(timingSafeStringCompare('evil_hacker', 'verysecret'));
```

### `crypto.verify(algorithm, data, key, signature[, callback])`

<!-- YAML
Expand Down