Side channel methods rely on indirect data such as timing, sound, power consumption, and cache behavior to infer secret data on a system. These types of vulnerabilities may get missed because they are outside of defenders’ system threat models.
Traditional targets of side channel methods include secret cryptographic keys and passwords. Side channel resistance requires significant attention to detail, so it is often best to use libraries that have undergone comprehensive security review and testing whenever possible.
Developers can employ a number of security best practices that affect their applications’ resistance to side channel methods. Some of these best practices directly avoid side channel vulnerabilities, some involve using existing code that is side channel resistant, and some of these best practices mitigate side channel methods to provide systems with additional defense in depth.
When choosing third party libraries, it’s important to recognize that these libraries can and will impact your security. Developers may wish to read up on how to do a simple security risk assessment for open source software. While this article is specific to open-source, many of the same principles apply for any software.
In particular, care must be used when choosing libraries that handle secrets, for example, cryptographic libraries and authentication libraries. We recommend that most developers utilize these existing tools for side channel resistant programming rather than attempt to implement these complex methods independently. Do not write your own cryptographic libraries—this is generally best left to professional cryptographers, as naive implementations (such as those based on textbook representations of the algorithms) are often very vulnerable to side channel attacks. Make sure that the library explicitly indicates that it includes protections for side channel methods.
Be aware that libraries designed for resource-constrained devices may not include the types of protections you would want on larger systems. If you need a smaller footprint, it is better to remove unused algorithms. OpenSSL, for example, has the ability to choose a set of algorithms at compile time.
If you are interested in reading more about what it takes to write a side channel resistant cryptographic library, see Guidelines for Mitigating Timing Attacks Against Cryptographic Implementations.
Software is constantly changing, and the industry finds new security issues regularly. For many malicious actors, finding a way into a system is as simple as reading the lists of known vulnerabilities for known system components or the operating system and finding a vulnerability to use. To avoid this, developers must be vigilant about watching their whole systems for known vulnerabilities, and keep software up to date.
This includes, but is not limited to the following software components:
When choosing the most secure version of a piece of software, there are typically two options:
For many projects, the latest stable version is the correct option as there may be no long term support available for older versions. We do not recommend that you backport fixes and provide your own long-term support for an older version unless absolutely necessary. Doing so is both more dangerous (new bugs could be introduced by the backport) and represents significant engineering effort to maintain and validate any backported code.
Many pieces of software come with best practice guides that include advice for security-related options, as well as for secure deployment. While best practice is to make default deployment secure, sometimes your application or deployment methods require specific configuration, or you might want to enable or disable additional options based on your specific needs. Some actions, such as setting up appropriate certificates, can only be done at deployment time. Make sure you read and follow the instructions in any guides provided.
In following the “principle of least privilege” it is possible to create architectures that separate secret data and user data. For example, data could be handled in different processes, containers, or virtual machines. This can help with some types of speculative execution side channel attacks (for example, bounds check bypass and branch target injection) that attempt to reveal data in the victim's address space. As another example, sensitive data can be moved to a different physical core, or even a different socket than potentially malicious actors in order to reduce the resources shared between the sensitive data and potential attackers. Doing this is very dependent on your specific application, so ideally you should work with security professionals to review the architecture.
Error responses that contain detailed internal information or are too specific about failures in intermediate steps of security checks are often used as side channels to aid attack strategies. As a simple example, a network interface that returns an Unknown User or Invalid Password error when verifying passwords instead of a more general Login Failed error allows a malicious actor to enumerate the set of registered users by repeatedly asking to login with different user names instead of forcing a much more intensive search using both username and password values. Daniel Bleichenbacher showed a more complicated example in his paper, Chosen Ciphertext Attacks Against Protocols Based on the RSA Encryption Standard PKCS #1, where detailed error codes are used to guide chosen ciphertext attacks on SSL.
When writing code that processes secret data, it’s important to consider whether the time it takes to process a request can provide hints about the data. One scenario to consider is whether certain error conditions or processing choices have discernable timing differences. A few common ways this can occur are detailed here, although more can be found in documents providing guidance to developers implementing secure cryptographic functions. Refer to Guidelines for Mitigating Timing Attacks Against Cryptographic Implementations. Often, the easiest mitigation is to use well-tested and well-constructed libraries that are designed to handle secret data.
Data-driven applications must take care to protect information that malicious actors could infer based on properties of the full data set. For example, a query to a medical database that is supposed to hide gender information must ensure that processing time is not statistically different regardless of whether the gender is listed as male, female, non-binary, and so on, even though the database may contain significantly more people with one gender than all others.
This practice can be counterintuitive for programmers who are used to optimizing for performance time or CPU usage. Operations such as comparisons for password processing are especially sensitive to timing attacks. These vulnerabilities, also known as oracle attacks, can even target processes that are not vulnerable to speculative execution side channel attacks.
For example, a programmer might choose to add a length check to a password field to save on executing a more computationally expensive string comparison. But doing so would cause any password entry that is not exactly the same length as the real password to return a login failed more quickly, thereby allowing a malicious actor to learn the exact length of the string they need to find. This could potentially eliminate more than half of the possible target password search set!
Even without a length check, the string comparison itself may be faster for inputs that are similar to the target input. The solution is to use a constant-time comparison, as well as always maintaining inputs and secret data in known-length buffers. The best practice is to use a library that already uses constant-time functions for checking passwords and other security-sensitive functions. See the Use Well-Maintained and Reputable Libraries section for more details.
Many compilers with optimizers will look for ways to make the code faster, and sometimes the changes they introduce are quite unexpected. For any code that you write—whether C or hand-coded assembly—you must verify constant-time operation in the environment where you expect to use it.
In addition, some languages do not have good support—or any support—for providing code generation hints to the compiler or just-in-time (JIT) compiler for managed and dynamic languages. When considering constant-time coding principles in these environments, it’s important to be extra vigilant about what is actually executing on the processor.
The complexity and careful attention required to get this right is another reason that developers should make use of a library that has already taken these considerations into account. See the Use Well-Maintained and Reputable Libraries section for more details.
In addition to using reputable libraries, keeping all software and dependencies up to date, and following all the other best practices described above that are focused on side channels, system security is dependent on implementing good general security practices. Remember that malicious actors often do not care about the type of attack they use to compromise a system, only that a viable attack can be found. Good practices include applying the principle of least privilege to all sensitive resources and isolating software components into functional components with well-defined interfaces to communicate between components.
For more information on secure coding, several developer manuals and programming books contain detailed descriptions and implementations of other good general security practices. Some useful examples include:
When prioritizing your engineering effort, you may also wish to examine some of the lists of most dangerous and commonly exploited issues, such as: