What's the difference between a secure compare and a simple ==(=)
Asked Answered
C

4

30

Github's securing webhooks page says:

Using a plain == operator is not advised. A method like secure_compare performs a “constant time” string comparison, which renders it safe from certain timing attacks against regular equality operators.

I use bcrypt.compare('string', 'computed hash') when comparing passwords.

What makes this a "secure compare" and can I do this using the standard crypto library in Node?

Commons answered 28/6, 2015 at 3:36 Comment(2)
I rolled back the edit that added the ruby tag as it appears the OP is not using Ruby, and the linked page uses Ruby for examples but is discussing language-agnostic concepts.Armistice
@WallyAltman actually, I am specifically asking if there's a Nodejs way to perform a secure compare without a third-party module. But you're right, the spirit of the question is language-agnostic.Commons
A
56

The point of a "constant time" string comparison is that the comparison will take the exact same amount of time no matter what the comparison target is (the unknown value). This "constant time" reveals no information to an attacker about what the unknown target value might be. The usual solution is that all characters are compared, even after a mismatch is found so no matter where a mismatch is found, the comparison runs in the same amount of time.

Other forms of comparison might return an answer in a shorter time when certain conditions are true which allows an attacker to learn what they might be missing. For example, in a typical string comparison, the comparison will return false as soon as an unequal character is found. If the first character does not match, then the comparison will return in a shorter amount of time than if it does. A diligent attacker can use this information to make a smarter brute force attack.

A "constant time" comparison eliminates this extra information because no matter how the two strings are unequal, the function will return its value in the same amount of time.

In looking at the nodejs v4 crypto library, I don't see any signs of a function to do constant time comparison and per this post, there is a discussion about the fact that the nodejs crypto library is missing this functionality.

EDIT: Node v6 now has crypto.timingSafeEqual(a, b).

There is also such a constant time comparison function available in this buffer-equal-constant-time module.

Abbyabbye answered 28/6, 2015 at 4:48 Comment(6)
Node.js has a crypto.timingSafeEqual method added in: v6.6.0 - nodejs.org/dist/latest-v6.x/docs/api/…Outstation
@SaugatAcharya - Thx, I added that to my answer.Abbyabbye
Full example comparing strings a and b (strings can't be passed to timingSafeEqual): const isEqual = crypto.timingSafeEqual( Buffer.from(a), Buffer.from(b) );Teucer
Take care here: timingSafeEqual() will throw if a and b's byte lengths differ.Backed
@Backed - Yes, as the doc for timingSafeEqual() says: "a and b must both be Buffers, TypedArrays, or DataViews, and they must have the same byte length.".Abbyabbye
Node really screwed the pooch with timingSafeEqual. I just want a timing-safe function to compare two strings and return true or false, but instead they give us this thing that throws if the strings aren't equal in the most basic way and requires "ArrayBufferViews." I'm not a fan of rolling my own crypto functions but I think I'll make an exception for this one caseSgraffito
T
8

jfriend's answer is correct in general, but in terms of this specific context (comparing the output of a bcrypt operation with what is stored in the database), there is no risk with using "==".

Remember, bcrypt is designed to be a one-way function that is specifically built to resist password guessing attacks when the attacker gets hold of the database. If we assume that the attacker has the database, then the attacker does not need timing leak information to know which byte of his guess for the password is wrong: he can check that himself by simply looking at the database. If we assume the attacker does not have the database, then timing leak information could potentially tell us which byte was wrong in his guess in a scenario that is ideal for the attacker (not realistic at all). Even if he could get that information, the one-way property of bcrypt prevents him from exploiting the knowledge gain.

Summary: preventing timing attacks is a good idea in general, but in this specific context, you're not putting yourself in any danger by using "==".

EDIT: The bcrypt.compare( ) function already is programmed to resist timing attacks even though there is absolutely no security risk in not doing this.

Timotheus answered 28/6, 2015 at 22:22 Comment(2)
Why would you say GitHub would call this out and bold their suggestion if === carries no security risk in this case? developer.github.com/webhooks/securingTeucer
@LukeWilliams comparing a HMAC signature is a different context than comparing a bcrypt hash output. As a security professional, I generally advise you to use the secure-by-default option: programmers are not cryptography experts and cannot understand the subtleties of every possible use case. OTOH I am a cryptographer and my point was in the context of a bcrypt hash comparison, the less secure option is not exploitable, and that comment was targeted at really smart people who seek a deeper understanding.Timotheus
W
4

Imagine a long block of material to compare. If the first block does not match and the compare function returns right then, you have leaked data to the attacker. He can work on the first block of data until the routine takes longer to return, at which time he will know that the first chunk matched.

2 ways to compare data that are more secure from timing attacks are to hash both sets of data and to compare the hashes, or to XOR all the data and compare the result to 0. If == just scans both blocks of data and returns if and when it finds a discrepancy, it can inadvertently play "warmer / colder" and guide the adversary right in on the secret text he wants to match.

Wobbly answered 3/8, 2015 at 9:53 Comment(0)
S
0

The difference is that a secure comparison between a secret and a test string does not reveal information about the secret in the time it takes to run. For instance, suppose the secret was aaaaaaaaab and you compare with the test strings b, bbbbbbbbbb and aaaaaaaaaa. A classical comparison yields false immediately for b because the lengths do not match. For bbbbbbbbbb is false almost immediately because the first character does not match. For aaaaaaaaaa is also false, but not so immediately (takes more microseconds or nanoseconds). So, the longer it takes, the closer your test string is to the secret. An attacker can for instance try to guess the length by sending many tests of different lengths and finding the smallest. Then, he can proceed to find the first character in a similar fashion, and so on, until finding out the secret.

To avoid this, as recommended by @WDS, you can do a simple for loop instead:

// (Javascript)
function equals(test, secret) {
  // String comparison that does not leak timing information
  let diffs = test.length !== secret.length ? 1 : 0;
  for (let i = 0; i < test.length; i++) if (test[i] !== secret[i]) diffs++;
  return diffs == 0;
}

The accepted answer points to Node's timingSafeEqual(a, b) which is mostly designed for hashed inputs since, as said in the comments, it expects the inputs to have the same lengths. Using this function without hashing can lead you to troubles since you can not just return false when the input lengths mismatch because this would leak information about the length of the secret.

Stavanger answered 16/10, 2023 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.