This is the first part of a two-part article about using the OpenSSL functionality in PHP to encrypt and decrypt data. This part will cover key generation and the second part will show you how to use those keys to encrypt and decrypt data.
The project I am currently working on requires some JSON data to be encrypted and later decrypted in a different location (potentially on a different platform). Because of the cross-platform component, we have chosen to use OpenSSL.
Rather than just showing PHP code, I am going to explain what is going on. I won’t cover signing or certificates as this article is long enough as it is.
Nor will I be dealing with sockets. Although SSL stands for Secure Sockets Layer, this article is about using OpenSSL to encrypt and decrypt data rather than about transmitting it from one place to another.
Symmetric and Asymmetric encryption
For centuries, encryption/decryption was symmetric. This simply means that you use the same key to encrypt and decrypt your message. Decryption just reverses the encryption process. Even complex codes such as Enigma used symmetric encryption.
In the 1970s, asymmetric encryption was invented. This uses a pair of keys which are related to one another. Unlike symmetric encryption, once a message is encrypted with one key you cannot decode the encrypted version of the message with that key. The process to decrypt the message is not to run the encryption in reverse, but to decrypt it using the other key.
These two keys are usually referred to as the public and private keys. The public key can be given out freely because it cannot be used to decode an intercepted message. The private key is the one that must be kept safe.
Asymmetric keys are essentially very long numbers that relate to one another. When the keys are generated, two large prime numbers are chosen. Multiplying these two numbers together is trivial, but taking the resulting number and trying to deduce the two prime factors would be an incredibly time-consuming task, even with large amounts of computing power. The larger the numbers, the more time-consuming the task becomes.
The private key contains the two prime numbers. The public key contains the product of these two numbers (ie. The two primes multiplied together).
When the keys are generated, you specify the size of the key in bits. These are typically powers of 2 (512, 1024, 2048, etc), although they don’t have to be. Larger keys are more secure because of how long it would take to calculate the two prime numbers, but there are also disadvantages. It takes longer to encrypt data with a larger key and a larger key can also generate a longer encrypted message. Beyond a certain point, increasing the size of the key only really adds overhead without making things any more secure. Depending on your circumstances, you may decide to choose a shorter key to keep speed and storage/bandwidth down. As with many things in life, it’s a compromise.
OpenSSL and PHP
So let’s look at some of this in practice. I’ll start by generating a 1024-bit private key using the OpenSSL functions available in PHP (provided they have been compiled in).
This code snippet generates a new 1024-bit private key and exports it to a file called private.key. You can specify a different key size if you like (the minimum size is 384 bits). The larger a key is, the longer it takes to generate.
The resulting file looks like this:
—---–END RSA PRIVATE KEY—---–
As you can see, the key details are base64 encoded.
If I were to run the script again, I would get a different private key because different random primes would be picked.
If I want, I can even password-protect the private key. In this example I’m exporting to a different filename:
This gives a longer private key file:
---—–END RSA PRIVATE KEY—---–
Note that the password is only applied when the private key is exported to file. The private key itself is not affected by this password, but you will not be able to load the key from the file without this password.
Adding a password is probably a good idea when a human is using the key, but probably of less value when the process is automated. Loading an encrypted private key uses more processor resource than loading an unencrypted one. If a server is compromised and the private key obtained, the chances are that the file containing the password would also be obtained. It is much better to protect the server itself than trying to protect the private key with a password.
Obtaining the public key
Having generated the private key, we next need to get the corresponding public key. The openssl_pkey_get_details() function provides some information about the private key:
The result tells us the size of the key as well as the public key for this private key:
[bits] => 1024
[key] => —---–BEGIN PUBLIC KEY—–---
—---–END PUBLIC KEY—---–
[type] => 0
If your implementation requires a server to have access to both public and private keys (for example, it may generate the key pairs and send the public key to a user or another server), this means that you do not need to store both keys in files or in a database. Storing the private key is enough, since you can get the public key from the private one.
Writing the public key to file is simple enough, using file_put_contents() or similar.
Finally, it’s not a bad idea to free a key once you have finished with it – in the same kind of way that you would close a database connection once you’re done with it. It’s not strictly necessary under normal circumstances since it normally happens at the end of a program and resources will be freed up once the program ends. But it’s a good habit to get into.
The full code for generating a public/private key pair looks like this:
This code is obviously very bare-bones and you would probably want to add error checking at various stages, but I am keeping the code simple for this article.
In the second part of this article I will show how to use the public and private keys to encrypt and decrypt data.