87

I had a look at this question, and wanted to do it for myself. When I ran this code (taken straight from this answer):

$textToEncrypt = "My super secret information.";
$encryptionMethod = "AES-256-CBC";  // AES is used by the U.S. gov't to encrypt top secret documents.
$secretHash = "25c6c7ff35b9979b151f2136cd13b0ff";

//To encrypt
$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secretHash, '1234567812345678');

//To Decrypt
$decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $secretHash);

//Result
echo "Encrypted: $encryptedMessage <br>Decrypted: $decryptedMessage";

However I get the warning

openssl_encrypt(): Using an empty Initialization Vector (iv) is potentially insecure and not recommended

So I went and had a look at the docs, but there 'is no documentation'. I found this comment, but still no mention of what the Initialization Vector should be and how I should use it. Can anyone enlighten me?

I know I could have done some more Googleing, but Stackoverflow comes up first in so many search results I thought this question might be useful to anyone else who was having this problem.

Community
  • 1
  • 1
Alfo
  • 4,801
  • 9
  • 38
  • 51
  • 2
    Did you lookup what [initialization vector](http://en.wikipedia.org/wiki/Initialization_vector) means? – Jared Farrish Aug 06 '12 at 00:09
  • Yes, but I was wondering how this should be best implemented in PHP. – Alfo Aug 06 '12 at 00:10
  • 1
    An empty IV is **bad**. It's what led to the whole Debian/OpenSSH fiasco a while back. – Ignacio Vazquez-Abrams Aug 06 '12 at 00:10
  • Try: http://www.php.net/manual/en/function.openssl-encrypt.php#99188 See the last argument, `$iv`. – Jared Farrish Aug 06 '12 at 00:13
  • @JaredFarrish yes, as I say I've read the docs. It doesn't really reveal anything about the problem I'm facing. – Alfo Aug 06 '12 at 00:15
  • EDIT: Oops, just realized you already linked to it too. Are you looking for what kind of value to pass as `$iv`? – Jared Farrish Aug 06 '12 at 00:18
  • Also note, `$iv` is relatively new (v. 5.3.3), so I'm sure some older demonstrations could be out of date/obsolete. – Jared Farrish Aug 06 '12 at 00:20
  • @JaredFarrish yes, that's what I'm asking. I should have been more clear :) – Alfo Aug 06 '12 at 00:23
  • Well, here's something a unique demonstration: https://bugs.php.net/bug.php?id=60798 And here's another using [`openssl_random_pseudo_bytes(openssl_cipher_iv_length($method))`](http://www.yiiframework.com/forum/index.php/topic/26435-aes-encryption/page__view__findpost__p__127538) and `$method = 'aes-128-ecb'`. – Jared Farrish Aug 06 '12 at 00:25
  • Someone else mentioned this in a comment on one of the answers, but your most immediate problem is that the Initialization Vector (you're using '1234567812345678') is supposed to be the 4th parameter of openssl_encrypt(), not the third. You need pass a 0 value for the options first. Same for including it when you call openssl_decrypt(). – orrd Aug 06 '14 at 21:35

1 Answers1

152

An IV is generally a random number that guarantees the encrypted text is unique.

To explain why it's needed, let's pretend we have a database of people's names encrypted with the key 'secret' and no IV.

1 John dsfa9p8y098hasdf
2 Paul po43pokdfgpo3k4y
3 John dsfa9p8y098hasdf

If John 1 knows his cipher text (dsfa9p8y098hasdf) and has access to the other cipher texts, he can easily find other people named John.

Now in actuality, an encryption mode that requires an IV will always use one. If you don't specify an IV, it's automatically set to a bunch of null bytes. Imagine the first example but with a constant IV (00000000).

1 John dsfa9p8y098hasdf 00000000
2 Paul po43pokdfgpo3k4y 00000000
3 John dsfa9p8y098hasdf 00000000

To prevent repeated cipher texts, we can encrypt the names using the same 'secret' key and random IV's:

1 John sdf875n90mh28458 86714561
2 Paul fg9087n5b60987nf 13541814
3 John gjhn0m89456vnler 44189122

As you can see, the two 'John' cipher texts are now different. Each IV is unique and has influenced the encryption process making the end result unique as well. John 1 now has no idea what user 3's name is.

Decryption requires the use of the same IV the text was encrypted with of course, which is why it must be stored along side the encrypted data. For decryption the IV is useless‡ without the key though so transmitting or storing it with the encrypted text is of no concern.

This is an overly simplistic example but the truth is, not using IV's has serious security ramifications, which is the reason IV's exist in the first place. Undoubtedly, countless IV-less encryption implementations have been exploited.

‡ As my security knowledge has progressed, I've learned that there are some more complicated exploits for various block cipher modes and concatenation techniques (not particularly related to the concatenation methodology described below) that can utilize IV's to attain keys. Notable examples exploit RC4 data streams to determine the keys and decrypt wi-fi traffic for older technologies like WEP and WPA:


Now, your code appears to be setting the IV (1234567812345678) but not using it on decryption. That's certain to fail.

You also may want to utilize some of PHP's IV generation functions. I think this should work for you:

$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secretHash, 0, $iv);
$decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $secretHash, 0, $iv);

