Creating Public and Private Keys
00:00 In the previous lesson, I discussed asymmetric key exchange and introduced you to the concept of a m Certificate Authority as a Trusted Third Party. In this lesson, I’m going to drill down further and start the discussion about how to become a Certificate Authority. First off, you’re probably never going to become a Certificate Authority that’s trusted by the browsers.
00:21 This is an arduous process with a lot of bureaucracy, which is good for us. We want it that way. It should be a difficult journey. Certificate Authorities are meant to be Trusted Third Parties. Of course, this does beg the question, “Trusted by whom?” The answer to that really is Windows, Mozilla, and Java.
00:39 So, it isn’t just enough to become a Certificate Authority. You have to be a Certificate Authority that’s trusted by these three organizations so that you are listed as one of their CAs. If you’re not listed as one of their CAs, then the end user—using a browser that uses these certificates—will not trust you. As an example, Mozilla’s CA policy is publicly available.
01:01 You can take a look at what is involved in going through it. So, why am I talking about being a CA for yourself? Writing the code will help you better understand how HTTPS and certificates work. And secondly, you can use this process to self-sign certificates.
01:16 This allows you to test HTTPS locally, but it means you have to change the settings on your browser to acknowledge private Certificate Authority.
01:26 Let’s go back to talking about Alice and Bob. Alice wants to host a web server. The first thing she does is create a Certificate Signing Request, which she sends to a Trusted Third Party. This is Charlie, the Certificate Authority.
01:40
Charlie verifies Alice’s identity and then signs her certificate. Alice can use this certificate to host HTTPS on her web server. When Bob connects to https://alice.example.net
, he receives the corresponding certificate.
01:57 His browser can then verify the authenticity of this certificate with the Trusted Third Party. This is how Bob knows that Alice is who she says she is—or at least, who her web server is. To do all of this, the Trusted Third Party needs a public key, a private key, needs to be able to receive a CSR, and sign a CSR.
02:21 Alice needs a private key. She uses this private key to generate a CSR, and once her identity has been validated by Charlie, she receives the certificate.
02:31 The only thing that Bob needs is a browser that’s aware of Charlie’s Certificate Authority. If you’re using Firefox or Chrome, then these have built-in CAs for them. Inside of the settings, you can add a CA. So, for example, if you want to create your own Certificate Authority you could add it, but that’s only going to work for you and your instance of your browser.
02:54
Okay. So, let’s start becoming a Certificate Authority. In this case, you are Charlie. The first thing you need is a private key. The cryptography
library’s rsa
module has key generation mechanisms inside of it. Because the process involves a whole bunch of key management, I’m going to show you how to build a PrivateKey
class that can generate the key as well as read and write PEM files, the serialization of this key.
03:19 In addition to that, I’m going to build a separate script that actually calls this class and then generates the private key. What I’m about to show you is the first part of a fairly long example.
03:30 There’s over 200 lines of code explained in this lesson and the next one. You may find it easier to follow along if you actually have the code in hand. Don’t forget that the Supporting Materials dropdown contains a link to a ZIP file with all of the code.
03:46
This file defines the PrivateKey
class. The PrivateKey
class is a wrapper around an actual RSA key. I’ve done this so that I can have .save()
and .load()
methods associated with the key.
03:59
The first method defined here is a class method called .generate()
. This is being used as a factory. In case you haven’t seen this pattern before, the idea here is instead of creating a PrivateKey
object in the normal way, you would call PrivateKey.generate()
.
04:15
This will return a new PrivateKey
object with the key inside of it. It’s done this way because you don’t want a new key generated every single time you create the object.
04:27
You may want to create an object by loading one from a file. So the method has to be done separately. The key generation happens on lines 10 and 11. rsa.generate_private_key()
with some settings returns a key that’s going to be used.
04:43
This is stored locally in the .key
attribute of the PrivateKey
object that is being generated. Line 13 returns the object with the newly generated key inside of it.
04:54
Because you’re going to want to load and save these keys from files, the PrivateKey
object has a .save()
method. Inside of this, it takes a password
and a filename
to store the key.
05:06
The password itself needs to be encrypted. password.encode()
sets up for this step.
05:12 The library supports different mechanisms for serializing encrypted data. Line 29 is a factory for one of these serialization methods. It takes the prepared password as a parameter so that when the file is serialized, it’s serialized against this encrypted password.
05:29 You would need the same password to be able to decrypt the file to read it later. Line 35 is where the actual data is serialized into bytes and line 38 is where that’s written to a file.
05:43
The name of the file isn’t important, but typically they end in .pem
extension.
05:50
This is the generate_keys
file. This is the file that actually gets executed and generates all of the keys. By the time the example is done at the end of the next lesson, I will have generated five different keys. As such, I’ve hidden big parts of this file and at the moment, I’m just showing the first key. Line 9 creates the PrivateKey
object, from the PrivateKey
module that was just demonstrated.
06:16
Once the .generate()
method is called, that object has an RSA key inside of it. This is stored in ca_private_key
.
06:24
The object’s .save()
method is then called, creating a file called "ca-private-key.pem"
. Private keys are always password encoded, so the .save()
method takes a password, which is encrypted into the file. To use this key later, you’ll need to know this password. Here’s the resulting PEM file.
06:45 It starts with a header indicating what it is: an RSA private key. You can see information inside of it that this was encoded using AES and a 256-bit key. The contents are all ASCII.
06:58 This makes it easy to transmit—including in things like email—without having cross-encoding problems.
07:04 Down at the bottom, there’s a footer indicating the end of the data.
07:10 So far so good. You and Charlie now have a private key. Next step, Charlie needs a public key. Both the private and the public key are used when signing a CSR.
07:22
The cryptography
library also has a certificate management module called x509
for the use of public keys. Additionally, I’m going to have to add a method to the PrivateKey
object so that it can load the PEM files that it saved before. The private key will be used to generate the public key. Similar to the PrivateKey
class, I’m going to create a PublicKey
class.
07:44 There’ll be a factory inside of that for generating the public key and—just like the private key—a method for writing the public key to a PEM file.
07:54 Finally, I’ll add this to the key generating script, so when you run it it’ll generate the private key and then the public key.
08:02
To create a public key, you’re going to need your private key to be able to sign it. Earlier, I wrote the private key to a PEM file. Now, I need to add a .load()
method to the PrivateKey
object so that I can reload that PEM file and reuse it. Like the .generate()
method, this is also a class method, it’s a factory.
08:22
It creates an empty PrivateKey
object and then loads the file assigning the .key
attribute from the loaded file. The saved PEM file was saved with a password.
08:33
You will need to use that password in order to load the key. Line 18 prepares the password to be used to load the file. The cornerstone of this method is line 22. Using serialization
’s load_pem_private_key()
, it takes the encoded password and the data found in the file and returns it. That’s put on that .key
attribute of the PrivateKey
object and this PrivateKey
object is returned.
09:01
This method is an echo of the .generate()
method. Both of them create an empty object, get an RSA key, either through generating it or loading it from a file, and then return that object with that .key
attribute.
09:19
Now, on to the PublicKey
. I’m using the same pattern here. PublicKey
has a .generate()
method that is responsible for creating an object with a .key
attribute. This time, the .key
attribute will be the public key. Up until now, I’ve been a little sloppy about my terminology.
09:37
There’s a subtle difference between a public key, as a certificate, and an RSA public key. This PublicKey
object is actually returning an X.509 public key certificate.
09:52 The reason I need to make the distinction is because the RSA private key can be used to get an RSA public key, which is associated with this certificate.
10:04 You’ll see how these pieces fit together in a second.
10:08
The certificate has information about the certificate holder in it. This is called the subject. The make_x509_name()
function is defined in a utils
(utilities) file, and it takes a list of name attributes—like your name and where you live—and creates this subject
object.
10:26
That subject
object is passed to a builder
. The builder
is a factory for building certificates. The make_builder()
function is also defined in the utils
file.
10:40
The builder
requires a subject for the certificate and an issuer for the certificate. Because this is a self-signed certificate, the subject and the issuer are the same.
10:52
The final parameter to make_builder()
is a flag to indicate whether or not I’m building a CA certificate. There’s a small change in behavior necessary when building a CA certificate, and I want to be able to reuse make_builder()
, so I pass in this flag.
11:08
Once the builder has been created, it’s used to generate the actual certificate public key. Notice that it’s using the RSA’s private_key
corresponding .public_key()
method.
11:21 This is what I meant before by being sloppy about my terminology. What the builder is ready to build is a signed public certificate. This is distinct from the mathematical key that RSA uses, which is the public key inside of the certificate.
11:38
The builder gets signed with the private key, and the end result is the X.509 certificate considered the public key. Like the PrivateKey
object, I’ve assigned this to the public_key
object, and then the class returns it.
11:56
Here’s part of the utilities file defining the make_x509_name()
function. This function’s fairly simple. All it does is take the country, state, locality, organization, and hostname that are associated with this certificate and build x509.NameAttribute
s out of them.
12:17 This is the informational portion of the certificate.
12:21 I’ve put this in a separate file because it’s going to be reused later.
12:30
Elsewhere in the utilities file, I’ve defined the make_builder()
function. Certificates require a date range—the starting date they are valid to the ending date they are valid.
12:42
The date_range()
method in the utilities file returns two dates: the starting valid date, which is right now, and the ending valid date, which is some number of days from now. Line 26 of make_builder()
uses this helper function to create a certificate that’s valid for 30 days.
13:01 Let me just scroll down.
13:04
builder
is a factory for an x509.CertificateBuilder
. If you’ve ever done any JavaScript programming this pattern might be familiar to you, but if you haven’t seen this kind of call before, what’s happening here is chain calling.
13:18
This is a shortcut for writing builder = x509.CertificateBuilder()
, builder = builder.subject_name()
, builder = builder.issuer_name()
, et cetera.
13:31
Each one of these methods returns the builder
and gets called on the object returned by the previous method, essentially compounding the function calls.
13:41
This builder
sets up the different properties of the certificate. You need a subject, you need an issuer, you need a serial number—which is randomly generated—you need the starting valid date, and you need the ending valid date.
13:56
These are combined together into the builder
, as a factory, which is passed back to the public method, which uses this builder
to generate the actual certificate.
14:07
Lines 38 through 40 specify that this certificate is going to be for a CA. This is actually an updated version of this lesson. The original version did not have lines 38 through 40, and it worked fine for a certain version of curl
.
14:24
If you happen to be using curl
with a build prior to 2019, it would work without these three lines. Conversations in the forums show that it stopped working all of a sudden. In digging into this, I discovered that the newer version of curl
uses TLS 1.3.
14:41 The older version uses TLS 1.2. The requirements for the certificate have changed slightly between those, and the newer versions now require this additional extension.
14:55
This is back inside of the generate_keys
file. I’ve hidden everything but the key that I’m concentrating on, which is the CA’s public key.
15:04
This dictionary contains the locality information that is going to be included in the key. This information is passed to the make_x509_name()
method that I just showed you.
15:15
21 and 22 are where the key is actually generated and saved. Similar to before, but instead of a PrivateKey
, a PublicKey
object is called. The .generate()
method is the factory.
15:27
It takes the ca_private_key
object instantiated earlier in the file, and this name_dict
(name dictionary) with the locality information.
15:35
This call also has is_ca
set to True
to explicitly require that this is a public key for a CA. Once .generate()
is called, the PublicKey
object now has a key inside. All of this is stored in ca_public_key
.
15:51
The .save()
method takes the key and serializes it into a "ca-public-key.pem"
file. Because this is a public key, no password is necessary.
16:06 Here’s the resulting public key certificate. Like the private key, It has a header line explaining what it is, easy to transfer ASCII values, and a footer line defining the end of the data.
16:20 That was a lot of code. You tired yet? Well, there’s still more to come. Next up: coding like a CA.
Become a Member to join the conversation.
Jeff Anderson on Nov. 11, 2020
This is by far the best instruction on web HTTPS that I have ever found. This is awesome!