PHP has a lot of available documentation. So much that Googling just about any PHP function provides a php.net result on the first page and a good majority of the content is accurate. And when it’s not, the public comments usually fill in the holes. The OpenSSL cryptography extension is one part of php.net that is very lacking, so much that you’ll even be greeted with Warning: this function is currently not documented; only its argument list is available for both openssl_encrypt
and openssl_decrypt
— perhaps the two most commonly looked up OpenSSL functions.
Here’s the full example. We’ll jump into the details below.
Update — 4/3/2017 — Thanks to those commenters who pointed out the issue with the initialization vector clashing. The gist has been updated to base64 encode $iv to mitigate.
This example regenerates the encryption key each time it runs. This is most likely not what you want. Ideally, the encryption key or password should be kept somewhere safe and only readable by the process that’s responsible for encrypting/decrypting.
After define
ing our cipher, we generate the encryption key / password using openssl_random_pseudo_bytes
. Here we’re creating a 32-byte or 256-bit key (the largest supported by AES).
Next, we create an initialization vector required by the openssl_encrypt
function (well, technically it’s not required but you should use it). We use the same openssl_random_pseudo_bytes
function used to generate the key, but this time we provide it with openssl_cipher_iv_length
to generate the appropriately sized initialization vector for our cipher.
After creating some data to encrypt we use openssl_encrypt
to create our ciphertext. If an initialization vector was used, we must be able to access it again for decryption so the simplest way to do this is to append it to our ciphertext with a separator. Because the initialization vector is not confidential, there’s no need to further encrypt it.
The decryption process starts by splitting the ciphertext into the original ciphertext and the initialization vector. We can then call openssl_decrypt
, providing the original ciphertext, cipher, encryption key, and the initialization vector. The resulting value should match our unencrypted with which we started.
Sign up for Turret.IO — the only data-driven marketing platform made specifically for developers.
Anonymous
Great work. It really helped me a lot! Previously I was using an algorithm called rijndael but openssl one seems much neater. Kudos to you! 😀
Fred
Actually Rijndael is the cipher algorithm used by AES. And OpenSSL is not an algorithm, it’s a library.
P. Dantic
Thanks for this. Your example could be made slightly clearer by using:
list($encrypted, $iv) = explode(‘:’, $encrypted);
Jos
Great article! Probably good to tell that if you want to store the encrypted info in a (MySQL) database you need to use the VARBINARY type for that field
josh
This does not always decrypt the data!!!! if the iv has a : in it everything blows up!
Marat
You would probably need to bin2hex $encrypted and $iv before sending in through HTTP GET or POST
chadwalt
I have aes and it does encrypt but when I use it to decrypt I don’t get the original file.
Twodee
Hey! This post seems great, and it has proved to be so helpful. But there’s one question, I have seen the openssl autogenerated iv and key, and it returns some characters my browser can’t recognize. Should I use something like base64 encode or hash them maybe so that I can get them when I require in plain text?
/dev/random
Good article, thank you.
Sometimes the decryption function does not work because the initialization vector (IV) contains another “:” so the explode function does not split correctly the encrypted data and the IV. To fix it you just need to add another parameter to the explode function to split at the first occurence of “:”:
explode(‘:’, $encrypted, 2);
Mark
You’re missing a critical step. When you use openssl_random_pseudo_bytes you should be passing in a second argument. The function populates this argument with true/false indicating whether or not strong cryptography was used. If it is not then your IV may not meet standards and should not be used.
Josh
Thanks for the article!
Sometimes the decryption function does not work because the initialization vector (IV) contains another “:”
Replace line 29 with this:
$parts = explode(‘:’, $encrypted,(mb_substr_count($encrypted, “:”))+1);
Mike Harris
I think its also worth noting that picking a character such as : which *wont* be present in your base64-ed string gives a pretty big clue to anyone who gains illicit access to your data that the portion after the : is some sort of salt or iv.
Surely its safer to make your iv look exactly like base64 data and just append to the start or end. you always know the length of the iv so you can just substring it off before decrypting the remaining data.
I’m new to this stuff so apologies if I’m talking rubbish!
Marcelo
Im geting this error: Constant AES_256_CBC already defined
Jonathan
@/dev/random
Exploding on the first occurrence of “:” will not allow you to have a “:” in your encryption data.
Using base64 encode on both the encryption data and the iv before combining will solve this:
base64_encode($encrypted) . ‘:’ . base64_encode($iv);
Dale
As a possible workaround for the : issue mentioned above, perhaps a check of the generated hash for the : character and regenerating until one comes up without a : in it would work? Otherwise,
$parts = explode(‘:’, $encrypted_data);
$encrypted = array_shift($parts);
$iv = implode(‘:’, $parts);
might get you where you want to go even if there was a : in there.
Emile Jobity
Thank looked all over could not find. this help
Walturburk
Why not just write the encryption key and iv to a config.ini file on your server, since they always stay the same after being generated.
The config file is stored in a folder not accessible from the internet.
No need to re-save the iv for everything encrypted, right?
harpej singh
we have AES encrypted code but i am unable to decrypt them.
i have also key for them.
encrypted code in java/jsp and i want to decrypt in PHP.
how it is possible.
i am using below code
function encrypt($data, $key)
{
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$data,MCRYPT_MODE_CBC,”\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0″));
}
function decrypt($data, $key)
{
$decode = base64_decode($data);
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$key,$decode,MCRYPT_MODE_CBC,”\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0″);
}
it working fine if we encrypt our new string, but we have already encrypted code.
please help me
harpej singh
hs_2005@yahoo.co.in
9214311070
clem
Great but I am encountering error below using the very same code above:
Warning: openssl_decrypt(): IV passed is only 8 bytes long, cipher expects an IV of precisely 16 bytes, padding with \0 in C:\xampp\htdocs\aes256cbc.php on line 26
Decrypted: Encrypt W�S�?��:se!
Billy
About “:” problem >> use “separator” instead of “:”. That’s it.
$separator = “separator”;
$encrypted = $encrypted . $separator . $iv;
explode($separator, $encrypted);
Cocksworth McJohnson
The reason for the “:” is that u wont notice that its a separator when looking at the encrypted text. The word “separator” makes this whole dealio kinda obvious.
Billy
It is not a problem IMHO, because the iv itself can be public.
Taylor Morgan
Thanks for this great example, Tim. I used the flow you describe to create encrypted permalink tokens.(http://www.tmdesigned.com/passing-immutable-data-with-encrypted-tokens/) Data is encrypted and sent as part of the URL, then decrypted when the user visits to prepopulate the page.