top of page
  • Writer's pictureMichael Paltsev

TLS for Developers

Updated: Feb 23, 2023


We use TLS all the time, as users when we open our web browsers or use mobile apps, and as Software Engineers when we implement a server or a client (or both). Many great guides have been written about TLS. Some are very short, explaining the concept for regular users, and some are extremely long and have a lot of detail in them. Most of the time, developers have a very high-level understanding of how TLS works making the short guides useless, because the information they provide is already known, and when it comes to troubleshooting an issue, reading the detailed documents is frustrating and hard. Therefore, I've created this article in hopes that it'll help people to get just enough information to be able to understand TLS in a way that will help them to excel at their work.


So, what is TLS? TLS stands for Transport Layer Security which means that its purpose is to provide security for the transport layer. That means that it encrypts (symmetrically - same key encrypts and decrypts the data) the traffic and allows one-way (the server) or mutual (the server and client, which is usually called Mutual TLS, or mTLS for short) authentication. It is the successor of SSL v3.0 protocol (Secure Socket Layer) but the name is a bit confusing because the implementation is meant primarily for reliable transport protocols like TCP and not for other unreliable transport protocols such as UDP (to which a different protocol exists - DTLS).


The latest version of TLS (as of the date of this article) is 1.3 (RFC-8446) but it had other iterations such as 1.2 (RFC-5246), 1.1 (RFC-4346, RFC-4366), and 1.0 (RFC-2246, RFC-3546) that was based on SSL v3.0 (and was known as SSL 3.1 - this version convention kinda stayed in the inner implementation, and so TLS version 1.3 is represented as 3.4). In this article, we will be talking about the latest version (1.3) and will only mention 1.2 briefly for comparison while leaving out both 1.1 and 1.0 because they are deprecated (RFC-8996).


Before we dive into talking about the specific version, we should understand how it works. You can't encrypt traffic if there is no connection, so, we need to create a connection. And because TCP (Transmission Control Protocol) is the most common reliable transport layer protocol and most of the time we find ourselves using it, it'll be a good idea to talk about it first (we assume that the DNS lookup is very fast and therefore negligible for our discussion).


TCP (RFC-9293) is implemented inside the OS (Operating System) and the connection is made by a three-way handshake process which can be seen in the diagram below:


When our application tries to create a connection (or our server receives one), there will be three packets sent between the two different parties (usually a client and a server). The first one will be sent by the client and is called the SYN (synchronization) packet which will hold the sequence number of the client, following that, the server will respond with a packet containing an ACK (acknowledgment), telling the client that it received the SYN and his own SYN that will hold his sequence number. After the client has processed the SYN of the server, it will acknowledge it with an ACK packet. At the end of this process, the transmission of data between the client and the server will begin. As you can see, after every packet, the receiver acknowledges it by sending back an ACK.


Therefore, we can say that it takes 1 RTT (Round Trip Time) from the Client's point of view. One thing to note here is that the actual time it takes for the connection to take place varies because of distance, load, etc.


Now that we have initiated a connection, where does TLS comes in? The TLS handshake starts right after the TCP handshake before the data has started flowing. In the below diagrams, we can see a comparison between TLS 1.3 and 1.2 handshakes:



The thing that we see straight away (apart from the omitted TCP ACKs that were omitted for convenience) is that TLS 1.3 has fewer messages which result in fewer round trips. This saves the client 1 RTT. Combined with TCP, when using TLS 1.3 the client has an overall 2 RTT as opposed to 3 RTT when you use TLS 1.2. There is another time-saving benefit in using TLS 1.3 - 0 RTT when reconnecting to a server with which you've already established a secure connection. These times are of-course, optimistic.


Now, let us talk about what is going on behind the scenes (we assume that the server supports TLS 1.3). I will not elaborate on all the fields that the messages hold but will try to give information about the important ones. Keep in mind, that all messages have headers to let the receiving party understand what type of message is it and its size.


