After some discussion about TLS1.3 and ciphers with a few colleagues, I have decided to write a short summary of “the basics” you need to know as a developer. Here I will also explain shortly a bit which niche each of the 5 ciphers aims for and what the hash function they contain is actually used for. This article will though not go into the details of key negotiation other than is what it affects the ciphers used by TLS1.3 to secure the communication. In other words, I will not talk about the Diffie-Hellman or Elliptic Curve Diffie-Hellman options, the signature algorithms (for certificates), the preshared keys or any other such topics. If there is a need for it I will cover those topics in a future article.
How the cipher to use is chosen.
The first thing to understand is how the two parties communicating using TLS1.3 decide which cipher to use. This is known as cipher negotiation and is part of the “handshake” or initial communication between both parties. Here other things are done aside from choosing which ciphers to use but as I mentioned most of them are out of the scope of this article.
So to perform this handshake, the party that acts as a client starts by sending a message called “ClientHello” which contains a lot of information including the sets of ciphers it supports. This message usually contains the ciphers in order so the first are the ones the client would rather to use most while the last are, instead, the ones they would rather to use less.
Once the party acting as a server has this information, the party has to choose a cipher to use (or close the connection saying that none of the ciphers are supported). This is performed using a message known as “ServerHello”. This is followed by messages to start encrypted communication from the server and then the client called “ChangeCipherSpec”. All this process usually happens in clear text and looks as follows:
- Client: Hey! I prefer cipherA, otherwise cipherB and otherwise cipherC.
- Server: Hum… okay! Let’s use cipherB in particular from the ones you said. Changing to it for the rest of the protocol. (Encrypted stuff).
- Client: Nice! Changing into it too. (Encrypted stuff).
So as can be seen here, the client first sends the list of ciphers it supports saying which one it prefers. The server then chooses one of them and goes ahead with it. Alternatively if none of the ciphers are supported or liked by the server, the server chooses the connection.
This means that, as long as the client does send the ciphers in the order it prefers, the server has all the knowledge it needs to make a choice. In particular:
- The client says the ciphers it supports (or wants to support).
- The client says in which order it prefers them.
- The server knows the ciphers it supports (or wants to support).
- The server knows in which order it prefers them.
In particular, if the client has a strong preference for a cipher for a reason, he can put it the first and, unless the server decides to override the client’s decision, that will be used if it is supported by the server. This is VERY useful for performance reasons as we will see later, not so much for security reasons, nor client nor server should ever announce or accept any ciphers they deem to be insecure.
Available ciphers
RFC 8446 defines TLS1.3 and in doing so also 5 cipher suites that can be used. There is another one, RFC 8998 which defines 2 ciphers (known as ShangMi) which are mandatory in China, and two drafts one adding GOST (Russian) cipher support, and another one adding authentication only modes. I will only cover the 5 ciphers originally defined in TLS1.3 since the other ciphers still are either experimental drafts or not widely available. I will be happy to update this article in the future if there is stronger interests in these other ciphers, in particular the authentication only which may be useful under certain circumstances.
So, which are the 5 ciphers supported by TLS1.3? They are the following:
- TLS_AES_128_GCM_SHA256
- 128-bit AES in GCM mode as cipher with SHA-256 as hash function.
- TLS_AES_256_GCM_SHA384
- 256-bit AES in GCM mode as cipher with SHA-384 as hash function.
- TLS_CHACHA20_POLY1305_SHA256
- 256-bit ChaCha20 with a Poly1305 authenticator with SHA-256 as hash function.
- TLS_AES_128_CCM_SHA256
- 128-bit AES in CCM mode as cipher with SHA-256 as hash function.
- TLS_AES_128_CCM_8_SHA256
- 128-bit AES in CCM mode with authentication tags truncated to 8 bytes as cipher with SHA-256 as hash function.
Ciphers and hashes
You may have noticed that at each time we define a cipher and a hash together (and no other things like for example key negotiation algorithms). This is so because both are tied but not in the way in which you think.
In TLS1.3 the ciphers are what is known as AEAD (authenticated encryption with authenticated data) ciphers. That means that the ciphers themselves do quite a few things at the same time:
- Encrypt the data using the key. This preserves confidentiality of the data. I.e. guarantees nobody other than those knowing the key can read it.
- Authenticate, using the key, the data. This preserves integrity of the data. I.e. guarantees that any changes to the data (including additions and deletions are detected). The authentication, also guarantees that only those knowing the encryption key can send data that the other side will accept.
- Authenticate, using the key, data that is not encrypted too. This provides the same guarantees regarding integrity and ability to create valid messages for any other data that the protocol wants to validate. This is particularly useful to ensure non encrypted data that defines properties of the encrypted data is not modified.
Keep in mind that these ciphers do not provides guarantees like non repudiability (the guarantee that only the sender could have sent the message as is provided by signatures) because anybody with the key to decrypt the data can easily create a new message that will validate. This is usually provided by TLS in a different way. Despite that, since it provides confidentiality for the data and integrity and authenticity both for the encrypted and non encrypted data, these ciphers are the perfect building block for building a protocol like TLS which aims to do exactly that.
So, if the cipher can handle all of our data needs why is there a hash involved too? Well the hash serves a very different purpose. First the hash is used to create a summary of the messages exchanged so far. This is particularly useful because if an attacker changes any of the messages, these would result in a different number. Second, the hash is used to derive all the keys used by the protocol from the exchanged data (including the summary of the messages exchanged). Third, the hash is used to prove to each other that no tampering was made by generating an authentication tag from the summary of the messages exchanged and that the keys are the same on both sides by using one of the derived keys to authenticate this data (and only for that purpose).
As can be seen, although the hash plays an important role before the actual messages from the application using TLS are exchanged, once we know we are talking to the right person and that no foul play was involved, we send all of the data using the AEAD cipher. In consequence, for longer connections sending significant amounts of data, this last cipher will be one of the main performance factors to take into account.
In the next sections I will explain the differences between the hash algorithms and the different ciphers and the reasons why each is chosen.
SHA-256
SHA-256 is a fairly old (2001) hash algorithm that was created by NIST as an alternative to SHA-1 and MD5 as concerns about their safety were raised. At the same time, a competition to choose a new algorithm (SHA-3) was started by NIST.
The algorithm offers a security equivalent to 128-bit ciphers for most purposes and since it works with 32-bit operations is fairly fast on most architectures. Certain modern desktop and server processors and many cryptographic accelerators provide fast support for this algorithm.
For most purposes SHA-256 is secure enough and fast enough.
SHA-384
Like SHA-256, SHA-384 was defined in the same (2001) standard and uses a similar approach as SHA-256.
Unlike SHA-256, SHA-384 offers much better security, being equivalent to that of 192-bit ciphers. Additionally, SHA-384 uses 64-bit operations meaning that it can handle twice the amount of data at the same time. Because of this it has to work a bit longer than SHA-256 but on 64-bit processors it is still faster than equivalent SHA-256 implementations. Unlike SHA-256 it also drops 128 bits of data from the hash result. This makes extension attacks harder since the attacker needs to guess those bits of data to be able to perform them.
Desktop and server processors do not have specialized support for this algorithm and support among cryptographic accelerators varies given the larger size of the state information and operations this algorithm requires.
In summary, SHA-384 is more secure than SHA-256 (although not in a way that will provide a relevant advantage unless quantum computers become feasible). It is also comparatively faster when implemented on software on 64-bit processors if the message is large enough. But on systems with an accelerator or instructions supporting only SHA-256 or without support for 64-bit operations SHA-384 will instead be slower than SHA-256.
128-bit AES in GCM mode
In order to choose a replacement for the, then aging, DES (data encryption standard) algorithm, the AES (advanced encryption standard) competition was held by NIST, one of the organizations that defines standards in the United States of America. The winner algorithm, Rijndael, was then adapted to become the cipher known as AES.
AES is a block cipher, it gets as input a fixed-size encryption key (128, 192 or 256 bits) and a fixed-size block of data (128-bits) and returns an encrypted block of data of 128-bits. Similarly it allows you to perform the reverse process, provide it a key, and the block encrypted with that key to get the original block back.
Still, you need to define how to handle data that is larger or smaller than a single block size. This is what the cipher’s operation mode does. And in this case, this mode of operation is called GCM which stands for Galois/Counter Mode. The GCM mode of operation works as follows:
- Use the block cipher to encrypt blocks containing a counter to generate a large enough string of binary data to encrypt the message and the 128-bit string used to authenticate the data.
- Perform an exclusive or of the data to encrypt with the encrypted counter blocks. This encrypts the data.
- Use a fast keyed “hash” function, called GMAC, over the additional data, the encrypted data and the size of both inputs to generate a 128-bit string to authenticate the data. This generates a unique identifier of the data.
- Encrypt the idenfitier of the data with the remaining encrypted counter block. This prevents external sources from accessing the data.
Decryption is similar but in the reverse order and verifying the calculated hash with the one that is obtained.
This approach has many advantages. First, the blocks can easily be encrypted in parallel by just taking into account the differences in the counters. Second, the “hash” used can be much simpler hash because it is keyed and unique to the specific key being used. Third they make use of a specific kind of hash (a multiplicative hash) that can also be parallelized. Also, for larger amounts of data, only one encryption (on average) needs to be done. Encryptions are particularly expensive in most cases.
In general, this mode is reasonably secure and fairly performant when hardware support is provided. This is the case of most modern desktop processors and some cryptgraphic accelerators but not for many embedded and mobile devices.
256-bit AES in GCM mode
This cipher is exactly the same as the one above, even 128-bit authentication tags are still used, but with larger keys (256-bits) to ensure the security of the hash function matches the security of the keys SHA-384 is used (unlike in the other modes). These larger keys make the cipher somewhat slower than the 128-bit one. On the other hand, keys this size provide the advantadge of being secure even if a powerful enough quantum computer is created.
ChaCha20 with a Poly1305 authenticator
Unlike the AES GCM ciphers described above, ChaCha20 is something more similar to a hash function. In particular ChaCha20 is a function that takes a 512-bit input and produces a 512-bit ouput in a way which makes it very hard to find what the input was and in which it ensures each one of the output bits is affected in some way by each of the input bits. The way in which this function is used is by creating a block with a 256-bit key, a 128-bit constant and a 128-bit mix of a counter and a value that is used only once.
Other than that, this cipher works in a way similar to the AES GCM ciphers. Instead of encrypting counters, these are “hashed” using the ChaCha20 function. Also a slightly different keyed hash is used than the one in GCM. Otherwise the ideas are fairly similar. For completeness here is a high level view of the encryption process (decryption is again similar but in reverse and verifying the hash):
- Use ChaCha20 to “hash” blocks containing a counter to generate a large enough string of binary data to encrypt the message and the 128-bit string used to authenticate the data.
- Perform an exclusive or of the data to encrypt with the “hashed” counter blocks. This encrypts the data.
- Use a fast keyed “hash” function (different from ChaCha20) over the additional data, the encrypted data and the size of both inputs to generate a 128-bit string to authenticate the data. This generates a unique identifier of the data.
- Encrypt the idenfitier of the data with the remaining encrypted counter block. This prevents external sources from accessing the data.
So if AES is approved by NIST and reasonably fast and secure, why would anybody support yet another cipher? The reason is fairly simple, while AES GCM is very fast when hardware support is available, it may not be as fast when that is not the case. In oposition, ChaCha20 and Poly-1305 is made to be as fast as possible in software. This means that they slower but still reasonably fast when compared to hardware accelerated AES GCM and VERY fast when compared against pure software AES GCM. In other words, ChaCha20 with Poly1305 are the best choice when no hardware support is available.
128-bit AES in CCM mode
Like GCM, CCM is another mode of operation of the AES block cipher which stands for Counter with CBC-MAC Mode. And where CBC-MAC stands for Cipher Block Chaining Message Authentication Code. This mode is similar to GCM in that it uses a counter block that is xored with the data to perform the encryption, but the keyed hash function it uses is special and applied in a different way. In particular, CCM works as follows:
- Use the block cipher to generate a hash like function of the additional data, the data that will be encrypted (before encrypting it) and the size of both inputs. This is done by including some extra blocks with information about how the “hash” function will be ran and padding to ensure all matches the desired block size. Then for each block we perform the exclusive or of the prior encrypted block with the plaintext data of this block and then encrypt the result. The authentication tag is the 128-bit result of the last encryption.
- Use the block cipher to encrypt blocks containing a counter to generate a large enough string of binary data to encrypt the message and the 128-bit string used to authenticate the data.
- Perform an exclusive or of the data to encrypt with the encrypted counter blocks. This encrypts the data.
- Encrypt the idenfitier of the data with the remaining encrypted counter block. This prevents external sources from accessing the data.
CCM mode has various design issues in their design, for example you need to know the size of the data that will be encrypted beforehand. CCM also requires performing two encryptions per block of data to encrypt and one per block to authenticate which can result in a serious performance problem when compared to GCM. Finally the equivalent of the hash that CCM uses cannot be parallelized unlike the ones used by GCM or ChaCha20-Poly1305. Despite those design and performance issues, CCM is as secure as the 128-bit GCM mode.
Why was this mode included then? Well the answer is fairly simple, certain embedded devices may not have enough resources to implement the AES cipher along with hardware support for the keyed hash used in GCM mode. In these devices, performing two encryptions is still faster than one encryption and one keyed hash. Also, unlike other modes offering similar solutions like OCB, this one was not encumbered by any patents.
Despite this rationale, openssl does not enable this mode by default.
128-bit AES in CCM mode with 64-bit authenticator tags
This one is similar to the one above but only uses 64-bit of the “hash”. This is a very more niche scenario on which you are accepting a higher possibility of an attacker modifying packets (1 in 2⁶⁴ instead of 1 in 2¹²⁸) in exchange for sending 8-bytes less of data per packet. In general this only makes sense in very specific embedded world situations where those 8 bytes suppose a huge difference. Nowadays, the IESG does not recommend using this cipher and it is not enabled by default by openssl. Personally I would not recommend its use unless you really know what you are doing.
The ideal scenario
In an ideal world, clients would remove the ciphers they consider insecure and then set the ciphers they want to use in a preference order according to their own security expectations and, the speed at which they can handle the different ciphers. Servers will then use this order to choose the best cipher to use based on their own security expectations and speed limitations.
For example, a client without any specific hardware support would put ChaCha20-Poly1305 first followed by the other ciphers and the server would probably honor that. On the other hand, a modern desktop may put 128-bit AES in GCM mode first followed by other ciphers including ChaCha20-Poly1305 so that when connecting to a server without hardware support for them, the server can still choose ChaCha20-Poly1305. That same client when connecting to a server with hardware support for 128-bit AES in GCM would still end up with the server choosing the GCM one instead.
In other words, for P2P systems the scenarios would be something akin to the following:
- Paranoid client:
- Sets only TLS_AES_256_GCM_SHA384 and maybe TLS_CHACHA20_POLY1305_SHA256 in speed order.
- Client without hardware support:
- Sets TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384 and maybe TLS_AES_128_CCM_SHA256. In that order. 256 bit AES may go earlier depending on security expectations.
- Client with hardware support only for AES:
- Sets TLS_AES_128_CCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256 and TLS_AES_256_GCM_SHA384. In that order. 256 bit AES may go earlier depending on security expectations.
- Client with hardware support for AES GCM, (for example AES-NI and PCLMULQDQ):
- Sets TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 and maybe TLS_AES_128_CCM_SHA256. In that order. 256 bit AES may go earlier depending on security expectations.
- Paranoid server:
- Chooses only TLS_AES_256_GCM_SHA384 and maybe TLS_CHACHA20_POLY1305_SHA256. Picks ChaCha20 if preferred on client or faster on server.
- Server without hardware support:
- Picks TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384 and maybe TLS_AES_128_CCM_SHA256. In that order. 256 bit AES may go earlier depending on security expectations.
- Server with hardware support only for AES:
- Picks TLS_CHACHA20_POLY1305_SHA256 if prefered by client, then TLS_AES_128_CCM_SHA256 if supported by client. Otherwise TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384. In that order. 256 bit AES may go earlier depending on security expectations.
- Server with hardware support for AES GCM:
- Picks based on client preference. May prioritize 256 bit AES depending on security expectations.
Notice that I have purposefully avoided setting TLS_AES_128_CCM_8_SHA256, if you need to support that you should already know that beforehand.
With the above approach we end up with the following table on the ideal scenario for P2P clients.
Client\Server | No HW | AES only | AES GCM |
---|---|---|---|
No HW | ChaCha20 | ChaCha20 | ChaCha20 |
AES only | ChaCha20 | CCM | CCM |
AES GCM | ChaCha20 | CCM | GCM |
As can be seen this means that we always end up chosing the option that provides the best performance for the side that would take the biggest impact if a more performant (for one side algorithm) was chosen.
On a purely client-server scenario prioritizing the server performance may be critical. Although the gains of GCM may not be too significant (CCM speed varies a lot from implementation to implementaion) they may still make a big difference depending on the service at hand. In this case we would have a table as follows:
Client\Server | No HW | AES only | AES GCM |
---|---|---|---|
No HW | ChaCha20 | CCM | GCM |
AES only | ChaCha20 | CCM | GCM |
AES GCM | ChaCha20 | CCM | GCM |
Other approaches are also possible, but by making sure the client expresses the performance it has for the different algorithms in the cipher order we can ensure the server always makes the most sensible choice with knowledge of both of the communication sides needs and abilities.
Conclusions
As can be seen, although in TLS1.3 the server will always be the one with the final say on the algorithm to use, the client still has a way to express preference for different algorithms. The client should use that to indicate security and then performance expectations so that the server can make an informed choice.