-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
crypto: add secure key comparsion function #5139
Conversation
This commit adds function to compare cryptographic keys (as strings or buffers) without risk of timing attacks. Default comparators return result right after first symbol mismatch which can be measured and used to determine what symbol is wrong. The difference is about 5-6 times if we compare string keys.
Related issue — #3043. |
I've commented on this before but I'll repeat it here: I don't think there is a reliable way to do truly constant time comparisons in node. The underlying representation of a JS string (cons, flat, one byte, two byte, multi-byte) may be such that an attacker can still glean information from timing differences in comparing them. This new API function would thus give a false impression of security. For background, I did a similar PR against node-forward but I ended up abandoning it. Even if you restrict it to buffers and typed arrays only, I'd still not be 100% confident you can't extract timing information through (for example) page faults or differing object shapes. |
I think real timing attacks on small keys are somewhat impossible in real life even with simple comparsion, because keys are usually small (2048 and 4096 bits) and the difference on first symbol mismatch and latest symbol mismatch is hundreds of nanoseconds. On buffers it is even smaller, that is why I am comparing execution time in test on strings, not buffers. New function reduces this difference to just a few nanoseconds. Zero difference is not really nessesary. |
@SukharevAndrey Your implementation at least allows to find out the length of the key. This method should not be used on strings or buffers of unknown lengths, so either it should throw when arguments are of unequal length (to make this a clear indication of an error to the developer, so he or she does not expect that it could be used for something like passwords) or add a third argument to specify a fixed length (but then you should double-check that you don't return false positives). |
Also take a look at https://github.com/hapijs/cryptiles/blob/master/lib/index.js#L48-L68 — but that works only if the known secret is the first argument. |
@ChALkeR I thought key length is a public information that any user can find out, at least in cryptography. When we compare passwords, usually they are stored as hashes (e.g. bcrypted), hash length is fixed and is not a secret information. I can add the third parameter, but does it make any sense? |
@SukharevAndrey Then just throw when their length mismatch, I think. I know that you suppose that this would be used for keys, but someone could think that it's a generic contstant-time comparison method that magically does not depend on either of the lengths. |
@SukharevAndrey I think you underestimate the impact that object shapes have on run-time performance. Apart from that, a big issue with the current implementation is that V8 may optimize the loop into one that exits early. I don't think Crankshaft is smart enough to do that but it wouldn't surprise me if TurboFan is (eventually - it may not be quite there yet.) Using Aside, I appreciate you took the time to write tests but I'm not sure how reliable statistical analysis will be. |
@bnoordhuis I don't know then what to use to measure time. We could disable garbage collector while measuring time as it is done in Python's Instead of writing result to boolean variable we could xor keys like in library @ChALkeR mentioned. But what to do with length mismatches? |
As I can see in Node.JS there is no way to temporary disable GC :(. All other benchmarking libraries don't handle situations like that. What is the chance that GC will start collecting garbage in test? The whole |
That's correct, there isn't.
Non-zero. If you're very careful, you can write the hot path in a way that doesn't do allocations. It's hard to get right, though, and fairly brittle to boot; it may not carry over to another V8 release. Even if you could disable the garbage collector somehow, there is still the optimizer/deoptimizer that can kick in at any time (and that too is not stable across V8 releases.) |
@bnoordhuis Ok then. We can't properly measure execution time in test. The function will not always check all the strings of given length in the exact same amount of time. But none of functions will. And still it doesn't mean that we break security, the function is still safer than simple comparsion of strings or buffers. Delays by GC will affect all calculations and malefactor will not distinguish delays by GC and delays of comparsion. But GC doesn't affect calculations any moment and malefactor can use moments when GC doesn't collect garbage and theoretically use that to find mismatching symbol position. XORing strings is 10 times slower than current implementation. We can add some simple useless calculations (such as variable addition) and use them to produce answer. Then compiler will not optimize it, or optimize it the same way for all strings of given length. |
I think that's a dangerous assumption, long term. Take the code that @ChALkeR linked to; it's vulnerable to at least two compiler optimizations that would nullify the constant-time guarantee:
|
@bnoordhuis What about converting to unpooled Buffers (or TypedArrays) and comparing those in C++ code? This is something that is either done totally wrong, or as potentially wrong as in hapi. |
That's what I did in the node-forward pull request but I wasn't convinced I could make it 100% reliable. If I don't even trust my own code, you can imagine the suspicion with which I view other people's code. :-) |
Also see: #3073. |
@brendanashworth Approach in #3073 looks better to me, perhaps. |
Just to clarify a misconception here: In general, the requirement is to compare HMAC output in a timing-safe way, not public or private asymmetric keys. |
7da4fd4
to
c7066fb
Compare
c133999
to
83c7a88
Compare
Closing. We have a timing safe equal method now. |
This commit adds function to compare cryptographic keys (as strings or buffers) without risk of timing attacks. Default comparators return result right after first symbol mismatch which can be measured and used to determine what symbol is wrong. The difference is about 5-6 times if we compare string keys of usually used lengths (2048 bits) and even higher on longer keys.