Highly Secure Data Encryption & Decryption Made Easy with PHP, MCrypt, Rijndael-256, and CBC

Random hexadecimal codes on a computer monitor.  Shallow depth of field.In various projects in the past I’ve had to revisit the topic of data encryption and decryption and the best way to accomplish it. In the interest of developing in the simplest, most efficient, and most secure way I have choosen the MCrypt PHP library (built-in to PHP since v4.0.2), Rijndael-256 cipher, and the Cipher Block Chaining (CBC) mode.

Previously I have used the Electronic CodeBook (ECB) mode, but have learned that it is far less secure than CBC because it creates the same hash every time for the same source data. CBC on the other hand creates a unique hash every time even for the same source data.

Anyways, below you’ll find my revised encrypt/decrypt functions with support for all PHP data types.

Note: If you are not running PHP 5.3+ then you may need to replace MCRYPT_DEV_URANDOM with MCRYPT_RAND.

Update 11/14/2013: Switched to MCRYPT_DEV_URANDOM. Changed to hexadecimal encryption key that is later converted to binary for use with MCrypt. Added message authentication code (MAC) check. Thanks go to Bryan C. Geraghty for his recommendations.

Update 4/9/2014: Created an object-oriented version for those interested, view the Gist on GitHub.

Update 9/25/2014: Updated decrypt function so that you don’t receive a PHP notice if the input data is formatted incorrectly (not pipe delimited). Thanks go to Jonathan for noticeing.

