It has been a while since I wrote the third post of the series. It turns out that the first post of this series was so successful that the dev team included me in one of their weekly must-read lists, and they sent me a sticker pack as a christmas gift for being a distinguished 2020 author! I received the gift today and it reminded me that I never got to finish the series, so forgive me for the delay if you have been waiting for this post.
I feel like this last article in the series is probably the most useful one because of the topics that it covers. However, to be able to understand it, a knowledge of the already covered topics is needed. So for anyone that is finding this article and has not read the previous ones, I would advice doing that before reading this one.
Key exchanges
So far in the series we have covered symmetric systems. Meaning that two parties need to agree on a shared secret key before starting the communication.
Chances are that you have never in your life agreed on a secret key with somebody to be able to communicate privately with them. And yet, you probably use private messaging applications every day. How is that possible?
The answer is that internet applications handle the key exchange for you. But how is it possible to share a key without first establishing a secure encrypted communication? Does it not make the encryption useless if you first share the key publicly unencrypted on the internet before starting the encrypted communication? The answer to this is yes, if the key was shared unencrypted. But it is possible to share a key between two parties without revealing the key to a middle observer. The first discovered, and most used algorithm to do this is Diffie-Hellman, and it relies on both parties having a public key that anybody can know, and a related private key, that can not be obtained from the public key. Diffie-Hellman relies on modular arithmetic maths, and it would be beyond the scope of this series to explain them, but here is a link to a video explaining how it works in an intuitive way, for those that are interested:
There is one issue with Diffie-Hellman however. If a middleman can not only read the key exchange messages, but also can modify them, then it is possible to manipulate the exchange in a way where the middleman is able to read all the encrypted messages sent after. For this reason, it is important before we start the key exchange to verify that the public key we are doing the exchange with, really belongs to who we want to communicate with. We will explore some ways of doing this when we talk about protocols.
Public key Cryptography
We should also mention Public key cryptography algorithms such as RSA, which allow sending messages without needing to share a key. They use a public-private key pair and mathematics similar to those of Diffie-Hellman.
In Asymmetric cryptography, the sender uses the receiver's public key for encrypting, and the receiver uses their private key for decrypting. In reality, this is mostly used for the purpose of exchanging keys and establishing symmetric key communications, because symmetric algorithms are much more efficient for a computer than executing asymmetric key maths.
RSA and similar algorithms have another very useful use case, which is asymmetric signing. If the sender sends a message encrypted with their private key, then a receiver could decrypt it with the public key. This is not safe encryption, since anybody can have the public key and decrypt the message. However, only someone that has the private key could generate a message that correctly decrypts with the public key, verifying the identity of the sender. This is the asymmetric alternative to symmetric signing algorithms such as HMAC, and is used very frequently in most protocols for the purpose of verifying the identity of parties during key exchanges.
In the bad practices list linked in the first post are included 3 rules related exclusively to RSA:
Don't use a short key (<2048 bits)
When generating a keypair we are given the option of selecting the size of the key. It is recommended to use keys 2048 bits or longer.
Don't use the textbook (raw) algorithm
The RSA algorithm is actually pretty simple, the hardest part being the key generation. As explained in the basic rules on the first article however, we should never program our own crypto and use libraries whenever possible.
Don't use PKCS#1 v1.5 padding
If we are using an updated library, we should use the default settings for algorithms when we are not sure of what we are doing, since they will usually be safe. This particular version of padding allows for an attack on RSA and should not be used.
Cryptographical protocols
In this section we will finally put it all together and see how cryptography is most commonly used in the real world. And how it is currently being used by your browser right now as you are reading this article.
The majority of cryptographical protocols follow the same structure.
Trust and parameter negotiation
The first step in a protocol is the most important, and is the main difference between protocols. Here the two parties in the communication need to verify their identity. In the case of a client/server scenario such as in TLS, only the server needs to verify their identity. Once the identity of the parties has been verified they will both have each other's public key which they can use in the next step.
In this initial contact there can also be parameter negotiation, where both parties agree on which algorithms and parameters they will use for each of the steps. Most protocols can use different versions and different algorithms for each of the steps. The set of algorithms chosen is referred to as a cipher suite, and it commonly contains:
- a key exchange algorithm (such as Diffie-Hellman)
- a symmetric block cipher for communication (such as AES-256-CBC)
- a symmetric signing algorithm for message integrity and authentication (such as HMAC-sha256)
It is important to choose safe parameters and to use the latest versions of protocols. In the case of setting up a server, it is important to configure it so that it will not accept unsafe parameters from a client. Unsafe parameters meaning algorithms which we have seen in previous articles that are considered deprecated, or have important vulnerabilities.
Key exchange
Now that the identity of the parties has been verified they can use their public key to execute a key exchange with the negotiated algorithm. Common key exchange algorithms are Diffie-Hellman or RSA.
Communication
Now a secret key is shared between the two parties and they can freely communicate using a symmetric algorithm. Usually this is a session key, and is renegotiated each time a new communication session starts. The reason why block / symmetric cryptography is used instead of directly using the public keys to communicate with something like RSA is because the encryption/decryption is much more efficient with block ciphers.
Along with the encrypted message a signature is sent using the symmetric key to verify the integrity and authenticity of the message. In t he case of using a block mode such as GCM, the auth tag is included in the encryption, so no separate authentication algorithm is required.
Protocol examples
Let's see some examples of some of the most used protocols and the different trust models they have.
SSH
ssh is commonly used for connecting remotely to servers. SSH establishes an encrypted connection with a server, where we can access and execute commands remotely from our own comupter.
The way in which the client verifies their identity varies. It can be setup so that the access is granted with a simple password, or (safer) it can be set up so that it will grant access to a whitelist of public keys. In this case the client verifies their identity by signing a message with their private key.
The identity of the server should also be verified however. The reason is that someone could enter the communication and pretend to be the server, and receive all our commands pretending to be the server we are trying to connect with. If the access is set up with a password they could also capture our sent password and use it to connect to the real server.
The way in which we verify the server's identity is manually. Meaning ssh has no automatic identity verification. If you have ever connected to a server with ssh you have most likely seen this message (and chances are you ignored it):
In this message ssh is warning us that the public key of the server is not verified, and gives us a fingerprint of the ECDSA key. The fingerprint is a hash of the public key, which is easier to verify, since public keys are usually pretty long. What we are supposed to do in this situation would be to contact a trusted party that is able to confirm that this is the correct fingerprint. This could be achieved for example by calling the system administrator and checking with them. Once we confirm that the fingerprint is correct, we can continue, and it will be stored in our computer in a known_hosts file, so that this step will only need to be performed once for each server (or whenever the server changes their public key).
By typing yes without verifying the fingerprint we are placing a bet on the possibility no one is performing an attack on us. In some cases this is ok, but for important servers such as production servers, it is worth it to perform the verification. Your system administrator will be very happy that someone cares, and it will reflect positively on you 😄.
TLS
TLS, also commonly referred to as SSL (which is the previous deprecated version to TLS), is used to encrypt web traffic. It is the protocol used for protecting https webpages among other things. TLS is arguably the most important cryptographical protocol. Your device is using it right now to receive this article encrypted to your browser from dev.to.
It would not make sense for TLS verification to be manual such as in ssh, so an automatic system is set up for trust, which is based on digital certificates. TLS/SSL certificates are basically a signed public key. Anybody can sign a certificate, but for the certificate to be trusted by a browser, it needs to be signed by a reputable CA (Certificate Authority). Certificate Authorities are trusted entities which verify the identity of webpages, and if the requester passes their checks, they will receive a certificate signed by the CA's private key. The certificate contains the public key, and the domain of the webpage that requests it.
When connecting to a webpage protected by TLS, the server will send your browser their certificate, and they will sign a message with their private key to prove their identity. An attacker can not send a valid certificate since no reputable CA would have provided them with one for a domain they don't own.
The CA's public keys are included in browsers when they are downloaded. It is important when downloading a browser to do it from a trusted source. A possible attack on TLS would be to inject an attacker's public key in the list of trusted CA's in your browser, which would allow them to sign forged certificates that would be then trusted by your browser.
In TLS the browser does not need to authenticate themselves to establish a secure channel.
Conclusions
I really hope that if you got all the way through, you found the series useful and that you learned something from it. I have done my best to express the topics in the least confusing way, without entering in the details. I would encourage anybody interested by any of the topics covered to look for more information and learn by themselves.
I am looking forward to writing more posts in the future, as I enjoyed writing these. Let me know if you have any ideas about possible topics that you would like to read about regarding areas such as cryptography, blockchain, or general backend development. There are many important things that were not covered and could be additional articles in themselves. 🤖