Video Summary and Transcription
This Talk provides an introduction to cryptography with Node.js, covering encryption concepts, AES-256-CBC algorithm, initialization vector for encryption, key derivation function and salt, randomness and key agreement, key distribution and RSA, signing and verification, public key certificates, and trust in certificate hierarchy.
1. Introduction to Cryptography with Node.js
In this talk, I'll discuss mastering cryptography fundamentals with Node's crypto module. Cryptography is the science of secret communication, which is crucial in our online world. Node.js has a built-in crypto module that offers common cryptographic algorithms for application use. The module's documentation assumes familiarity with cryptography basics, so I'll provide a shortcut by covering the concepts and practical usage. Let's get started!
Hi everyone. So in this talk, I'd like to talk to you about this. Oh, sorry. The title of the talk is encrypted. So let's apply the decryption.
Today I want to talk about this. Okay. Mastering cryptography fundamentals with Node's crypto module. So this title is a bit of a mouthful. So let's break it down. First let's talk about what is cryptography. Cryptography is the science of or the study of means of secret communication. And as you can imagine, in our online world, this plays a very important part.
So luckily inside of Node.js we have a built-in module that's called the crypto module and it's there to offer common cryptographic algorithms that you can use in your application. Now this is a built-in module just like FS so you can start using it right away without installing anything. Also as a built-in module it has documentation. So when I wanted to start using it for the first time, this is where I went. And I found that this type of documentation isn't really suited for a beginner like me. Because it's a very flat structure. There's very little information about each method. It kind of assumes that you're already pretty familiar with the basics of cryptography, what each algorithm does. And it just gives you the specific syntax in Node.js. So what I had to do is go back to the beginning and read up on the concepts. And learn about what each different algorithm does, what problems it solves, when it's applicable to use it. And only then when I had this conceptual data I was able to go back and see the actual syntax in Node.js so I can get practical and experiment with writing code that uses that algorithm. And this is the process through which I want to give you a shortcut in today's talk. I want us to cover the basics of cryptography, the conceptual information, and then get practical and see how we can use these things in Node.js.
So let's get started. My name is Yonatan Navarro. I'm a web developer from Wix.com.
2. Encryption Concepts and AES-256-CBC
The first concept in cryptography is encryption, which allows secure communication over an insecure channel. To perform encryption, we need to choose an encryption algorithm. One popular algorithm is AES-256-CBC, which ensures security and privacy. The algorithm's key size of 256 bits provides a large number of key combinations for attackers to guess. CBC mode and initialization vector enhance encryption security. The encryption process involves the plaintext and a shared key between the sender and receiver.
And this is where you can find me online. So the first concept in cryptography which we'll talk about is this idea of encryption. This lets two parties, let's say Alice and Bob, to exchange information over an insecure channel like the internet without having to worry that someone who has been able to capture the messages that are being sent across, someone like Eve, can actually understand the meaning behind these messages. And this is why we do encryption.
So the first thing we need to consider in order to perform encryption is which type of encryption algorithm to use. There are quite a few of them out there. They all pretty much work by this concept of substitution and transformation upon the original message and then doing the reverse in order to decrypt. Now this part of this code that I'm showing here is actually the first bit of code from the crypto module that I'm showing today, and this is called the getCypress function. And as the name suggests, this will return a list of all the cryptographic algorithms that you can run on your machine using the crypto module. For example, when I run this on my machine, it returns 171 results. So let's pick one to illustrate this idea of encryption.
So there's a family of encryption algorithms that are known as AES, Advanced Encryption Standard. And as the name suggests, these are algorithms that are advanced enough to be used in modern applications. And it's a standard, so they are implemented across different environments. Specifically, let's take a look at AES-256-CBC. So let's break it down. So 256 applies here to the size of the key. We'll talk about the key and the role it plays in decryption in a minute. But this is essentially the part of the algorithm that actually guarantees the security and privacy of the communication. What an attacker like Yves might try to do is try to brute force and try to guess the key that is used. So if we use a key with 256 bits, it means there is an astronomical number of different key combinations that the attacker would have to try.
Now CBC stands for cipher block chaining. This is a mode of encryption where as part of the encryption process, the message is split into these different blocks. And in order to compute the very first block, you need to supply something which is called an IV or an initialization vector. And we'll talk about its role and how it makes our encryption more secure in a minute. So let's talk about the encryption process. Let's say that Yves wants to send Bob an encrypted message. He would need the plaintext, which is the original message, the key, which we'll talk about more later. We'll talk about how we decide on what value to use as the key and how to transport the key. But for now, let's just say that it is a piece of data that Yves has and Bob has to have an identical key, meaning the exact same piece of data in order to perform the decryption.
3. Encryption with Initialization Vector (IV)
To ensure different ciphertext results, a random Initialization Vector (IV) is used for encryption. In Node.js, Alice uses the createCipherIV function to encrypt her plaintext using the chosen algorithm, key, and IV. Bob uses the createDecipherIV function to decrypt the ciphertext using the same key and IV. This process allows secure communication between Alice and Bob.
Now there's also the IV I mentioned earlier. For that, we'll just use a completely random value every time we want to apply the encryption. So let's say that every morning, Alice sends Bob a hello message. So she's always using the same plaintext and always using the same key. Because she is using a different IV every time, then the encryption algorithm which they are using, which is a deterministic function, given different input every time because of the different IV, this will also return different output every time.
So then Yves sends this information, the IV, the ciphertext, and the algorithm to Bob, and Bob, using the same value as the key, can perform the decryption. Let's see how this is done in Node.js. So we have Alice's encryption code. She chooses the algorithm to use. She takes the plaintext. Notice that the plaintext is not kept as a string, but rather converted into a buffer. She generates a random IV value using the random bytes function, and she loads the key into memory. Later we'll talk about what we use as the value of the key. And then she's ready to perform the encryption by calling createCipherIV, passing in the algorithm key and IV. And then she calls update and final on the cipher object, which gives her the ciphertext, which she can share with Bob. Bob gets the IV ciphertext and algorithm, loads the identical key into memory from his copy, and then there is a very similar looking API, creates decipherIV, and calling update and final on the decipher object. And this will get back the original plaintext that Alice wanted to share with Bob.
4. Key Derivation Function and Salt
Bob gets the IV ciphertext and algorithm, loads the identical key into memory from his copy, and then there is a very similar looking API, creates decipherIV, and calling update and final on the decipher object. So we talked about the key. We don't want to use a human memorable password as the key. Instead, we can use a key derivation function (KDF). A KDF is similar to a hash function but is purposefully slow, which slows down attackers. To further enhance security, a random value called the salt is used as a second parameter. This ensures that even if well-known passwords are obtained, the attacker still needs to run the KDF function for each iteration with the salt value. In Node.js, the s-script KDF from the Crypto module can be used, along with two other built-in KDFs.
Bob gets the IV ciphertext and algorithm, loads the identical key into memory from his copy, and then there is a very similar looking API, creates decipherIV, and calling update and final on the decipher object. And this will get back the original plaintext that Alice wanted to share with Bob.
So we talked about the key. What value can we use as the key? We might be tempted to use a human memorable password as the key itself, because it's easy to remember. But this is a problem. We mentioned before that we have a large space of options to store the key, right? With 256 bits. If Eve the attacker can assume that Alice is using a human memorable password as the key, then Eve doesn't really have to try all combinations within that really big space of options. She instead can limit herself to a much narrower set of human memorable passwords.
So this is why we don't want to use a human memorable password as the key. So what can we use if we still want to rely on something which we remember as the basis of our security and our key? For this, we can use something which we call a key derivation function, or KDF for short. A KDF is similar to a hash function, which you might be familiar with from checking the integrity of files. It is a deterministic one-way function, and you give it the human memorable password and it expands it. The output is something that will provide a much stronger, longer key, which we can use for our encryption. Unlike a normal hash function, the KDF is meant to be purposefully slow, which is not something you see often when we design things to be slow on purpose.
The reason we do this is because the fact that this function is slow won't slow down Alice, because she only has to execute this function once for the password that she remembers, but it will slow down Eve. What Eve might try to do is download the list of, let's say, the 10,000 most commonly used passwords from the internet, and then in order to derive the key from them on every iteration on that list of 10,000 options, she will need to run this expensive KDF function. This is going to slow down Eve, the attacker. This sounds good. We understand the value of the KDF. But if you think about it, there's still a part missing because what Eve can do is not go to the internet and just download a list of the 10,000 most commonly used passwords. She can download a list of pre-computed values when those common passwords are passed through some well-known KDF that she can assume that is used in the encryption. And if this is the case, if this is possible, then the KDF is not slowing her down at all, and it's just making life more complicated for Alice and Bob.
So this is why crucially in a real-world scenario with a KDF, there is a second parameter, not just the password, but another random value, which we call the salt value. So even if Eve has access to our salt, in order to try these well-known passwords, she will have to pay the cost of running the function for each iteration with the salt value. And this is what is making sure that the KDF is really slowing her down and preventing her from brute forcing our password. So let's see how this is done in Node.js. We can take a look at the s-script KDF, which is a well-known KDF, secure crypt. It's available inside the Crypto module. And you can see we're taking the password, generating random bytes as the salt, passing those to the s-script function, and we get back a key which we can use. Another notable mention here, we have two other KDFs baked into the Crypto module.
5. Randomness and Key Agreement
We have HKDF and PBKDF2. To generate random values in Node.js, avoid using math.random() and use the random bytes, random fill, random int, and random uuid functions from the Crypto module. To agree on a shared key, the RSA system is used, where Bob generates a private and public key, and Alice can encrypt a message using Bob's public key.
We have HKDF and PBKDF2. All right, so as we can see, being able to generate random values plays a major role in cryptography.
The first rule of generating randomness in Node.js and in JavaScript is don't be tempted to use the familiar math.random() function because it is not considered cryptographically secure. Luckily, inside the Crypto module, we have a function that lets us generate random values. We have the random bytes function that returns a buffer with random values, random fill that replaces values in an existing buffer with random values, random int that returns an integer in a given range, and random uuid.
Now, let's talk about agreeing on a shared key between two parties. In online communication, they don't have the luxury of meeting offline to decide on a key when they know no one is listening. In the 1970s, scientists came up with an elegant solution called RSA. In this system, Bob generates a private key and a public key. The public key is made public, while Bob keeps the private key secure. Alice can use Bob's public key and the plaintext to encrypt a message, which Bob can then decrypt using his private key. This solves the key distribution problem.
6. Key Distribution and RSA
To agree on a shared key, we can use the RSA method. In this system, Bob generates a private and public key, and Alice can encrypt a message using Bob's public key. To perform encryption and decryption in Node.js, Bob can generate a key pair using the generateKeyPair function and export the public and private keys. Alice can encrypt a message by reading Bob's public key and calling public encrypt, while Bob can decrypt the ciphertext using his private key.
That's one way of deciding on the value of the key. But how do we agree on two parties having the same key? If Alice and Bob are only talking online, then they never have the luxury of meeting offline and deciding on the key there when they know that Eve isn't listening in. So this is a big problem, and this is something that Alice thought long and hard about. And frankly, I thought long and hard about it as well. And actually, this was a major problem in cryptography that many smart people thought about for many years.
Luckily, in the 1970s, a group of scientists published an elegant solution to this problem. And the method that they came up with is known after their initials, RSA. Let's take a look at their solution. So in this type of system, Bob generates two keys. His private key and a public key. And there is a mathematical link between the two. As the name suggests, the public key is made public, and the private key, Bob keeps that secure somewhere where only he has access to it.
So now, if Alice wants to send Bob a message, she can take her public key, sorry, Bob's public key, and the plaintext, and use them to perform the encryption and get the ciphertext. She can then send the ciphertext to Bob. And then Bob does the decryption using his private key. So by using a different key for encryption and decryption, we're able to kind of circumnavigate and avoid the key distribution problem altogether. So let's see how this is done in Node.js. First, Bob wants to generate a key pair. He doesn't have to do this only for the correspondence with Alice. He can use the same key pair for communicating with everyone. So he kind of only has to do this once if he wants. So we use the generateKeyPair function. And we say that we want to use this for RSA communication. And then he has access to the public key, which he can export to file and share, and the private key, which he can export to file and store away where only he has a copy to it.
Now, if Alice wants to perform the encryption, she can read Bob's public key into memory. She can take the original message, convert it into a buffer, and call public encrypt on it in order to get back the ciphertext. Passing in the public key, the plaintext, and a constant value, choosing which type of padding to use. Now, to perform the decryption, Bob gets the ciphertext, he reads his private key into memory, and he calls private decrypt from the Cryptol module, passing in the similar parameters. And what he will get back is the original plaintext message. So as you can see, this is a pretty elegant solution to the key distribution problem.
7. Notable Mentions and Signing
There are other notable mentions in cryptography, such as the Diffie-Hellman key exchange and elliptical curve Diffie-Hellman. In a world where encryption is possible, the issue of verifying the sender's identity arises. To address this, signing and verifying can be used. By generating a signature using a private key and verifying it using a public key, the authenticity of a message can be ensured. In Node.js, signing is performed using crypto.sign, and verification is done using crypto.verify. Additionally, the crypto module includes the createHMAC function for hash-based message authentication code.
We also have other notable mentions here. There was another group in the 1970s which published a different solution to the key distribution problem. And their solution is called the Diffie-Hellman key exchange. It's also available in the Cryptol module. And they have another, the same group has another algorithm that is widely used today, which is called elliptical curve Diffie-Hellman, which is also available.
So if you think about it, now we've come far along, and with this system where everybody has a private key and public key, it might seem like there are no more problems in cryptography. But if you think about it, in this type of world, anybody can send me an encrypted message. But how can I know that that encrypted message really came from a certain entity that is who they claim to be? This is where we talk about signing and verifying.
So let's say that Alice is an organizer of JS Nation, and she wants to send Bob a ticket. Bob wants to walk up to the venue door and say, look, I know Alice, Alice wants me to get in. And the only way they're going to let him in is if he can prove that that message really came from Alice. So to do this, Alice will take her private key and the message that she wants to send Bob. In this case, the message itself does not need to be encrypted. It's fine if other people can read the message. And she will generate a signature. A signature looks like a hash output, and it's like gibberish. It's just a cryptographic tool, and we'll see how to use the signature in a little bit.
And then she generates the signature. Her public key is known to everyone, and she sent Bob the message and the signature. Now, Bob wants to be able to prove that he has this message from Alice, that it really came from Alice. So he can take Alice's public key, the message and the signature, and perform the cryptographic verification. And this will only return that the verification succeeded if indeed the public key, the signature, the message, and the private key all match. So only someone with access to Alice's private key can create a signature that will be verifiable with Alice's public key.
Let's see how this is done in Node.js. To perform the signing, Alice will read her private key into memory. She will take the message that she wants to sign. And she will call crypto.sign, passing in the hash function to use as part of the signature process, the message, and the private key. To perform the verification, Bob, or anyone else, can take the message and the signature, read Alice's public key, and call crypto.verify, passing in the similar parameters. And this will only return true for is verified if all of the cryptography and math all match up. Another notable mention here, we have inside the crypto module a function called createHMAC, hash-based message authentication code.
8. Public Key Certificates and Verification
This is an alternative approach to authenticating using public key certificates. Certificates help establish ownership of a public key, and a trusted third party like Carol can verify the authenticity of the key. Alice sends a certificate signing request to Carol, who then creates a signature using her private key. Bob can verify the certificate using the signature, metadata, and Carol's public key. Node.js provides the X.509 certificate class for this process.
This is an alternative approach to authenticating. So what does this really prove? What did we gain when we introduced this signing and verifying? Can someone like Bob know for sure that the message really came from Alice? In a way, yes, but in a way, no. Because all this proves is that someone with a private key that matches a public key for someone claiming to be Alice sent that message. But how do we know that it really was Alice, the entity, Alice, the person who sent that message? This is where we get into public key certificates.
Public key certificates help establish ownership of a public key. So if Bob wants to get a message from Alice and be able to know that it really came from Alice, there's kind of like no magic solution. They need a third party, someone who knows both of them, or that both trust. And this third party will help establish the trust between them. So I want to introduce you to Carol, which you can tell by her clothes is Bob's sister, and she's also a friend of Alice. And she will help out in this process.
So in order to prove that the public key that Alice is using really belongs to Alice in a way that Bob will find reliable, then Alice will use Carol's help. She will create a certificate draft. It will contain Alice's public key and some metadata about Alice, like her name and her country. And this is what we call the TBS to be signed. She will send this as a certificate signing request to Carol. Carol will do her due diligence to make sure that this is really Alice. And when she's satisfied, she will create a signature of the TBS using her private key. And then Alice will take the signature, and with it, she'll have her final certificate.
Then Bob, before he can actually trust that this is Alice's public key, he will perform the verification similar to the process we showed before using the signature, the metadata, and the public key from the certificate, but he will verify it using Carol's public key. So let's see how this is done in Node.js. First, we can use the X.509 certificate class. This is the name of the standard for how to create certificates and read the certificate from the file. Then Bob can read the metadata about the certificate. Who is the subject? Alice. Who is the issuer? Carol. Until when is this certificate should be considered valid? And to perform the verification, what Bob can do is he can call Carol's certificate, load it into memory, that contains Carol's public key. And Carol's public key as the issuer can be used to verify Alice's certificate, and only if this returns true, Bob can know that he can trust the certificate.
9. Trust in Certificate Hierarchy
To establish trust in Carol's certificate, Bob can rely on Doris's certificate. Trust in certificates is built upon a hierarchy or chain, where the top certificate is trusted because it is part of the operating system or browser.
Now, you might be wondering, well, okay, that's fine. But if Carol's certificate is used in the process, how can Bob know to trust Carol's certificate? Well, how do you begin to trust your sister? You do it because your mom tells you so. So this is Doris. She's, you know, their mom. Bob can use Doris's certificate to trust Carol's certificate. And what this is trying to illustrate really is that, again, there's no magic solution here. In order to trust a certificate, you need to trust a different certificate. And at the top of this certificate hierarchy, or certificate chain, there is a certificate that you just trust because you trust it, typically because it comes as part of your operating system or browser.
Comments