The first is the ClientHello message that is sent from the client to the server with the following values:

  • Record Header that stores a TLS version that is equal to 1.0. This is done for interoperability with older implementations.

  • Handshake Header that tells that it is a ClientHello message.

  • Client Version that is always set to 1.2. No, it's not a mistake. The reason it is set to 1.2 is because of issues with middleboxes (a term that describes things like Firewalls, NATs, Load Balancers, and other components that are deployed on the network) that do not allow messages they do not understand. Remember, prior versions are deprecated and no longer supported and should not be used.

  • A client random which is a 28-byte secured random number - will be used later by the server to generate the Master Secret.

  • The supported cipher list that consists of 5 ciphers: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256 (which is much smaller than the one in TLS 1.2 that had 37 supported ciphers) from which the server will choose a cipher to use for the entire communication period and with which the encryption will be made. The client can send fewer supported ciphers, but it must support TLS_AES_128_GCM_SHA256 and should support both TLS_AES_256_GCM_SHA384 and TLS_CHACHA20_POLY1305_SHA256.

  • Extensions (there are more than the ones I'll list):

    • supported_versions - this holds the real supported versions of the client where we should have the 1.3 version

    • key_share - this holds the public keys that the client generated based on the ciphers it thinks the server will choose

    • server_name - this holds the SNI (Server Name Indication) that tells the server, in case it serves multiple virtual hosts on the same IP, which cert it should provide to the client

The server should now respond with the ServerHello, after which it can start encrypting the messages, including the rest of the handshake. It can do that because it now has everything to be able to create its Master Secret. The ServerHello has the following values:

  • Record Header that stores a TLS version that is equal to 1.2 (and not 1.0 as in the client).

  • Handshake Header that tells that it is a ServerHello message.

  • Server Version is always set to 1.2, just like in the client.

  • A server random which is a 28-byte secured random number - will be used later by the client to generate the Master Secret.

  • The selected cipher suite.

  • Extensions (there are only two extensions in this case):

    • supported_versions - this holds the TLS version that the server selected (1.3 in our case)

    • key_share - this holds the public keys that the server generated based on the selected cipher

From the server's point of view, the handshake has been completed and it sends the Finished message.

It will now send his x509 (RFC-5280) in an encrypted way for the client to be able to authenticate it. The client will be able to decrypt the certificate because it has all the information needed from the ServerHello message.


The last operation is being done by the client. The client now needs to generate the master secret and validate the identity of the server by looking at its public certificate, which it has to decrypt first (this is important because we not only want our data to be encrypted but, we also want to make sure that the receiver of the data is who he claims he is). After it finished with these steps, it sends the Finished message and from now on, the traffic is encrypted both ways. The steps for validating the certificate can be found below:

  1. It looks at the date of the x509 certificate and compares it to the current date to check if it is valid. There are two dates that the current date should be in between them (not before, and not after). If it is not, the validation will fail.

  2. It checks to see if the CA (Certificate Authority), which was the one that issued the certificate, is trusted by looking at its DN (Distinguished Name) and matching it with the DN of one of the CAs that is stored in its trusted list of CAs. If no match is found, it will have to be presented with a chain of CAs that will lead from the untrusted CA to a trusted one. Otherwise, the validation will fail.

  3. It checks to see if the FQDN (Fully Qualified Domain Name) of the server matches the CN (Common Name) that is present in the certificate.

  4. Now it has to verify the cert itself by using the public key from the CA cert retrieved from the trusted list of CAs (in step b) to validate the digital signature of the CA that creates the public certificate of the server. If it fails to validate, the whole validation fails.

All in all, TLS 1.3 has many advantages: it's faster, more secure, and can reuse previous handshakes to save time. It can also fail in many places because of human errors, for example during the validation phase of the server if the client time is not set correctly.


I hope that I've managed to explain TLS in a way that will help you understand how TLS works without going into too much detail.


Please let me know what you think in the comments.


48 views0 comments
bottom of page