2

In general timing attacks are a manifestation of the implementation. So if I am processing data on the server or the client then I can leak information depending on how the code is written.

To prevent such vulnerabilities, as much as possible, I need to make sure I always process the same amount of data whether I accept the request or return an error.

Then I can also pick crypto algorithms that are designed to be resistant against such attacks like the EdDSA or Chacha.

However, is there something I need to/can I do when I am creating the protocol itself? Something in bytes sent back and forth that can either help in my implementation when processing it or in general that attackers can use in a side channel? Are there papers/studies about that? I couldn't find any.

2

Avoiding side-channel attacks in protocols (or in general) requires just a few things. Unfortunately, it's often easy to slip up, as it is not always intuitive. The general way to avoid side-channel attacks is to ensure that any behavior that can be monitored by the attacker is not in any way dependent on any secrets which the attacker must not be able to access. A simple example is string comparison. How long a task executes is behavior available to the attacker. With a simple, "unsafe" string comparison function, the time it takes to compare the string is dependent on the number of matching characters.

In order for this question to be answerable, you have to define where your attacker is. Are they on the client, running an untrusted process next to one handling sensitive data? Are they on the server? Are they simply monitoring the traffic going back and forth between the client and server? Knowing this will allow for a more precise answer. Since there are a large number of papers detailing side-channel attacks, knowing your precise threat model is even more important for suggesting papers to read.

On the computer itself

If the attacker is listening on one of the computers handling sensitive data, avoiding side-channel attacks is harder. You must ensure that memory accesses and computations on secret material occur without regards to the value of the secret. This means avoiding certain common optimization techniques that take "shortcuts", skipping unnecessary operations. Unfortunately, doing this correctly often requires an understanding of the underlying CPU architecture. Sometimes, under specific and obscure circumstances, the same instruction may take longer based on the contents of the secrets. An example is any instruction accessing memory. The speed at which it accesses memory depends on whether or not the data being accessed is in the cache, opening up the possibility of cache-timing attacks (such as Flush+Reload).

Avoiding side-channel attacks that exploit the behavior of the cache requires both an understanding of the algorithm being processed itself, as well as the architecture the algorithm is running as. One of the most-studied examples is AES, where a common optimization stores S-boxes and round keys in the cache. This is very fast, but vulnerable. The solution is to implement it using bit-slicing.

Over the network

If your attacker is simply monitoring the network, the solution is much easier. Generally, unless the network is exceptionally low latency (a very, very fast local LAN), the timing required to perform side-channel attacks is often lost in the noise on any regular network. An attacker very close to the server (its own router, a router very near it) may be able to obtain more precise timing information. This means that, in general, timing attacks over the network are not a problem, but it doesn't hurt to avoid the risk anyway. This can be done in the same way as if the attacker is local. Simply ensure any sensitive computations are constant-time. String comparisons should take the same amount of time regardless of whether or not all the characters match, some of the characters match, or none of the characters match.

A common example

As mentioned a few times before, one of the most common mistakes people make is to use optimized string comparison functions on secret strings.

An unsafe string comparison function. It will immediately return an error as soon as the first non-matching character is found. Timing how long the function takes to return false will allow an attacker to brute-force the password one character at a time. It is suitable only in situations where the attacker cannot determine how long it takes for the function to return.

bool password_check(char *s1, char *s2, int len)
{
    while (len--)
        if (s1[len] != s2[len]) return false;

    return true;
}

A fairly safe string comparison function, suitable for use in high-latency networks where nanosecond-level timing information is not available to the attacker. It checks the whole string and keeps going even if it knows the strings do not match. It will take a constant amount of time to check if the passwords match.

bool password_check(char *s1, char *s2, int len)
{
    bool mismatch = false;

    while (len--)
        if (s1[len] != s2[len]) mismatch = true;

    return !mismatch;
}

Note that this is constant-time in theory, but an attacker able to do extremely precise monitoring may still be able to gain some information from it. For example, setting mismatch to true involves, at the least, writing 0x1 to a register. Setting it to false on the other hand is often optimized by XORing a register with itself, which sets it to 0x0. Whether or not this is done depends on the compiler, so a highly sensitive string checking implementation should be done in assembly, with a good knowledge of CPU optimizations. If it must be done in a higher level language like C, you must understand how it will be compiled into assembly and design it so that optimizations will not result in a side-channel attack.

A very safe string comparison function. It uses bitmasks to determine whether the strings match. It is suitable for use when an attacker can precisely time how long it takes the function to return.

bool password_check(char *s1, char *s2, int len)
{
    char mismatch = NULL;

    while (len--)
        mismatch |= s1[len] ^ s2[len];

    return !mismatch;
}

Another common way to avoid side-channel attacks when comparing strings is to hash them, and compare the resulting digests. A hash function should take the same amount of time for input of a given size regardless of the contents of the input. The output will be of fixed size, and can be safely compared with an "unsafe" comparison function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.