At work I recently gave a presentation about the practical aspects of public key cryptography with the OpenSSL toolkit. I showed many openssl command line examples and when I was asked for a transcript I decided to expand the material and publish it here in the hope that it’s useful for others too.
If you see broken examples or broken English, please let me know.
Thanks to Kaspar Brand for reviewing the material and providing additional information and links.
There are four parts covering these major topics:
- Part 1: Public Key Cryptography
- Part 2: Public Key Infrastructure (PKI)
- Part 3: Inspecting and Creating X.509 Certificates
- Part 4: OpenSSL Bag of Tricks
This is part 1, about Public Key Cryptography. This part shows how to create RSA private and public keys and then use those keys to encrypt/decript and sign/verify information with openssl.
This assumes that you know what public key cryptography is about. If you don’t, read the Wikipedia article, especially the postal analogy. Wikipedia summarizes:
Public-key cryptography is a method for secret communication between two parties without requiring an initial exchange of secret keys. It can also be used to create digital signatures. Public key cryptography is a fundamental and widely used technology, and enables secure transmission of information on the Internet.
It is also known as asymmetric cryptography because the key used to encrypt a message differs from the key used to decrypt it. In public key cryptography, a user has a pair of cryptographic keys—a public key and a private key. The private key is kept secret, while the public key may be widely distributed. Messages are encrypted with the recipient's public key and can only be decrypted with the corresponding private key. The keys are related mathematically, but the private key cannot be practically derived from the public key.
Creating Keys
So we need to create a key pair and we start with the private key.
The openssl command line utility provides a lot of functionality. To keep it manageable, this functionality is broken down into various subcommands. The subcommand to create an RSA private key is genrsa. In its simplest form, it works without parameters:
openssl genrsa
Generating RSA private key, 512 bit long modulus ....++++++++++++ .++++++++++++ e is 65537 (0x10001) -----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBALWv2VcerryVqaj0AZ/tTLUa3L+sf/CVl27gul4l5B/F/FbjpYB1 4HOuewKCXqJqUiNJechliFsi7XqWedOta4MCAwEAAQJBAIaiwic6GHitW2VVNGMD 4WUPe+gZ91EcOXSz2cdswsj7l+LrjiNkOVClQANfZiP/d9YSFsk8WzyHnTtlbgun VeECIQDvnwNdWJ6Q3y+46s+d2FSe1P22lb5qMuSQYUhDLqZScwIhAMIbFFHW2eKB 3mRCqY6oSY9pK1r6QSssL/QGuIMKe26xAiEA7JgAFXn7zqVgFGBcsMi5/L3m6RH/ mhI4FcrIM/VqqbsCIEgklGrOdfDv395XwHlbJuv8ZLbNqIcJR1FlZhKeRL4hAiEA nehFFepANrjgjPU6iqM/5T6454mqDpQmOQzZHlDh+Ys= -----END RSA PRIVATE KEY-----
That outputs the private key in a plain text format called PEM on the console. We’ll want it in a file instead:
openssl genrsa > private-key.pem
Another openssl subcommand called rsa, and specifically its option -text, lets us inspect what exactly is in the private key:
openssl rsa -text < private-key.pem
Private-Key: (512 bit)
modulus:
00:bc:42:41:6e:cd:07:28:0d:0e:27:dc:79:e4:0e:
fc:9e:f8:74:5d:db:42:82:c1:f9:97:9a:9a:0b:d2:
2d:76:e0:04:74:34:5b:de:c6:67:58:47:5d:12:84:
e6:a1:d8:dd:94:67:71:d7:2a:00:4d:27:67:68:5f:
ef:a3:94:e8:e9
publicExponent: 65537 (0x10001)
privateExponent:
77:dd:b7:cb:d4:bf:b9:c0:96:42:cb:1e:d1:16:b9:
c2:7e:6f:99:53:02:06:3f:7b:ac:0b:c4:09:c2:c9:
2f:e6:5a:a3:e3:a7:0c:bd:ef:59:00:55:8c:79:14:
35:82:3e:4f:41:0c:5a:cf:e7:b2:09:9f:ba:3a:7c:
be:a7:b0:01
[...]
The private key is sensitive information so it shouldn’t lie around on the file system unprotected like that. openssl’s -des3 option encrypts the key with a password:
openssl genrsa -des3 > private-key.pem
Generating RSA private key, 512 bit long modulus ......++++++++++++ ....++++++++++++ e is 65537 (0x10001) Enter pass phrase: Verifying - Enter pass phrase:
The key now looks like this:
-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,93CD4E9C3BD610A5 /gwqg7Vp+y9mN/J8qyzOxBUX5lnVMpwYBe/EMPDcC1KDSj//vSfcRzta/lxT8MPS 7JA+wYajxTNnRoGZCip73GgtfHjiHpf7LlNPHTinDgD0La8ZmNET507ciRmOgRCZ y8+mmaFUcsgBmn5e/mtFJbFnbv/MCwIEpQ0ootfCgwoqicKG6y+D/3qoR1TxTUTE sV00+n7BN6s7yZftmaXZANVz8DBbhnAgOQZBHJkAQ0vedIlL2wOWm8MdOUOxeGTY Y+AzaOy9FJg6rGdiuB4xGKUUXzrVw8ophD2q3cK+ha4RxQiWPzrhLZsjcffXMLEF U3FLG+ivqDd+v1EgpO213kbPaFH0IjzoWsR8vwEjY6N9T8wYha0mEfAM2HDL2kuk gohlHnrkFdZMbuqc8jQ6cAhcFrRgzAM+8Bm+yc4Duvw= -----END RSA PRIVATE KEY-----
Note the password prompt and the additional header lines indicating the encryption.
With this protection, openssl asks for the password every time the key is used. For example, the rsa -text command from above now behaves like this:
openssl rsa -text < private-key.pem
Enter pass phrase: Private-Key: (512 bit) [...]
Sometimes there’s a reason to store the keys unencrypted, for example when systems like a web server need to be able to load the key automatically. For clarity, I also omitted the encryption in the examples below.
genrsa’s default settings generate private keys with 512 bits but more would be better. To generate longer keys, add the number of bits as argument:
openssl genrsa 1024 > private-key.pem
This key is longer:
-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDLVqzJbJMPxpUnUlhv8x1qd6dM2peHYpP3PbTpxBFYXmfPzqMt XYpdpcLPHP/DGuabkWcfUtlfBuqFy/03aVI24xchYUAvuiJsfPbLErufFfYwUdW/ Bp0xIp/pptjzyOsx86FCroGQ/8VWBnMkmeG39i8sfXYSQt5Jptr1OnSJoQIDAQAB AoGAcrO1Cnalyothzemkm5oPqORQSokwWx2fjCOR7yA8DJZMhyvwXoHasJL1/nFE UWHWXkE6Y7APkIxNNSZp850E05G42IEYuvEDYp74WKL9Cf+db4t/JJeR+3Vcrila poMhkAevuM4QWPmacTu58B2CiJI5GiQxf7w2m4Qm2EtMpuUCQQDvYDmOYRwcZF4Z ScBeKCI8Atekr7gmfAo83IxRJ4goLtp12/ou6frIFD/dG9dLkWCtTMcD16Bs8GjI Mvo8fkP/AkEA2XXBl71VCu+iAEysVwL0fDiyRNRs4rOWvH2oHxmP7cQKetDElX50 6ro9/4yU9bbnBddrDeoQKFS9dVl1O66yXwJBAIn9K1CjNEQ9q2zice0VL57ueIbr 8LjmmjQ5Yv3JTxjy4WY9l7wBj1pVKfd4/CQIuvVLpBHX9Be4gn6dvFiw5NsCQQDA 74tdt6vWUhonrd9SK9sCw8LW8qnQ0YmxnsMlJKbgS4kIwmVIRbQx9h+/tdkL0MVU F3ZyF/NR9S1LCp3K4Ap9AkEA4Ee88r/hkRih3C2sajIpXire7h7rhry5NaNzND7B q2FpVqaJfoJvhsTIX8ju/pzF6WbVL6VjrY4OJ3miLWGBEg== -----END RSA PRIVATE KEY-----
Now we’ll extract the public key into a separate file, again with the rsa subcommand:
openssl rsa -pubout < private-key.pem
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIxa9c7tYDvCL5ZCk5FGT2Zlo2 nYLEpX1KocTjfggXjrnL6+92HZMJs2jcbxp6Q4lyMPhlGP3t7/DCBpXM39v7w3U7 HRy0GAxa7tWICCnvXZRoI2BKF3bZDuWSeNyHXJtkJwnId6udn7eLj2q5TdTxc8On JpCKjheJ/xs9fFHmSwIDAQAB -----END PUBLIC KEY-----
And again we’ll want this in a file:
openssl rsa -pubout < private-key.pem > public-key.pem
A tip regarding openssl’s man pages: Each subcommand has its own manpage, so to get information about genrsa you would type man genrsa and not man openssl.
Using the keys
Now we have a key pair. As noted above, this allows us to:
- encrypt and decrypt a message to make sure that it is not read in transit
- sign and verify a message to make sure that it is not altered in transit
It is possible to combine these operations, ensuring that a message is neither read nor altered in transit.
Encryption and Decryption
To send an encrypted message, the sender encrypts the message with the recipient’s public key (what we stored in the file public-key.pem above). How exactly the sender gets this key is not discussed here but it is important and the topic of the next part in this series, “Part 2: Public Key Infrastructure”. For now let’s assume that sender and recipient met in person and exchanged their public keys.
The recipient uses his or her private key (what we stored in the file private-key.pem above) to decrypt the message.
Our example message content to be protected is the string “Alice in Wonderland”, stored in the file test.txt:
echo "Alice in Wonderland" > test.txt
Let’s encrypt this content. The openssl subcommand is rsautl:
openssl rsautl -encrypt -pubin -inkey public-key.pem < test.txt > test-encrypted.txt
test-encrypted.txt now contains the encrypted message as unreadable binary data. This block of data is what’s sent over an insecure channel to the recipient.
The recipient also uses rsautl to decrypt the message, using the private key:
openssl rsautl -decrypt -inkey private-key.pem < test-encrypted.txt
this outputs
Alice in Wonderland
What if we want to send a much longer message? Lets’s try the list of English words in /usr/share/dict/words:
openssl rsautl -encrypt -pubin -inkey public-key.pem < /usr/share/dict/words > test-encrypted.txt
This fails with a “data too large” message as rsautl can’t encrypt arbitrary amounts of data:
RSA operation error 37951:error:0406D06E:rsa routines:RSA_padding_add_PKCS1_type_2:data too large for key size:rsa_pk1.c:151:
To handle this, we use a clever combination of the public key encryption we already have with a symmetric key algorithm, for example the AES block cipher.
We first encrypt the data with the symmetric key algorithm, which can handle large amounts of data. Such an algorithm requires a key too, and sender and recipient use the same key to encrypt and decrypt the message, which is why it’s called symmetric. Normally sender and recipient need to agree on a shared secret key before exchanging messages, but in this use case, the sender just makes up a random encryption key for the symmetric algorithm and then encrypts the message content with the key. Next, the sender encrypts the made-up, random key with the recipient’s public key. The symmetric algorithm key is small enough (comparable to our “Alice in Wonderland” in size) that rsautl can process it.
The message we send to the recipient consists of the large data block encrypted (symmetrically) with the random key and the (asymmetrically) encrypted random key itself. Both are unreadable to someone intercepting the message in transit.
This combination of symmetric and public key encryption has another advantage: Processing the same amount of data with a public key algorithm is much more computationally intensive than with a symmetric key algorithm. Many encrypted real-world communications protocols such as SSL/TLS and S/MIME use this combination.
Let’s work through an example. First we decide on (make up) a random key for the symmetric encryption and stick that key into a file. We’ll use the incredibly hard to guess “foobar”:
echo -n foobar > test.txt
Then we encrypt the large word list file with the AES cipher using this password and store the encrypted data in another file:
openssl enc -aes-128-cbc -salt -pass file:test.txt < /usr/share/dict/words > words.dat
(Technically, “foobar” is not the key but a password from which openssl derives the actual key used by the algorithm.)
Now we asymmetrically encrypt the small password file with the recipient’s public key. You already know how to do this, because it is exactly what we used above:
openssl rsautl -encrypt -pubin -inkey public-key.pem < test.txt > test-encrypted.txt
It’s exactly the same except that instead of “Alice in Wonderland” we encrypt “foobar”.
We now have the two parts that together make up the protected message, they are in the two files words.dat and test-encrypted.txt. These two have to be sent to the recipient, and if someone intercepts them in transit, they are useless.
For the recipient of the message, the first step is to decrypt the symmetric encryption key. Again, you already know how this works because it is identical to the rsautl example above:
openssl rsautl -decrypt -inkey private-key.pem < test-encrypted.txt > test.txt
test.txt now contains “foobar” and we can use this to decrypt the word list:
openssl enc -aes-128-cbc -d -pass file:test.txt < words.dat > words.txt
You might be wondering why we don’t simply use the symmetric algorithm directly to exchange data, instead of going to such lengths to combine the two methods. The reason is that asymmetric keys have desirable properties regarding key distribution and ownership, especially when encryption is used on a large scale, such as between millions of users on the Internet. The Wikipedia article linked above has more information.
Signing and Verifying
Here’s a look at the other operation we can perform with a key pair, signing and verifying messages. As noted above, this lets the recipient determine if the message has been altered in transit.
As with encryption, sender and recipient must have exchanged their respective public keys before they can exchange signed messages. In this use case, the sender uses his or her own private key to sign the message.
Signing is actually encryption with reversed keys; encryption is performed using the private key and decryption using the public key. Here’s an example of this fact, with some help from Alice again:
echo "Alice in Wonderland" > test.txt
rsautl is also the subcommand for signing and verifying:
openssl rsautl -sign -inkey private-key.pem < test.txt > signed.dat
signed.dat now contains the signature data, which is the message data “Alice in Wonderland” encrypted with the private key. It’s unreadable binary data.
Anyone with the public key can decrypt this data with rsautl’s -verify option:
openssl rsautl -verify -inkey public-key.pem -pubin < signed.dat
This yields:
Alice in Wonderland
Now let’s consider how this helps us. Using the keys this way means that everyone can read the message, as the public key is, well, public. So how can this possibly be useful?
It’s useful because the recipient being able to successfully decrypt the message using the sender’s public key also means something else: It means that the message in its decrypted form must have been sent by the sender who gave us the public key (because only the sender has the related private key), and the message must have been sent exactly the way it came out of the decryption process. It tells us unambiguously that the message was sent by who we think sent it, and that it has arrived exactly the way it was sent.
You saw during the discussion of encryption/decryption that the amount of data that can be processed directly with the asymmetric algorithm is limited, and our little “Alice in Wonderland” message was short enough but most real-world messages are not. If we want to transmit our English word list as a signed message, it won’t work because the data is too big:
openssl rsautl -sign -inkey private-key.pem < /usr/share/dict/words > signed.dat
RSA operation error 79490:error:0406C06E:rsa routines:RSA_padding_add_PKCS1_type_1:data too large for key size:rsa_pk1.c:73:
Like with encryption/decryption, we can use a clever combination of the public key algorithms with a second technique to solve this problem. In this case the second technique is a cryptographic hash function. If you don’t know what a hash function is, take a look at the Wikipedia description first. It says:
A cryptographic hash function is a deterministic procedure that takes an arbitrary block of data and returns a fixed-size bit string, the hash value, such that an accidental or intentional change to the data will almost certainly change the hash value. In many contexts, especially telecommunications, the data to be encoded is often called the "message", and the hash value is also called the message digest or simply digest.
The ideal hash function has four main properties: it is easy to compute the hash for any given data, it is extremely difficult to construct a text that has a given hash, it is extremely difficult to modify a given text without changing its hash, and it is extremely unlikely that two different messages will have the same hash. These requirements call for the use of advanced cryptography techniques, hence the name.
Our English word list is the “arbitrary block of data” mentioned in the description.
openssl has the dgst subcommand for cryptographic hash functions. We choose the SHA1 algorithm:
openssl dgst -sha1 < /usr/share/dict/words
The resulting digest:
a7d455ffb810853b51dfd3613af24333ecc603e9
Adding (or changing) just a single letter to the word list results in a completely different digest, clearly indicating that the input data has changed:
echo -n x >> /usr/share/dict/words openssl dgst -sha1 < /usr/share/dict/words
20b920565a9351b259deb11acd6e12867dec8240
The message digest is always of the same fixed length, 20 bytes in the case of SHA1. This is short enough for the public key algorithms. This means that we now have all the pieces in place to sign arbitrarily large messages.
The sender of the message begins by calculating the digest for the message data:
openssl dgst -sha1 < /usr/share/dict/words > digest.txt
The sender signs the digest with the private key:
openssl rsautl -sign -inkey private-key.pem < digest.txt > digest-signed.dat
The signed message to the recipient consists of two parts: The message payload, in this example the /usr/share/dict/words file containing the word list, and the signed digest, digest-signed.dat.
The recipient first reverses the signing operation, which if successful yields the digest of the payload data as determined by the sender:
openssl rsautl -verify -inkey public-key.pem -pubin < digest-signed.dat > digest-sender.txt
If this doesn’t work, the recipient already knows that something is wrong.
Otherwise, the next step is to calculate the message digest of the message data, exactly the same way the sender did it:
openssl dgst -sha1 < words > digest-local.txt
If the two hash values in digest-sender.txt and digest-local.txt are identical, the recipient knows that the message arrived exactly as the sender sent it. If they differ, the message was altered in transit:
diff digest-{sender,local}.txt
1c1 < a7d455ffb810853b51dfd3613af24333ecc603e9 --- > d0b081cb449533c4c9ad83595c1b35a17f12e625
Note: In my experience, it’s often a source of confusion that the key types are reversed for encryption/decryption (sender uses a public key, recipient uses a private key) and signing/verifying (sender uses a private key, recipient uses a public key), so think about this for a moment to keep it straight in your head...
As a reward for making it all the way through this part, here’s a comic for you :-)
What’s next
This part introduced the tools needed to exchange encrypted and signed messages. The next part will show the practical problems of public key exchange/distribution and how a Public Key Infrastructure helps with that problem.
|