Update 2/21/2015: Removed references to AES in the article since Rijndael-256 (256-bit blocks & 256-bit keys) is not the same as AES-256 (128-bit blocks & 256-bit keys).


  1. Kenan says

    Hi there, this is a very good script, but also a bit taxing when I need to decrypt say 20 data strings in one page. It currently takes around 10 secs to decrypt one.

    Is there a way to maybe shorten the processing time? I don’t mind reducing the security a bit. Thanks!

    • says

      Encrypt/decrypt speed is CPU-dependent, so on shared hosting it will be limited because your CPU usage is limited.

      To encrypt & decrypt a 1024 character string 100 times it takes around 150 seconds on shared hosting, but in a local or dedicated hosting environment it takes only a fraction of a second.

      Instead of encrypting & decrypting 20 times can you put your strings in an associative array and then encrypt/decrypt it once?

      If you have an array containing a hundred 1024 character strings it only takes around 0.5 seconds on shared hosting. Quite a time-saver!

    • says

      If you’re just looking to speed things up you can try a different cipher. For example, replace all MCRYPT_RIJNDAEL_256 references with MCRYPT_BLOWFISH and the speed should improve by 5-10 times.

      • Bryan C. Geraghty says

        Hey Josh,

        A friend of mine found these functions and asked if these were okay to use and here is the feedback I sent:

        1. CRITICAL: No MAC (Message Authentication Code); I could create all kinds of havoc in a system that use these functions.
        2. If the key is encoded, it must be decoded before use in encryption or decryption. (in this case, base64)
        3. Padding is done with null bytes. This means that if your original plaintext contains nulls at the end, they will be lost. Use PKCS7.
        4. The mcrypt_create_iv() call should use MCRYPT_DEV_URANDOM

        • says

          Thanks for the analysis Bryan, see my comments below:

          1) Could you please reference an article or explain how would you cause havoc? It just returns bool(false) if I modify the encrypted data – because the data is then corrupt and unable to be unserialized. I looked into adding MAC, looks like I would just need to create a hash of the data to be encrypted, save that like the IV and then check against it on decrypt.
          2) I’m not encoding the key, just the IV, but if I’m missing something let me know.
          3) Mcrypt does pad with \0 but since everything is serialized before encrypting any nulls will be inside the serialized data and not at the end. Try it for yourself.
          4) Great tip, that increases the performance. After some research it looks like, depending on the system, /dev/random must wait until it gets more entropy, which causes a delay.

          Thanks again, I look forward to your response!

          • Bryan C. Geraghty says

            Hey Josh,

            Sure. My responses are below.

            1) Sure. Please see the following links that are common attacks against this specific weakness: 1a) http://en.wikipedia.org/wiki/Bit-flipping_attack 1b) http://en.wikipedia.org/wiki/Padding_oracle_attack

            2) The statement define(‘ENCRYPTION_KEY’, ‘0GSQKwtk14lzW4TkRrOZwxUM5QaNIE8a’) shows a base64 encoded key. This should be decoded before use.

            3) Your point is valid about serialization and no valid following nulls… is there a reason you are doing that? The only thing it adds is predictable plaintext positions which will aide a cryptanalyst. I recommend removing the serialization and using PKCS7 padding.

            4) It’s not just a performance enhancement :) The locking leaks information about the internal state of your PRNG.

          • says

            1) Thanks, I’ll plan on implementing MAC even though the unserializing seems to prevent the decrypt function from returning corrupt data for attackers to analyze.

            2) It’s an 32-byte ASCII key right now. I’ll plan on switching to generating a 32-byte binary key from an ASCII key.

            3) Serializing enables easy encryption/decryption of all PHP data types, a main goal of these functions.

          • says

            I’ve updated the gist with my changes.

            I tried using crypt with 5000 and 1024 iterations for the purpose of MAC but that was too slow so I used hash_hmac and that didn’t seem to affect the performance.

            Thanks for the helpful comments Bryan, it’s better solution because of them.

        • Hexalys says

          One quick follow up. Would a PKCS7 padding be necessary or recommended in addition to the MAC? Or does the MAC now solve the “predictable plaintext positions” aspect?

  2. Pete says

    Hello Josh,
    Love these functions, they work great.

    My question is with regard to PKCS7 padding. I would like to use a Master Key system which would replace the static Key you are using. The problem is that the master key created by users may or may not equal 64 characters. Most likely they would be under 20 characters.

    Can I use PKCS7 padding to keep the Key at 64 characters and still decrypt the string with the same master key.

    Thanks for any help.


    • says

      The length of the password or pass phrase the user provides won’t matter, just hash it to produce a key of static length.

      $pass_phrase = 'HA HA, my pass phrase is unbreakable. Run in fear!';
      $master_key = hash('sha256', $pass_phrase);
      // Result is a 32-byte (64 character) hexadecimal string
      // fce73352086cd2f4013a18e089d0081d61371dcd4371b9b473db6c9f183179b3

      Store the master key hash per user and use as the encryption/decryption key for their personal data.

  3. Pete says

    Thanks Josh,

    I didn’t think of that. I was taking a totally different route.

    Your idea is much easier. I’ll consider it.

    Thanks again.


  4. Guigs says

    Hello, thanks for the code. I was just wondering : if I want to store a string encrypted in a DB, what would be the size of the field compared to the original string ?

    Thanks and regards,

    • says

      I don’t think there is a hard and fast rule to this. After some tests there seems to be a curve. Looks like it settles down to ~33% overhead when encrypting strings longer than 25,000 characters in length. Use a TEXT (~65KB), MEDIUMTEXT (~16MB) or LONGTEXT (~4GB) data field in the DB depending on the size of what you’re storing.

  5. Abhay Raj says

    Hi Josh

    Just i need to know, is this method gives the same result if we use it on different -2 languages like java, .NET, perl etc.

  6. Jonathan says

    Regarding the OOP version:

    1. Why don’t you call method “setKey” in the constructor?
    2. The IV can be changed by anyone (it is public) so it is recommended to include a check between line 29 and line 30 to validate the IV size, right? Otherwise PHP might throw an nasty error.

  7. Jonathan says

    Also Wikipedia recommends:

    To prevent this [padding oracle] attack, one could append an HMAC (Hash-based message authentication code) to the ciphertext. Without the key used to generate the HMAC, an attacker won’t be able to produce valid ciphertexts. Since the HMAC is checked before the decryption stage, the attacker cannot do the required bit-fiddling and hence cannot discover the plaintext.

    Source: http://en.wikipedia.org/wiki/Padding_oracle_attack

    Your code does not append the HMAC but actually encrypts it, so you have to decrypt the message before checking the HMAC.
    I’m not a cryptoanalysist, but is this the correct way of implementing a HMAC when encrypting messages?
    Is your code still safe to use?

    • says

      I used the MAC-then-encrypt method, which SSL/TLS uses. If your opinion of that MAC method is that it’s not safe then there are other solutions that use encrypt-then-MAC.

  8. Yasser says

    thank you for this great script but I have a question.Is it possible for professional hackers to decrypt the encrypted data? (without the key). If you have an advanced and more secure version of this script please provide us with it.
    Thanks a lot.

  9. Jonathan says

    Some questions regarding your code (OOP version):

    The key is a hexadecimal String (length: 32 byte).
    In the encrypt/decrypt method you convert this hex-key to binary format using `pack(‘H*’, $this->key);`
    Then you convert it back to a hex String using bin2hex($key).
    Question: Why not use $this->key instead of bin2hex($key) ($key is the binary representation of the hexadecimal String)?

    `$decrypt = explode(‘|’, $decrypt);
    $decoded = base64_decode($decrypt[0]);`
    You should check if `is_array($decrypt) && count($decrypt) === 2` or, alternatively: `isset($decrypt[0]) && isset($decrypt[1])`, right? Otherwise one could produce nasty error messages in the error log.

    You are aware, that MCRYPT_RIJNDAEL_256 is NOT Rijndael using a 256-bit-key?
    The “256” in MCRYPT_RIJNDAEL_256 refers to the block size length. Maybe that should be mentioned somewhere.

    You use the last 32 chars from the hex key as a key for the hash_hmac function.
    Is this safe to use? This might leak some information, right? Shouldn’t the encryption class have two different keys: One for mcrypt and one for the hmac function?

    Thanks for helping,

    • says

      1. Yes, you can use $this->key instead of bin2hex($key). It was carry over from the non-OOP version. Code updated.
      2. Thanks, I appended a pipe character to the input data to prevent the notice.
      3. I never mentioned anything about a 256-bit key but thank you for informing readers.
      4. As far as I know this is safe and does not leak any information. The MAC hash is encrypted with the original data and not visible like the IV.

      • Bruno says

        Hi Josh.

        Would a PKCS7 padding be a good addition to the MAC or unnecessary?

        I like having the serialization, but at the same time, it would nice to solve the “predictable plaintext positions” aspect mentioned by Bryan C. Geraghty.

        • says

          I don’t believe that it’s necessary. Yes, the fact that the data is serialized means that the structure will be the known to a certain degree (e.g., serialized data will always have a colon as the 2nd character and the last two characters will always be “;}”). PKCS#7 padding will not change this fact.

  10. Thom Turnbull says

    Great way for hackers to encypt codes into software, then sell the software as a modification to their site, since there’s no way to figure out what the code is. As I have found in boonex free mods for dolphin 7. If you’re a hacker this encyption method was created just for you. Enjoy.

  11. zzjin says

    Thanks great scripts. When use it, I found that it genarate a so long string with little inputs(12char->191char). I know it was build for more safe.
    I wanna to use it as cookie encrypt so the encrypted data length must be careful conisdered. Is there any optional options such as ‘MCRYPT_RIJNDAEL_128′ or some thing can much reduce encrypted data length but with little security down?

  12. Rob says

    You store the ENCRYPTION_KEY in plain text in your script. Doesn’t that undermine the whole thing?

    If someone managed to see your script then they’d be able to decrypt all of the encrypted info surely.

    (I’ve only just started learning about encryption so I’m almost definitely missing something!)

  13. Hamed says

    Hi Josh,

    is it possible to search item based on LIKE or = in select statement ? something like:
    ‘SELECT * from tbl WHERE name = “‘.$crypt->encrypt(‘George’).'”‘;
    ‘SELECT * from tbl WHERE name LIKE “‘.$crypt->encrypt(‘Geo’).’%”‘;

  14. Cary Bondoc says

    Hello Josh,

    Thank you for this wonderful code, this is what I’m looking for. I am trying to integrate it to my register and login function. However, I am having a hard time to find a way on how can I make the encrypt static.

    For example I want to encrypt ‘admin’. The first encryption will become like this


    Then if I refresh it it will become like this


    Do I need to store to the database the key also so I can use it to decrypt properly?
    How can I get the key so it will not become random?

    I am just an intermediate PHP developer, and I am not familiar in security, that’s why I’m having a hard time.


    Cary Bondoc

      • Jonathan says

        Not true.
        The only reason Cary gets different output when encrypting the same input (string “admin”) is that the IV (initialization vector) is different on every encrypt() call.

        Same input + same IV = always same output

  15. eric says

    i am passing data through url

    $data= $row[‘id’];
    $encrypted_data = mc_encrypt($data, ENCRYPTION_KEY);

    “. $row[‘title’].”

    can’t decrypt data when i am trying to decrypt in other page please help

    $encrypted_data= $_GET[‘ref’];

    echo’Decrypted Data: ‘ . mc_decrypt($encrypted_data, ENCRYPTION_KEY) . ”;

    • says

      First off, what you’re doing is a bad idea since you’ll be exposing your encrypted data. Putting that aside, you would need to urlencode the data and be aware of your URL length and request size limits. Servers and clients vary so what you’re doing may work for some and not for others. Find another way to do what you’re trying to do.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>