This is the second part of a two-part article about using the OpenSSL functionality in PHP to encrypt and decrypt data. Part One covers key generation. This part shows you how to use those keys to encrypt and decrypt data.
Loading a key file
There are a couple of ways to encrypt data using OpenSSL. But before you can encrypt or decrypt, you first have to load the appropriate key from file. Most of the sample code that I have seen shows this as two steps – loading the data and then getting the key from that data. Like this:
$publicKey = openssl_pkey_get_public($fileContents);
However, the openssl_pkey_get_public() (and _private()) functions allow you to pass in a filename prefixed with file:// and the function will do the loading for you. So you can actually get your key from a file with a single command:
The above example would load public.key from a relative path (ie. The directory the script is run in). You could also load from an absolute path by specifying the entire path:
Loading the private key is done in a similar way, but since we’re about to encrypt data we want the public key at this point.
openssl_pkey_get_public() has an alias, openssl_get_publickey(). This is functionally identical.
Encrypting some data asymmetrically
If you want to encrypt a tiny amount of data, you can use asymmetric encryption to encrypt the data itself. The amount of data you can encrypt and the size of the resulting encrypted data are both determined by the size of the key.
The size of the encrypted data is the number of bytes in the key (rounded up). So for a 1024-bit key this will be 128 bytes (1024 divided by 8). Even if you were to encrypt a string with a single byte in it, the resulting encrypted data would still be 128 bytes long.
The maximum amount of data that can be encrypted is 11 bytes less than this. So for a 1024-bit key, up to 117 bytes can be encrypted.
To encrypt your data, use the openssl_public_encrypt() function. This takes three arguments:
- The string to encrypt
- A reference to a string to place the encrypted data in.
- The public key to use for encryption.
It returns a boolean indicating whether encryption was successful or not.
$publicKey = openssl_pkey_get_public('file://public.key');
$encrypted = '';
if (!openssl_public_encrypt($plaintext, $encrypted, $publicKey))
die('Failed to encrypt data');
openssl_free_key($publicKey);
The resulting data is binary. A base64 encoded version of the encrypted data looks something like this:
4oz7XLLY3EwZB5ysiGQD6nZEhWM3fD7VorTp5e4K77FN5Y2oFOV48eX3IKOsNNAtjgQoMvKKMKh9QD/F
Fxn+SDNoyHI=
Part of the encryption mechanism uses a random value as a key (totally separate from the public/private key pair). So if I encode exactly the same text a second time with the same public key, I get different encrypted data:
MAcLLcg8u8mJXSgJh7DPGX7puRHQ5K+W7Hl+NgT6e8ZpBEIYpB69XnIkN66W6Rso8lsCN4Ozez1Z4JZ6
mX45aaY/JRI=
Both of these strings would decrypt to the same string.
Encrypting longer strings – splitting into chunks
If your data is longer than the maximum size that your key can allow, you might be tempted to split your data into chunks and encrypt each chunk, something like this:
$publicKey = openssl_pkey_get_public('file://public.key');
$a_key = openssl_pkey_get_details($publicKey);
$chunkSize = ceil($a_key['bits'] / 8) - 11;
$output = '';
while ($plaintext) {
$chunk = substr($plaintext, 0, $chunkSize);
$plaintext = substr($plaintext, $chunkSize);
$encrypted = '';
if (!openssl_public_encrypt($chunk, $encrypted, $publicKey))
die('Failed to encrypt data');
$output .= $encrypted;
}
openssl_free_key($publicKey);
This example gets the key size from the public key that you’ve loaded (in this case 1024) and calculates the maximum amount of data that it can encode (117 bytes). It then splits the data into chunks of this size and encrypts each one, appending the resulting encrypted data to $output.
While this may seem like a good idea, in practice it’s not so great for two reasons.
- Every chunk of data will add an 11 byte overhead.
- Asymmetric encryption and decryption is processor-intensive.
You may be able to reduce the size of your input data by compressing it, but this will only save you a certain amount. For larger amounts of data, you need to encrypt in a different way.
Encrypting longer strings – using two encryption mechanisms
The solution to this problem is to use two different encryption mechanisms – one symmetric and one asymmetric. It works like this:
- First, a random key is generated.
- This random key is used to encrypt the data using symmetric encryption. This step is a lot faster than using asymmetric encryption.
- Finally, the random key is encrypted using asymmetric encryption. Because the key itself is relatively short, the overhead of asymmetric encryption is kept to a minimum.
The symmetric encryption does not affect the size of the encrypted data – it is the same size as the input.
The encrypted key – called an envelope – is the same size as the asymmetric key. This means that no matter how large the data being encrypted, the storage overhead will always be the same. In the case of a 1024-bit key, this would be 128 bytes.
The openssl_seal() function does all of this work for us in a single function call. Here is some sample code:
$publicKey = openssl_pkey_get_public('file://public.key');
$encrypted = '';
$a_envelope = array();
$a_key = array($publicKey);
if (openssl_seal($plaintext, $encrypted, $a_envelope, $a_key) === FALSE)
die('Failed to encrypt data');
openssl_free_key($publicKey);
The openssl_seal() function takes four arguments:
- The data to be encrypted.
- A reference to the string where the encrypted data is to be stored.
- A reference to an array to store the envelopes in.
- An array of public keys to be used when creating the envelopes.
The function returns the size of the encrypted data, or FALSE if it fails.
The symmetric encryption is done using RC4. While this encryption mechanism has some known weaknesses, these relate to using the same key more than once. Since openssl_seal() generates a new key every time, this is not considered to be a problem in this instance.
You may be wondering why the last two arguments are arrays. This is simply because openssl_seal() can be used to encrypt the same data for multiple recipients at the same time. The data is encrypted once using symmetric encryption and then the random key is encrypted asymmetrically for each of the public keys. When the data is transmitted, you would send the same encrypted data to each recipient but only the relevant envelope to the intended recipient. If you were to send the wrong envelope to the wrong recipient, they would not be able to decode it and would therefore not be able to decode the encrypted data.
If I run the example above, I get encrypted data:
and an encrypted key:
9IlU4M0+deH+1I7qsPuui0VfdjqULw9QyO6IzV14HvBR9YRen0TCZm3ezH+flSgiMCw9z8Egs0DzuaEV
zha8CHuE00c=
(As before, I have base64 encoded these in order to display them).
If I were to run this a second time, a new random key would be picked and the encrypted data would be different. You can see that the encrypted data is short, because the input data was short.
For this particular data it would probably be more efficient to simply encode the data asymmetrically as a single chunk. The choice for any given application will depend on the size of “typical” data. If in doubt, I suggest testing both methods with typical data and using the microtime() function on either side of the test to take some timings. You should test both the encryption and decryption times when doing this.
An important caveat about symmetric encryption
The asymmetric decryption we are using includes integrity checking, but the symmetric encryption does not. If your encrypted data is corrupted or tampered with, you will get different results depending on whether it was encrypted asymmetrically or symmetrically.
If the asymmetric data is corrupted, it will fail to decrypt (the exact nature of the error will vary depending on which part of the message is corrupted). But if the symmetric data is corrupted, it is likely to still decrypt but the output will also be corrupted.
One simple solution to this problem is to generate a hash of your data prior to encrypting it, using functions such as md5() or sha1(). This hash can then be included with the data when you encrypt it. After decryption, the hash can be separated from the rest of the data and checked against it to ensure it still matches.
Throw it into reverse
All of this encryption is great, but it’s useless unless you can decrypt the data again. The first step in decrypting is to load the private key.
If you exported the private key with a password, you need to specify it when loading the key:
die('Private Key failed');
If you wanted to export the key with a different password (or no password) at this point, you could use openssl_pkey_export_to_file() to do this.
openssl_pkey_get_private() has an alias, openssl_get_privatekey(). This is functionally identical.
Decrypting the encrypted data
The decryption process is very similar to the encryption process. If the data was encrypted as a single chunk using openssl_public_encrypt(), you would use openssl_private_decrypt() to decode it. The arguments are very similar:
die('Private Key failed');
$decrypted = '';
if (!openssl_private_decrypt($encrypted, $decrypted, $privateKey))
die('Failed to decrypt data');
openssl_free_key($privateKey);
If the data was split into chunks, you need to do the same thing to decrypt. This time the chunk size is the number of bytes in the key (rounded up). We don’t subtract 11:
die('Private Key failed');
$a_key = openssl_pkey_get_details($privateKey);
$chunkSize = ceil($a_key['bits'] / 8);
$output = '';
while ($encrypted) {
$chunk = substr($encrypted, 0, $chunkSize);
$encrypted = substr($encrypted, $chunkSize);
$decrypted = '';
if (!openssl_private_decrypt($chunk, $decrypted, $privateKey))
die('Failed to decrypt data');
$output .= $decrypted;
}
openssl_free_key($privateKey);
Finally, if the data was encrypted using openssl_seal(), you need to use openssl_open() to decrypt it. Unlike openssl_seal(), the last two arguments are not arrays, since there is no need to do a bulk decrypt:
die('Private Key failed');
$decrypted = '';
if (openssl_open($encrypted, $decrypted, $envelope, $privateKey) === FALSE)
die('Failed to decrypt data');
openssl_free_key($privateKey);
And that’s how you encrypt and decrypt data using OpenSSL in PHP!

