Uh oh!
There was an error while loading. Please reload this page.
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.
ChALkeR
commented
Feb 8, 2016
Related issue — #3043. |
bnoordhuis
commented
Feb 8, 2016
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. |
SukharevAndrey
commented
Feb 8, 2016
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. |
ChALkeR
commented
Feb 8, 2016
@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). |
ChALkeR
commented
Feb 8, 2016
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. |
SukharevAndrey
commented
Feb 8, 2016
@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? |
ChALkeR
commented
Feb 8, 2016
@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. |
bnoordhuis
commented
Feb 8, 2016
@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. |
SukharevAndrey
commented
Feb 8, 2016
@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? |
SukharevAndrey
commented
Feb 8, 2016
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 |
bnoordhuis
commented
Feb 8, 2016
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.) |
SukharevAndrey
commented
Feb 8, 2016
@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. |
bnoordhuis
commented
Feb 8, 2016
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:
|
ChALkeR
commented
Feb 8, 2016
@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. |
bnoordhuis
commented
Feb 8, 2016
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. :-) |
brendanashworth
commented
Feb 9, 2016
Also see: #3073. |
ChALkeR
commented
Feb 9, 2016
@brendanashworth Approach in #3073 looks better to me, perhaps. |
jsha
commented
Feb 11, 2016
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
c7066fbComparec133999 to
83c7a88Comparejasnell
commented
Feb 28, 2017
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.