For storage/transmission, one option is to simply concatenate the IV and cipher text like so:

$data = $iv.$encryptedMessage;

Then on retrieval, pull the IV out for decryption:

$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = substr($data, 0, $iv_size);
$decryptedMessage = openssl_decrypt(substr($data, $iv_size), $encryptionMethod, $secretHash, 0, $iv);

If you're storing the IV in a database for example, you could also store the IV in an adjacent column to simplify the extraction process.


For more info, check out PHP's Mcrypt library. It's quite full featured and has tons of examples, many of which can help you out with openssh encryption implementations. http://php.net/manual/en/function.mcrypt-encrypt.php


An obligatory security disclaimer: My words describe the simplest of simple concepts in the simplest possible way. In the reach and depth of my knowledge, I am an absolute novice. Even the best of the best security researchers and experts introduce encryption vulnerabilities all the time. Even so, the less you write on your own, the better off your users, customers, family, and friends will be. No offense! While it's fun, interesting, and even practical to learn about encryption theory, especially applied to computer science in something as accessible as PHP, staying up to date with best practices and using the latest trusted libraries is going to provide the most secure systems for 99.9999999% of us. Nothing is perfect, but it's best to stand on the shoulders of giants. As a wise man probably might have said, "The more you learn the more you realize how little you know."

Joel Mellon
  • 3,672
  • 1
  • 26
  • 25
  • I should also mention that you need the mcrypt library installed. For Ubuntu/Debian: apt-get install php5-mcrypt Fedora: yum install php5-mcrypt – Joel Mellon Sep 20 '12 at 21:32
  • 4
    Awesome explanation. I believe you're missing the 4th parameter for openssl_encrypt/decrypt. IV is the 5th – Thilo Savage Dec 05 '12 at 20:42
  • Right you are. I added some falsy zeros. Thanks. – Joel Mellon Dec 05 '12 at 21:22
  • 2
    If you're going to send the encrypted data via an HTML form, be sure to bookend the PHP code with base64_encode/base64_decode and set the form enctype="multipart/form-data" so that you don't have encoding issues since the IV is binary. – Beau Oct 18 '13 at 05:17
  • @Beau: excellent point. Another thing to consider is escaping the cipher text when inserting into a database. Cipher texts can include quotes which conflict with SQL queries. Base64 encoding is one way since you're guaranteed alphanumeric characters...but of course you're increasing the data size. Standard SQL injection prevention methods should be employed, eg. `mysql_real_escape_string()` (although soon to be depreciated). – Joel Mellon Oct 18 '13 at 22:33
  • 1
    @sudopeople: Likewise for storing this in a database you should use a VARBINARY, BINARY or BLOB datatype and not a varchar or text. A more modern technique for preventing SQL Injection is to use PDO prepared statements. – Beau Oct 19 '13 at 02:39
  • @Beau All good points. When you start using (proper) frameworks you forget that you don't have to remember all these little details anymore. – Joel Mellon Jan 24 '14 at 19:49
  • I also want to add that there's a tendency to intermix random and unique. IVs need to be *unique* to work properly. I don't really stress this in my answer...although I do recommend using whatever IV generation tools are available in your language/library (eg. `mcrypt_create_iv`) as a pretty safe bet. – Joel Mellon Jan 24 '14 at 19:52
  • Having in mind all the recent found OpenSSL breaches it this function safe? – GTodorov Jun 12 '14 at 21:09
  • So if my key is already random, generated with `openssl_random_pseudo_bytes()`, then I don't really need an `iv`, is that correct? – kovshenin Feb 02 '15 at 15:08
  • 2
    @kovshenin Well, no actually. An IV is technically not so much random as it is unique. Random (especially pseudo random) functions are capable of generating the same string and encryption is capable of generating the same ciphertext with different keys and/or different data. You may be fine in your case, but if you really need to guarantee they're unique, setting an IV (correctly!) is about as good as you can get. – Joel Mellon Feb 03 '15 at 17:17
  • 2
    it has to be `MCRYPT_RIJNDAEL_128` – CIRCLE Oct 15 '15 at 17:42
  • thanks @CIRCLE because i'm getting error openssl_encrypt(): IV passed is 32 bytes long which is longer than the 16 expected by selected cipher – vee Jul 26 '16 at 09:32
  • 6
    mcrypt is deprecated – Márcio Rossato Feb 22 '18 at 17:36
  • @MárcioRossato Yeah, OpenSSL has become the de facto replacement. See Example #1: https://www.php.net/manual/en/function.openssl-encrypt.php – Joel Mellon Jan 17 '20 at 16:36