Hi Steve,
Compliments for this great article. I have some querries. I am implementing a client-server app where the server end is in PHP and client is in VC++. I am using OID 1.2.840.113549.3.4 (RSA_PC4) for encryption using the server’s pub key at the client end. I need to decrypt the same at the server end using php (openssl) running on a Linux server.
Can you please guide me as to how I should be going about it?
Thanks for the great article.
Thanks for your feedback Snehamoy.
I think you mean RSA_RC4 rather than PC4. I’m not sure if that relates just to RC4 encryption, or a combination of RSA asymmetric encryption and RC4 symmetrical encryption.
If it’s just RC4 encryption, I don’t believe there is native support for it in PHP, but I did find a class for it here: http://sourceforge.net/projects/rc4crypt/ (I haven’t tried it, but it might be helpful)
If it’s a combination of asymmetric encryption and RC4, then that’s what PHP’s openssl_seal() and openssl_open() use. Openssl_seal() picks a random key for the RC4 encryption (which is relatively speedy), then uses asymmetric encryption to encrypt the RC4 key into an “envelope”.
Openssl_open() does the reverse. It decodes the envelope using the private key to get the RC4 key, then uses that RC4 key to decode the data.
This gives the advantages of public key encryption without heavy processor overhead if the data is large.
Do bear in mind that RC4 is a stream cipher and it does not have integrity checking. So if the encrypted data is mangled, the decrypted data is likely to come out mangled rather than giving you an error.
Hi, great post! Thanks..
Good tutorial. I was able to get the asymmetric code working without a problem. However, the asymmetric/symmetric code did not. The data appears to be encrypted (I think) but the decrypt is failing: Here is my code:
1024,
‘private_key_type’ => OPENSSL_KEYTYPE_RSA,
));
openssl_pkey_export_to_file($privateKey, ‘private.key’);
// Public key generation
//$a_key = openssl_pkey_get_details($privateKey);
// Write the public key to file
file_put_contents(‘public.key’, $a_key[\'key\']);
openssl_free_key($privateKey);
// Load a key file
$publicKey = openssl_pkey_get_public(‘file://public.key’);
//Encrypting longer strings – using two encryption mechanisms
$plaintext = ‘Hello, this is an example of asymmetric/symmetric key encryption/decryption’;
$publicKey = openssl_pkey_get_public(‘file://public.key’);
$encrypted = ”;
$a_envelope = array();
$a_key = array($publicKey);
if (openssl_seal($plaintext, $encrypted, $a_envelope, $a_key) == FALSE)
die(‘Failed to encrypt data’);
openssl_free_key($publicKey);
// Decrypt the encrypted data
if (!$privateKey = openssl_pkey_get_private(‘file://private.key’))
die(‘Private Key failed’);
$decrypted = ”;
if (openssl_open($encrypted, $decrypted, $envelope, $privateKey) == FALSE)
die(‘Failed to decrypt data’);
openssl_free_key($privateKey);
I note the use of $envelope versus $a_envelope in the decrypt section and wondered if this was an error, but it doesn;t see, to work either way.
Thanks for your feedback Nick.
In my code, the prefix “a_” denotes a variable is an array. openssl_seal() allows a string to be encrypted for multiple recipients (public keys). This runs the RC4 encryption once and then encrypts the RC4 key for each recipient (the encrypted RC4 key is called an envelope). The result is an array of envelopes ($a_envelope).
$a_key in this case is populated with a single public key, but openssl_seal() still populates $a_envelope as an array rather than a string.
openssl_open(), on the other hand, only ever deals with a single envelope. So you pass the envelope and private key as strings rather than as arrays.
In your code, if you add the line…
$envelope = $a_envelope[0];
…before the decrypt, your code should work.
I hope this helps.
Worked! Thanks you your speedy response. Nick.
Hi, I’ve been trying to use both of these encrypt / decrypt methods for the last few days.
If I encrypt $text to produce $cypher, then decrypt $cypher to produce $decrypted, then $text and $decrypted are the same values.
If how ever I encrypt $text producing $cypher, HTTP POST $cypher to another php page that reads it (yes I have urlencoded it so it will transfer safely) then decrypt it to get $decrypted, $text and $decrypted do not match.
The same happens (I get a different value for $decrypted) if I encrypt it, store it in a db or file then read it back at a later date and try to decrypt it.
Does any one know whats going on?
I’ve tried everything that I can think of in the lst 3 days now
Hi CJ101.
I suspect something is trying to mess with the encoding of your binary data along the way. I would suggest using base64 encoding for the transmission and/or storage. Although it will make the data roughly 1/3 larger, it should eliminate your problems because all of the characters used by base64 are ASCII characters.
Public and private keys are stored as base64-encoded versions of the binary data.
Great tutorial. Thanks!
Whoa, whoa, get out the way with that good ifnomration.