- COMP.SEC.100
- 19. Applied Cryptography
- 19.2 Implementing cryptography (advanced)
Implementing cryptography (advanced)¶
Cryptographic libraries¶
From a developer’s perspective, cryptography is usually used via a cryptographic library, a collection of algorithms and schemes accessed through an application programming interface (API). Many different cryptographic libraries are available in various programming languages, with C variants and Java being among the most common. Libraries have different licensing restrictions, and many are available under various types of open-source licences.
Some libraries (e.g. OpenSSL or BouncyCastle) offer a wide range of features and can be used in diverse applications. Others are more limited and are designed to support only specific use cases. This partly reflects the preferences of the developers, but also the age of the libraries and the development resources available. For example, both of the above libraries were used in a TAU master’s thesis in 2021, in which the PACE protocol was implemented in a card reader for NFC use: OpenSSL for iOS and BouncyCastle for Android.
Some libraries are better maintained than others. For example, before the Heartbleed vulnerability (2012–2014), OpenSSL had fallen into a somewhat poor state. As a result, Google and OpenBSD independently created their own separate development branches (“forks”) of it. Heartbleed also led to broader awareness of how important OpenSSL is to the entire Internet ecosystem. This was followed by reforms to the OpenSSL project, and today it is in much better condition, with a larger core developer community, better funding, and more active development.
Some cryptographic libraries are developed by professional software engineers with significant experience in avoiding the kinds of issues discussed in this module. Others are not. In many cases, developers work on a voluntary basis; most of OpenSSL’s code development is carried out in this way. A cryptographic library should have a clear process for reporting bugs and security vulnerabilities to maintainers, and the developers should be committed to addressing these issues in a timely manner.
API design for cryptographic libraries¶
The application programming interface provided by a cryptographic library is critical. A delicate balance must be found between flexibility and security. Flexibility allows developers to use the library in many different ways, making it more useful. Security requires restricting the API so that developers cannot use the library in unsafe ways. This can be illustrated in the case where an API offers symmetric encryption. Should the library allow direct access to the block cipher itself, enabling developers to supply parameters and plaintext blocks directly? Some developers might need this at some stage, but an inexperienced developer might use the block cipher in ECB mode to encrypt large amounts of data—with well-known insecure consequences. This is only a simple example. One might instead consider, for instance, an API that either allows or does not allow the caller to provide the nonce values required for AEAD encryption. An even more demanding case for the user would be allowing them to select parameters for primality testing. This material has not even covered that topic, which is essential, for example, in generating RSA parameters.
Green and Smith [Green2016] present ten principles for cryptographic API design. These are based on case studies and interviews with developers. Later, more formal academic analyses have also been conducted, but these principles are suitable for this course.
Green and Smith note that cryptographic libraries appear to be uniquely prone to misuse and that even small mistakes can lead to major problems. They also observe that as cryptography becomes more widespread, more developers without cryptographic expertise are using these libraries. It is worth adding a local note: later TAU courses significantly enhance expertise precisely in this area within the field of cybersecurity.
The ten principles of Green and Smith can be summarised as follows:
- Integrate cryptographic operations into standard APIs; that is, hide cryptography from developers as much as possible.
- Ensure APIs are sufficient to meet both security-related and non-security-related requirements. The reasoning is that developers do not primarily think in terms of security goals. Meeting their actual requirements—as observed in interviews—encourages them to use the API instead of implementing their own cryptographic code.
- Design the API to be easy to learn without cryptographic expertise.
- Do not violate the developer’s mental model of what an API should look like.
- Design the API to be usable even without documentation. Developers are unlikely to read it anyway.
- Make the API difficult to misuse. Incorrect usage should result in clearly visible errors.
- Provide safe and unambiguous default settings.
- APIs should have testing modes. Otherwise, developers may disable security features during development to simplify testing and forget to re-enable them. This recommendation has the drawback that code might be released while still in testing mode; ideally, standard software quality assurance should prevent this.
- Code using the API should be easy to read and maintain. For example, the number of hash iterations used in deriving a key from a password should not be set via the API but adjusted within the library if necessary. One issue with this recommendation is that internal defaults may be overly conservative and degrade performance in some use cases, illustrating the tension between flexibility and security.
- The API should also support interaction with the end user rather than placing the entire burden on the developer. Green and Smith emphasise the importance of error messages: API and library documentation should help developers understand possible failure modes, their security implications, and how calling code should handle resulting errors.
[Green2016] M. Green and M. Smith, “Developers are not the enemy!: The need for usable security APIs,” IEEE Security & Privacy, vol. 14, no. 5, 2016.
. _18-2-3-en:
Challenges of implementing cryptography¶
The main challenge in implementing cryptography is translating the purely mathematical or pseudocode description of schemes into program code such that, when executed, it still adheres to the security analysis performed at the abstract level. In other words, the challenge is to ensure that the implementation does not introduce mechanisms through which sensitive information can leak and that have not already been anticipated and eliminated during the security analysis. The following presents several ways in which such leakage may occur. These are typically known as side channels.
Side channels related to message length¶
The AEAD encryption scheme (authenticated encryption with associated data) does not conceal the length of the plaintext. On the contrary, in AEAD schemes such as AES-GCM, the plaintext length can be trivially inferred from the ciphertext length. Leakage of length information may nevertheless be catastrophic for security. For example, consider a simple trading system where a user issues only two types of commands, “BUY” or “SELL”. If these are encoded in simple ASCII format and transmitted using AES-GCM, an eavesdropper can directly determine the command from the message length (the ciphertext has essentially the same length as the plaintext (+ tag), since in AES-GCM the text is not transformed blockwise by AES but encrypted via XOR in a stream-like manner). More generally, attacks based on analysing traffic and unencrypted metadata can result in significant information leakage.
Side channels related to timing¶
The time required to execute cryptographic code may leak information about the internal processing steps of an algorithm. This, in turn, may reveal sensitive information, such as details of keys. This issue first came to public attention in 1996, in a case where the attacker had direct access to timing information. In 2003, it was demonstrated that such attacks could even be carried out remotely, despite timing measurements being affected by network noise.
Consider, for example, a naïve implementation of elliptic curve scalar multiplication that is optimised to ignore leading zero bits of the scalar. If an attacker can measure the execution time of the scalar multiplication routine, they may detect cases where the computation terminates earlier and infer how many of the most significant bits are zero. Depending on how the routine is used, this may provide sufficient side-channel information to recover the key. This applies, for instance, to ECDSA, where even partial leakage of randomness values can be exploited. Research evidence for such attacks also exists from the 2020s. (ECDSA stands for Elliptic Curve Digital Signature Algorithm, derived from DSA, which itself is related to ElGamal.)
Side channels related to error conditions¶
Errors during cryptographic processing may reveal information about internal operations. A classical—and still relevant—example is CBC-mode encryption and the padding required for the last plaintext block. If the padding is incorrectly formatted, an error occurs during decryption. If an attacker can detect the presence of such an error, they may, through carefully crafted ciphertexts, exploit a side channel to obtain information about the plaintext.
In practice, error messages themselves may be encrypted, but their existence can be revealed through secondary side channels, such as timing. For example, an implementation might abort processing upon detecting a padding error. The difficulty of fully eliminating such side channels has been demonstrated in analyses of TLS as late as 2013.
Attacks involving shared resources¶
Cryptographic code does not always operate in complete isolation from potential attackers. Modern processors have cache hierarchies in which fast memory is shared among processes, and processes may overwrite each other’s cache contents. For example, in cloud computing environments, processes from different users may run concurrently on the same hardware, even if separated by security mechanisms such as virtualisation. An attacker running in a separate CPU process could deliberately evict specific cache lines and then, after the victim process has executed sensitive code, observe access timings to determine whether the victim used those cache lines. If the victim’s memory access pattern depends on secret data, such as a key, information may leak indirectly. This attack is known as Flush+Reload and was introduced in 2014. Numerous other cache-based attacks are also known. The potential for such attacks was recognised as early as 2002 and was later shown to pose particular risks for AES.
In recent years, researchers have developed a wide range of cache- and microarchitecture-based attacks against cryptographic implementations. These vulnerabilities generally arise because processor designers make architectural trade-offs in pursuit of performance.
Implementation weaknesses¶
In addition to side channels, cryptographic implementations often suffer from more conventional weaknesses. Keys may not be properly erased after use, or they may inadvertently be included in backups. Decrypted plaintext may be incorrectly passed to the calling application before its integrity has been verified. This can occur in schemes where MAC verification is performed after decryption. Similar issues may arise in streaming applications with limited buffer sizes for decrypted data.
Attacks due to system composition¶
Systems that combine multiple cryptographic components may inadvertently leak sensitive information due to incorrect integration. Such leaks occur at the system level rather than within individual components. An example is the Zcash cryptocurrency. Zcash provides anonymity for recipients using public-key encryption. A Zcash client determines whether a transaction is intended for it by attempting decryption with its private key. If this fails, no further processing occurs; otherwise, additional components (such as zero-knowledge proofs and commitment verification) are executed. This difference in behaviour may be observable and can break anonymity, even if each individual component is secure.
Hardware-related side channels¶
Cryptography is often implemented directly in hardware. Hardware acceleration of cryptographic functions has long been common in both constrained environments (such as payment cards) and high-performance settings (such as TLS on servers). Today, IoT implementations may use hardware components to perform computationally intensive cryptographic operations. Hardware-based cryptography is also used in trusted computing technologies such as TPMs (Trusted Platform Modules), Intel SGX (Software Guard Extensions), and ARM TrustZone. Moreover, modern CPUs often include instruction set support for efficiently implementing algorithms such as AES.
Implementing cryptography in hardware introduces additional side-channel risks beyond those already discussed. For instance, an attacker targeting a smart card may be able to observe its power consumption with fine-grained time resolution, potentially revealing the type of operation being performed. In an RSA implementation, for example, a private-key bit value of 1 may result in a square-and-multiply operation, whereas 0 results only in squaring. If this distinction is not masked, the key may be recovered bit by bit from the power trace.
Electromagnetic emissions from hardware can also leak sensitive information. Even acoustic side channels are possible. For example, an early quantum key distribution (QKD) prototype (1990) reportedly leaked information because an observer could listen to mechanical movements of optical components to determine which polarisation was used for each transmitted signal. This highlights one of the many challenges in achieving truly secure systems under the laws of physics. More on hardware side channels is discussed in a later module.
Attacks by inducing faults¶
Hardware implementations may also be vulnerable to fault or disturbance attacks, in which an external error is induced at a precisely chosen stage of cryptographic computation. This produces an incorrect result, from which sensitive information—typically a key—can be inferred. The first such attack (1997) targeted an RSA implementation that computed separately with respect to each prime factor (p and q) before forming the result modulo (p·q). A well-timed fault (“glitch”) could cause the computation to yield a result mod p only, leading to successful factorisation. A more recent instance of this type of attack (2014), known as Rowhammer, aims to induce faults in memory locations where keys are stored. This is attempted by repeatedly writing to adjacent memory locations (see Operating systems, including side channels).
Defences¶
Vulnerabilities in cryptographic implementations are distinct from weaknesses in the underlying algorithms or systems. General techniques for defending against implementation vulnerabilities come from the fields of software and hardware security. These are well summarised in other modules of this material. It is evident that traditional software security is even more important for cryptographic code than for other types of software.
In hardware, commonly used techniques include blinding, masking, threshold techniques, and physical shielding. For software, common approaches include formal specification and verification of software and hardware designs, static and dynamic code analysis, fuzzing, data-flow analysis, the use of domain-specific languages for cryptographic code, and strong typing for modelling and implementing security properties.
Length-based side channels can be mitigated by padding plaintexts to predetermined sizes before encryption and by adding cover or dummy traffic. Secure communication protocols such as TLS and IPsec include features to support this, but they are not widely used. Coding practices have been developed to achieve so-called constant-time cryptography. The core idea is to eliminate, through careful programming, all correlations between sensitive data and observable variables (such as execution time). This requires, among other things, avoiding memory accesses and branching that depend on secret data, as well as certain low-level instructions whose execution time depends on operands. It may also require writing high-level code such that the compiler does not optimise away constant-time properties. Writing constant-time implementations of existing algorithms is not trivial. In some cases, cryptographic designers have addressed this from the outset. For example, Bernstein’s ChaCha20 algorithm was designed with this in mind. Similarly, certain coordinate systems make it easier to achieve constant-time implementations for elliptic curve algorithms.
Generation of random bits¶
Cryptography relies critically on randomness. Most obviously, random bits are needed for symmetric keys and as input to key generation algorithms for public-key systems. Some signature schemes are also probabilistic, such as RSA-PSS, DSA, and ECDSA.
Cryptographic algorithms—and also protocols—must have access to “strong” sources of random bits. For general applicability, such a source should provide a large number of independent and uniformly distributed bits about which an attacker has no information. Some algorithms may reveal their random values, but general usage requires that they remain hidden.
In an ideal world, every computing device would include a true random bit generator (TRBG, also called TRNG, where N = number), whose output is hidden from attackers. In practice, this has proven difficult to achieve. Intel and AMD processors provide access to processed TRBG output via the RDRAND instruction, but the internal design of these generators is not fully transparent. In the absence of a TRBG, a common approach is for the operating system to collect entropy from weak local sources, such as keystroke timings, disk access times, process identifiers, and packet arrival times. This data is gathered and mixed into an entropy pool. From this pool, pseudorandom bits are extracted as needed to seed a cryptographic pseudorandom number generator (PRNG). NIST has standardised such models (2015). They are used in most operating systems, although often with ad hoc constructions that are difficult to analyse. Research has produced mature formal security models for random bit generators, but this is another case where practice preceded theory.
It is challenging to assess how much actual randomness can be obtained from these weak entropy sources. In some computing environments, such as embedded systems, some or all sources may be absent. This can lead to slow accumulation of entropy after a reboot. A related issue appears in virtual machine environments, where randomness may repeat if it is drawn from the host system too soon after a reset.
There has been long-standing debate on whether random number generators should block their output when the entropy estimate maintained by the operating system falls below a threshold. The short answer is no, provided that the PRNG is cryptographically strong and the entropy pool has been properly initialised after startup. This is because the output of a strong PRNG is computationally indistinguishable from true randomness, even if it is not truly random. Some modern operating systems offer an interface to a generator of the “do-not-block-after-initialisation” type.