I question the general utility of rule one, "compare secret strings in constant time", for many developers.
"Constant time" is not a recognized semantic of most programming languages and depends on the whims of compliers, VMs, runtime optimization, etc. Sample code in C that works (on today's compilers) for high performance crypto libraries is good to have, but what about Java, .NET, ECMAScript, etc.?
Because timing attacks against string comparisons generally rely on iterated, adaptive guessing, a rule I prefer for most applications is: "Do not compare secret strings directly, instead compare a MAC of each string." Applying a keyed PRF like HMAC to the strings before comparison removes the ability of the attacker to make adaptive guesses because every bit they flip in the input produces a 50% likelihood of flipping any bit in the output, and the secret key means they cannot predict it.
This has the benefit of deriving its security from the mathematical properties of the cryptography itself, rather than the vagaries of compiler and language implementations. I believe it is also easier to explain and less error-prone for inexperienced developers to implement. e.g. "Before comparing your HMAC outputs, HMAC them a second time."
Brad Hill [email protected]
- Applying a MAC on each string seems overkill, and what key would you use? The C snippet is straighforward to translate in most languages, and will remain constant-time. Jpa (talk) 09:39, 20 February 2013 (CET)
- Overkill perhaps, but what if you don't have any way to guarantee constant time in your language? You can use any key. This bug most often comes up when verifying HMACs as few crypto libraries provide an API-wrapped verify function, leaving verification as an exercise for the user. As a consequence it's probably the single piece of crypto code most commonly written, written mostly by amateurs, and always vulnerable. Simply re-applying the HMAC before compare is pretty lightweight - just one more round with data structures already in memory, the key already loaded, and the input already at the block size of the hash function. More importantly, the cognitive load on the developer is very low: they already know how to write the HMAC calculation call because they just did it. -Brad
Split the randomness part in two
There are two separate randomness related issues:
- Using a secure PRNG
- Avoiding dependence on the PRNG (e.g. deterministic DSA)
These should be separate points in the list.
Dubious statement in Clean memory of secret data section
- > On most operating systems memory owned by one process can be reused by another without being cleared,
- > either because the first process terminates or it gives back the memory to the system. If the memory contains
- > secret keys these will be available to the second process, which increases the exposure of the key.
I doubt that this statement is true. I believe most desktop OSes will zero the memory before handing it to a new process. Everything else would be a severe security hole in that OS. Might be different on embedded systems, I'm not familiar with those.
Wiping memory is about forward secrecy, crash dumps and swap files not about other processes learning what your memory contains when it's reused.
Dangerous and incorrect advice on how to use the volatile qualifier to guarantee that memory is zeroed
This standard currently has dangerous and incorrect advice on how to use the volatile qualifier to guarantee that memory is zeroed. In general, the compiler and the hardware are free at any time to make hidden copies of your data (for example, other cores on the CPU are free to cache stale copies of the data from before it was zeroed by your cunning memset_s implementation and also the CPU is free to cache the zeroing writes of the data in a store buffer before actually clearing the data in RAM or other core caches).
Examples of constant-time tests and comparisons for 32-bit values
The section "Examples of constant-time tests and comparisons for 32-bit values" contains some issues.
1. Is there any point in repeating the functions for the "unsigned" and the "signed" comparisons?
The code is identical except "u32" is replaced by "s32". The parameters for the "signed" comparisons are unsigned `uint32_t` anyway. This may be deliberate on the writer's part to force signed integers into unsigned, but should someone decide to change these types to the seemingly more logical signed `int32_t` type -- perhaps because their compiler warns about a signed/unsigned problem -- they will expose themselves to the very real danger that all these fancy bit-shifting techniques are not guaranteed to work with signed integers.
2. The very first example `ct_isnonzero_u32()` will not compile without warnings because `-x` is strictly not a valid operation on an unsigned integer.
could be replaced by
or, even better, by
(x|(~x+1))>>(sizeof(x)*8 - 1)
This assumes that you are confident that all computers your code will run on use twos-complement arithmetic -- a reasonable assumption *at the moment* but maybe not in a few years time. Most of these fancy bit-shifting techniques are exposed to a similar risk.
This is not to detract from the main point of the section that you should avoid `memcmp()` in favour of comparisons that always compare all the elements of an array regardless of finding a mismatch partway through.