1
\$\begingroup\$

Here's a JavaScript function that check the equality of input and secret strings, trying to do that without leaking information about secret:

function timeSafeEqual(input, secret) {
 let equal = input.length === secret.length
 for (let i = 0; i < input.length; ++i) {
 const ofInput = input[i]
 const ofSecret = secret[i]
 if (ofInput !== ofSecret) {
 equal = false
 }
 }
 return equal
}

The function seems to always run in O(input.length) time. Would you consider it to be timing-safe? This code runs on Node.js

Maybhe the time to compare undefined string values might be different than the time to compare string and string in V8?

asked Nov 16, 2023 at 12:18
\$\endgroup\$
5
  • \$\begingroup\$ So if input has a length of 10 and secret has a length of 5, what will secret[5] return? Honest question, I don't know TypeScript too well and other languages will throw an exception for out of bounds access, likely giving away the actual length of the secret. \$\endgroup\$ Commented Nov 16, 2023 at 14:15
  • 1
    \$\begingroup\$ @Marvin In JavaScript it will return undefined \$\endgroup\$ Commented Nov 16, 2023 at 14:32
  • \$\begingroup\$ Where are you running this Javascript (previously Typescript) code? \$\endgroup\$ Commented Nov 16, 2023 at 14:35
  • \$\begingroup\$ @KIKOSoftware The code runs on Node.js. I've edited the question \$\endgroup\$ Commented Nov 16, 2023 at 14:43
  • 2
    \$\begingroup\$ You've got a clear question, and the environment to test it. Did you do that? To prevent timing attacks you could simply add a random short delay in this function. \$\endgroup\$ Commented Nov 16, 2023 at 14:54

1 Answer 1

1
\$\begingroup\$

Is it safe?

No.


bug

 let equal = input.length === secret.length

In the happy path we assign True and life is good.

When we assign False, then de-referencing input[i] and secret[i] is trouble. Minimally, the attempted de-reference will consume a perceptibly different amount of delay. If we get an undefined back, then XORing it will be trouble.


Assume we assigned equal = True above.

timing of a conditional

 if (ofInput !== ofSecret) {
 equal = false
 }

This is just Bad.

We perform N comparisons, so that much is fine, there's no early exit.

But for M out of N mis-matches, we're performing an extra M assignments which an adversary armed with a stopwatch can observe. Total failure.

fix

Compute and store N xor's. In the case that the strings are equal, we store N zeros.

Sum the xor results and return that. Non-zero means the strings don't match.

Consider hashing both strings initially, then compare the hashes. One advantage is you're guaranteed to be comparing hashes of identical length. Another is that you have flexibility to pre-hash on a different host or at a previous time.


random jitter

Your adversary's ability to observe what's happening is impacted by random noisy delays caused by router queues and host scheduler queues.

Roll a random number and sleep that long before returning a boolean result. That way the adversary has less effective signalling bandwidth for his observations, so effective attacks will take much longer.

answered Nov 16, 2023 at 19:15
\$\endgroup\$
1
  • \$\begingroup\$ Indeed, all great points, thank you! I ended up using this algorithm: 1. Store secret in memory buffer. 2. Create badSecret buffer by flipping all bits of secret. 3. Set bufferToCompare = input.length === secret.length ? input : badSecret. 4. Xor bufferToCompare and secret, sum, and check if result is 0 \$\endgroup\$ Commented Nov 16, 2023 at 23:20

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.