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.
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.
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
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.
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
.load() methods associated with the key.
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
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.
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.
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.
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.
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.
The name of the file isn’t important, but typically they end in
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.
.generate() method is called, that object has an RSA key inside of it. This is stored in
.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.
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
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.
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.
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.
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
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.
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
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.
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.
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 is passed to a
builder is a factory for building certificates. The
make_builder() function is also defined in 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.
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.
Once the builder has been created, it’s used to generate the actual certificate public key. Notice that it’s using the RSA’s
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.
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.
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.NameAttributes 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.
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.
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.
builder is a factory for an
This is a shortcut for writing
builder = x509.CertificateBuilder(),
builder = builder.subject_name(),
builder = builder.issuer_name(), et cetera.
Each one of these methods returns the
builder and gets called on the object returned by the previous method, essentially compounding the function calls.
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.
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.
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
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.
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.
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.
21 and 22 are where the key is actually generated and saved. Similar to before, but instead of a
PublicKey object is called. The
.generate() method is the factory.
It takes the
ca_private_key object instantiated earlier in the file, and this
name_dict (name dictionary) with the locality information.
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
.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!