Side channel attacks rely on indirect data such as timing, sound, power consumption, electromagnetic emissions, vibrations, and cache behavior in an effort to infer secret data on a system. The complexity of certain such attack methods and the number of different channels from which secret data could be inferred may cause defenders to overlook potential incidental channels when developing system threat models. While not all incidental channels pose security risks in all environments, prioritizing safe development practices can help you identify potential vulnerabilities and work efficiently to minimize your systems’ exposure to these methods. The most common targets of side channel attacks include secret cryptographic keys and passwords. Side channel resistance requires significant attention to detail. Whenever possible, it is best to use actively maintained libraries that have undergone comprehensive security review and testing. It is also important to consider isolation between secrets and potential attackers to help mitigate transient execution side channels.
This article provides a set of basic advice1 for developers and system administrators to help provide a solid foundation to maximize an application or system’s resistance to side channels. The most important recommendations are to keep your system components up-to-date and to understand your system’s threat model2. Maintainers of security-critical libraries and operating system components are continuously making updates to counter new threats. Keeping components up-to-date can help prevent vulnerabilities from persisting. It is also important to keep in mind simple security principles such as least privilege, defense in depth, and limiting exposure of error details.
Developers can employ a number of security best practices to help protect against various types of side channel attacks and provide defense in depth. Some of these best practices include using the latest software version, not sharing resources when sharing can be avoided, limiting error messages, writing constant timing code, and being aware of compiler optimizations. To prioritize side channel defense, one should consider the threat model of one’s applications.
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 like, for example, cryptographic libraries and authentication libraries. We recommend that developers use 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 vulnerable to side channel attacks. Make sure that your library of choice explicitly indicates that it includes protections for side channel attacks. Moreover, it is wise to choose a library that is updated regularly and recently to add fixes for any new vulnerabilities.
Be aware that libraries designed for resource-constrained devices, like FPGAs or microcontrollers, may not include the types of protections needed for multi-user systems that run arbitrary applications. 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.
To help protect the overall security of the system, software developers should carefully choose the third-party libraries they include in their software stack, and keep these libraries up-to-date. Vulnerabilities are discovered in even the most reputable libraries, so keeping up with updates and mitigations is just as important as the initial selection. If not tightly written, an implementation can be vulnerable to known side channel attacks. For instance, the well-respected OpenSSL cryptographic library has been patched time after time to mitigate newly discovered side channels. It is also important to realize which side channels are addressed by the implementation.
Another factor to consider when choosing a third-party library is the stability of the API and using calls that are officially supported by the vendor. For example, among the many OpenSSL versions, only OpenSSL v1.1.1 is officially supported by the vendor.
In short, to help protect against known and future side channel attacks, software developers should use reputable libraries and plan for periodic security updates in their own code. With this goal, your software can be designed from ground up with a security-first mindset and with the ability to update its third-party dependencies without breaking functionality.
The two security principles of least privilege, where software is granted only what it needs to function and no more, and defense in depth, where multiple mitigation strategies are applied at once to help lessen the impact of a successful exploit, are often applied to the architecture of secure systems. Domain isolation, where different parts of a full system are partially or completely “walled off” from one another, is a technique that can be applied to achieve one or both of these principles. Processing sensitive data in a separate security domain—for example, a separate physical machine, virtual machine, or core—can increase the difficulty for a malicious user trying to gain insights on that data by limiting the number of shared resources that may be observed.
While secure domain isolation may help with specific side channels, such as resource contention, it is not a panacea that protects against all possible side channels. For example, even systems with good domain isolation could be vulnerable to timing attacks under certain circumstances. Not all domain isolation technologies have the same properties, and the specific run-time configuration can change security properties, so best practices and expected threat models may need to be documented to help ensure that deployed systems have the expected properties.
Error responses that contain detailed internal information or that are too specific about failures in intermediate steps of security checks may be 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 could enable a malicious actor’s attempt 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.
Software developers should carefully consider the error information that is available to end-users from both user software and any third-party libraries, apps, or services included in the software stack, and should return messages that are only actionable by users.
When writing code that processes secret data, it’s important to consider whether the time it takes to process a request or the shared resources used—for example, caches, processor pipelines—can provide hints about the contents of the data. The most common, and complex, example of where this matters is implementation of cryptography. Refer to Guidelines for Mitigating Timing Attacks Against Cryptographic Implementations for detailed guidance if you are implementing cryptographic functions. Most applications using cryptography should not implement the algorithm themselves and should instead use well-tested and well-constructed libraries that are designed to handle secret data with side channels in mind.
Aside from cryptographic implementations, there are other considerations for data-driven applications. The following sections describe a few areas to consider when designing a data-driven application.
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. The time to process the request could become an indirect indication of gender if care is not taken to eliminate timing differences.
This practice can be counterintuitive for programmers who are accustomed to optimizing for performance or hardware resource cost. However, keeping your program’s execution flow—and memory access sequences where possible—consistent regardless of secret inputs is necessary to safeguard against side channel attacks. While any input-dependent execution can leak information, techniques such as early exits based on processing user input with sensitive data and comparisons of user input with secrets—such as password comparison—are especially sensitive to timing attacks.
To avoid information being disclosed based on timing, it is important that all processing that occurs during a successful execution of an operation with sensitive data takes place regardless of any errors found during processing. Code should be structured such that all user-controlled inputs are validated first and errors are returned before processing secret data begins3. During processing involving secret data, ensure that all steps are processed, and then check that all steps completed successfully at the end, returning only generic pass or fail errors. Exiting early with an error during secret data processing could permit information about the data to be inferred. Aside from preventing potential information compromise, structuring code in this manner makes it easier to maintain since there is a clear separation of non-secret and secret processing.
When making comparisons to sensitive data, it is important that every operation takes the same amount of time regardless of length or content. For example, standard string comparison exits with a false result as soon as a length or content mismatch is found. While the timing difference may be insignificant compared to the overall operation, it is statistically observable and could reveal information about the secret data’s length or allow a malicious actor to infer the content using multiple samples. Programmers should use a data-oblivious comparison function in this scenario as described below.
For security-sensitive operations, constant execution flow is strictly required, but alone is not enough to prevent side channel attacks. Even if the secret data is processed with constant execution flow, if memory access patterns vary in any way based on the secret, a malicious actor might infer sensitive information about the secret. Memory access variance based on secret data has been used to infer the value of RSA and ECDSA private keys that use multiple different textbook mathematical implementations. It has also been used to infer AES secret keys based on byte-level table lookups indexed by portions of the AES secret key. Attackers can use a number of well-known techniques to detect changes in memory access patterns.
If you are implementing security-sensitive operations yourself, make sure to understand Guidelines for Mitigating Timing Attacks Against Cryptographic Implementations. It details techniques for implementing data oblivious code. Most programmers should take advantage of data oblivious implementations created by experts. See the Use Well-Maintained and Reputable Libraries section for more details.
Many operations, such as comparisons to secret data and manipulations of secret data—for example, mathematical operations—can create a side channel opportunity if they aren’t properly implemented. Simple performance optimization such as length checks, special cases for zero values, or inconsistent order of operations can be observed via timing or via interactions with shared resources like caches. A solution is to use implementations that adhere to the principles in Guidelines for Mitigating Timing Attacks Against Cryptographic Implementations. For all but the most specialized developers, the best practice is to use libraries that already use 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 these optimizations introduce are quite unexpected. For instance, code that appears to follow structures described above could have early escapes automatically inserted by the compiler’s optimizer. Regardless of whether your code is compiled C/C++, managed or dynamic using a just-in-time (JIT) compiler, or hand-coded assembly, it is extremely important that the constant execution operations be verified in the environment where it is expected to run.
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.
System administrators can employ a number of security best practices to help protect against side channel attacks and provide defense in depth. These include installing the latest software version, providing software or hardware isolation when possible, and being conscious of information provided in logging and error messages. To prioritize side channel defense, one should consider the threat model of one’s applications.
Software is constantly changing, and the industry finds new security issues regularly. For many malicious actors, finding a way into a system could be as simple as reading the lists of known vulnerabilities for installed applications on a system and finding a vulnerability to exploit. As a general security practice, software developers and system administrators should vigilantly watch their entire system for known vulnerabilities, and keep software up-to-date to help protect against side channel attacks and other vulnerabilities. This also means that systems need to implement some update mechanism.
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 older versions of software 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.
System administrators have great control over the hardware and software resources that software developers can access. Moreover, configuration of hypervisors and OSes plays a crucial role in helping secure the other software in the system. A bug in the hypervisor can allow cross-VM side channel attacks or VM escapes. To reduce the probability that a novel side channel attack turns a previously benign shared resource into an attack vector, take appropriate steps to ensure that proper access policies are in place. To help prevent such attacks, it is important to use reputable, side channel-secure libraries and update them frequently, including their recursive dependencies.
With side channel security in mind, there are multiple ways that system administrators can help to prevent potential attacks before they even begin. The first and foremost method is limiting the use of shared hardware and software resources between security domains. For instance, allocating two different virtual machines to run on the same CPU core, even if they run on different threads, can potentially increase the risk of malicious actors mounting various side channel attacks on the system.
For example, cache side channel attacks exploit the secret dependent execution and data access flows in software to infer information from the CPU caches. However, when the processes are isolated to separate cores, L1 and L2 cache attacks are inherently mitigated as they are exclusive to CPU cores. So even if the software is leaking side channel information, the hardware isolation helps protect the users from side channel adversaries.
In general, system administrators should deploy appropriate isolation techniques including containerization, VM isolation, etc. to minimize shared resources and increase defense in depth according to their threat models. Defense in depth applied to your deployments will help mitigate known threats as well as help limit the impact of new threats that may arise in the future.
Limiting the information shared on debug, runtime logs, and error messages plays an important role in helping to secure against side channel attacks. System administrators should carefully consider what information to share about the hardware, software, and third party dependencies running on the system, and should limit the exposed error and debug information accordingly. Low-level crash logs and debug information of the underlying hardware can leak side channel information and expose the software running on the system.
On the hardware side, in an Infrastructure as a Service (IAAS) or a Platform as a Service (PAAS) cloud, detailed hardware errors or faults are useless to the guest VM since the guest OS has no role or control over the maintenance and the upkeep of the hardware. However, these hardware error and debug messages can possibly create attack surfaces for side channel attacks, potentially revealing usage patterns of other guests running on the system through a variety of channels like power consumption, performance metrics, etc. Therefore, such data should be either filtered for each guest or limited to security-insensitive data.
On the software side, for instance in the .net deployment or web environment, when there is a crash, one should not give the information to an external interface but instead should log the memory dump or the stack trace to a location where only the system administrator can see it. Giving access to this data can reveal highly sensitive information, like passwords held in memory.
To help prevent expanding the attack surface, system administrators should limit the error and debug messages available to untrusted code, and only relay information when there is actual benefit and the relayed information is actionable by the user.
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 your 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.
The increasing number and complexity of side channel attacks create a greater need for maintainers and developers to regularly review their code for potential new side channels, and to identify and mitigate vulnerabilities. This can be a complex task, especially for large codebases and complex hardware, but is important.
Not all side channels expose systems to equal risk. Assessing your system’s needs can help you prioritize which side channels to address immediately and develop a timeline to deal with the others. When assessing your system, consider the following guidelines.
In addition to using reputable libraries, keeping all software and dependencies up-to-date, and following all the other best practices that are focused on side channels as described above, 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 they find a viable attack.
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 efforts, you may also wish to examine some of the lists of most dangerous and commonly exploited issues, such as: