How do I encrypt data in PHP, properly, using symmetric-key encryption? I have a message M and a secret S. I'm looking for a solution that uses cryptography properly without making any of the usual mistakes. In particular, the solution should use authenticated encryption, choose IVs properly, and generate the actual encryption key from the secret S using a suitable slow hash (in case S happens to be a password instead of an actual cryptographic key).

Can you suggest PHP code for this purpose?


My motive: I'd like to give PHP programmers good advice on how to do this, not bad advice. This question on StackOverflow is a real disappointment: it is full of highly-upvoted answers that are appallingly bad (ECB mode encryption? repeating IVs? encryption without authentication?). Let's figure out the right answer -- a code snippet that does things right -- and then go fix that broken window on StackOverflow. In keeping with the PHP programmer ethos, I'd prefer a snippet of code (that works for as many settings as possible; possibly with explanation and/or explanation of limitations/caveats) instead of just advice about algorithms and concepts.

  • 23
    You weren't pleased with the base64 suggestion from SO? What a shame. – Simon Feb 4 '15 at 18:34
  • 12
    Don't use base64. ROT13 is enough for almost every use. If you need more security, use it twice... – ThoriumBR Feb 4 '15 at 19:20
  • 6
    @ThoriumBR this joke, I like. – user62147 Feb 4 '15 at 19:28
  • 4
    @SteveSether, can you tell me in what way it is too general? I provided some specifications in the question (use authenticated encryption, use a slow hash for key derivation). What specific questions do you have about the requirements? What do you need elaborated? I'd be happy to clarify if you can help me understand what you find unclear. Thank you for your comments! – D.W. Feb 4 '15 at 23:23
  • 2
    @D.W. I think your question has more to do with how do I change the PHP community to understand the importance of understanding choosing the right cryptography and what the various modes of an algorithm mean. The reason they chose and upvoted the ECB mode was because it was the easiest, and it worked. Whose to say that if you give a good example with one of the feedback modes that anyone would understand the implications of why it's better? I think the code is really the unimportant part, and that sort of "copy/paste" mentality is the real culprit destroying security. – Steve Sether Feb 4 '15 at 23:48

Can you suggest PHP code for this purpose?

In order of preference.

1. PECL Libsodium

If you haven't heard of libsodium, go learn about it right now. I very strongly recommend that you use libsodium if possible.

These instructions should get you started with installing libsodium and the PECL extension on your computer. The rest of that e-book should give you an understanding of how to use specific components, and contains plenty of example code. If you need a starting point for PHP cryptography to point non-experts to, libsodium is the best available today.

For secret-key authenticated encryption, refer to this chapter of the e-book.

2. paragonie/halite

(Disclaimer: I wrote Halite.)

All the power of libsodium, and an interface that looks like:

use \ParagonIE\Halite\Symmetric\Crypto as Symmetric;

$key = KeyFactory::loadEncryptionKey('/path/to/key/file');
$ciphertext = Symmetric::encrypt($plaintext, $key);

Check it out: paragonie/halite.

3. defuse/php-encryption

Defuse Security published an encryption* library on Github at defuse/php-encryption.

By encryption, I actually mean authenticated encryption:

  • Version 1:
    • AES-128-CBC with PKCS#7 padding and a random IV
    • HMAC-SHA-256 (verified in constant-time)
    • Uses HKDF-SHA256 to split your key into an encryption key and an authentication key
  • Version 2:
    • AES-256-CTR with a random nonce (CTR mode doesn't need padding)
    • HMAC-SHA-256 (verified in constant-time)
    • Uses HKDF-SHA256 to split your key into an encryption key and an authentication key; now with a random HKDF salt to mitigate the impact of birthday collisions in the random CTR after encrypting 2^64 blocks

Disclaimer: I'm one of the coauthors of this library, although most of my contributions won't land until the version 2 rewrite, which includes version tagging and a streaming interface for encrypting/decrypting files safely.

4. zend/zend-crypt

I recommend this one last because I have not personally audited it, but it is part of the Zend Framework and a lot of PHP security experts attest to its quality. Go ahead and use it if you're already using the Zend Framework.

Update: Since I originally wrote this answer, I did discover an issue in Zend\Crypt\PublicKey\Rsa (ZF2015-10 a.k.a. CVE-2015-7503). However, their symmetric-key encryption implementation appears to be secure.

D.W., the answer needs to come more in the form of a Design Pattern than a code snippet. One reason for this is the state of the art with PHP and the PHP community. There are precious few examples of "getting it right" that are available to average PHP developers.

Examples of this difficulty:

  1. If you are typing mcrypt into your code you are doing it wrong: mycrypt, as of last time I looked, is no longer maintained and is effectively abandoned. However, mycrypt is the extension able to obtain random numbers from /dev/urandom, which does appear to be the right way to do it on Linux-based servers.

  2. How to safely generate a random number: PHP's openssl extension gets it wrong. Openssl is the extension to use for new development, but it doesn't get everything right.

This brings us to the conclusion that we need to create our own cryptography code. Any expert will tell us, "Don't do that!" But with PHP there may not be an alternative. There are community-provided crypto packages, but it's the same issue: How do we know if that package "gets it right?"

The other reason is that, with PHP, the question comes up because of a specific situation. Are you trying to store hashed passwords across an insecure network? One set of protocols might be best.

Are you trying to secure web services between your server and your mobile app? A different approach may be more appropriate. (For example, can you depend on the app keeping a secret key secret, when the app can be reverse-engineered by an attacker?)

For the record, here are the steps I considered with symmetric secret-key encryption. I am using PHP 5.3, the mcrypt extension for access to /dev/urandom, and openssl for aes-256-cbc.

  1. Recognize that everything depends on keeping the secret key secret. If the secret keys can be extracted from the remote client software (or from either endpoint of your communication), an attacker with access to the encrypted text can read anything and everything, and insert spurious messages.

  2. Recognize that transmitting over HTTPS, for example, provides insufficient security against some attacks. For example, an attacker can install our app and observe decrypted HTTPS traffic being sent/received to/from our app.

  3. In other words, understand the environment in which your encryption is operating, and find out what attacks must be considered.

  4. Both sides of the encryption need the same shared secret key. Both sides need to obtain the key, and both need to keep the key from being stolen, seen, reverse engineered, etc.

  5. I believe (but lack sufficient expertise to be sure) that it's correct practice to hash your secret key to exactly 256 bits (for AES-256), e.g., use hash('sha256', $secretKey) to create the 256-bit key to feed into the encrypt/decrypt function. In other words, plain ASCII text as-is doesn't have enough entropy to be directly used as the secret key. So, use a hash function creating (or being truncated to) the correct number of bytes.

  6. For AES, you need a 128-bit input vector. Understand that this input vector needs to be different for every encryption/transmission. I have seen too many code samples with a fixed input vector! Use mcrypt with mode MCRYPT_DEV_URANDOM for the random bytes.

  7. The Doom Principle *thanks @mti2935) states that, if you perform ANY cryptographic operation on the received message before verifying its integrity, it will SOMEHOW inevitably lead to doom. One best practice is to use the HMAC (the next point).

  8. (not enough reputation to post the link): Understand that Encryption is not Authentication. The HMAC can only verify that the message has not been altered. It also verifies that the message was sent by someone possessing your secret key. You may or may not consider that fact to be sufficient authentication.

  9. The HMAC must cover ALL inputs to your decryption function. That means both the encrypted message AND the input vector. If you have some identifier showing which decryption algorithm to use, THAT must also be part of the HMAC calculation. Otherwise, the attacker could change that indicator and mount certain attacks, and your HMAC validation will still appear clean.

  10. The above is the "encrypt-then-HMAC" school of thought. There are opposing schools of thought, notably including Bruce Schneier. Bruce's reasoning is that it's notably difficult to get "encrypt-then-HMAC" right. If Bruce says it's notably difficult, it's notably difficult.

My point is that if you are encrypting and HMAC'ing, in whatever order, FIND OUT what is so difficult to get right, and get concurrence that you got it right.

  • 2
    This is an interesting discussion. However, I wasn't looking for principles or concepts or ideas or a design. I was looking for specific code that I can recommend to PHP developers. I already understand the principles and already present the outline of a specific design in the question. It sounds like you might have some of the pieces, but you miss a few of the requirements in the question: item 5 fails to use a slow hash; item 8 doesn't describe how to compute a HMAC in PHP; we need a code snippet for key derivation (deriving multiple independent keys from a passphrase). – D.W. Sep 12 '15 at 3:39
  • 2
    Okay. I'll answer with specific code then :P – Scott Arciszewski Sep 13 '15 at 1:23
  • 1
    @ScottArciszewski I don't have enough rep to comment on Scott's answer. His article paragonie.com/blog/2015/09/state-cryptography-in-php points out my limitations: Our production site remains on PHP 5.3 for some time to come. We only use vetted OS and PHP versions from our Linux packager. defuse/php-encryption takes PHP 5.4. Now I have one more reason for getting to at least 5.4. I should NOT be writing my own encryption code! – Edward Barnard Sep 13 '15 at 2:24

Write crypto code don't publish it Upon further reflection it's possibly better that I don't expose roll-your-own code as an example. Use libsodium.

  • 1
    Thank you for sharing your code! 1. It requires two separate passphrases, which is unrealistic. Instead, you should derive all the keys from a single passphrase: perform a slow hash of the passphrase, then generate your encryption key and authentication key from that (e.g., if p is the passphrase and h is a slow hash like PBKDF2 or scrypt, set k = h(p), then use E_k(0),E_k(1) as the encryption key and E_k(2),E_k(3) as the authentication key, where E is AES ECB encryption). – D.W. Sep 12 '15 at 23:54
  • 2
    2. Hardcoding the keys in the source code is usually not a good idea. Usually it's better to pass them in as a parameter. Other than that, looks very promising -- thank you! – D.W. Sep 12 '15 at 23:57
  • @D.W., I'd like to better understand what you're saying. 1. Can you give a PHP sample for deriving the two keys? I think I follow you but I'm sketchy on proper terminology. 2. I'm just not seeing the need for a slow hash in these circumstances and I'd like to understand. The primary use of a slow hash, as I understand it, is for something like a large number of stored passwords. With individual salts, each hash must be attacked individually, and the slow hash makes the time factor prohibitive. In this case we are not storing the hashed result so there is nothing to attack. – Edward Barnard Sep 13 '15 at 0:06
  • 1
    1. I'm not very good at PHP, so I hesitate to give a sample as I worry it might be wrong. You could alternatively use PBKDF2 and ask for 512 bits of output (dkLen=512), then use the first 256 bits as the encryption key and the last 256 bits as the authentication key. 2. A slow hash is needed for these purposes too, otherwise offline bruteforce attacks on the passphrase are too easy. It's not limited to storing password hashes. Do security.stackexchange.com/a/2213/971 and security.stackexchange.com/q/15581/971 make it any clearer? – D.W. Sep 13 '15 at 0:19
  • @D.W., I agree with you on keys in source code, for many reasons. I chose to here to provide a complete self-contained example. I'll add a comment to the sample code. – Edward Barnard Sep 13 '15 at 0:44

I certainly will not provide a code snippet how it should be done, but I can provide some tools to apply for a possible solution.

I will use MCRYPT with BLOWFISH encryption in CBC mode, but you can certainly use MCRYPT_RIJNDAEL_128 (AES) or whatever you feel apropriate, I just didn't.

How to create an IV?

$iv=mcrypt_create_iv (mcrypt_get_block_size (MCRYPT_BLOWFISH, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);

Here you can use MCRYPT_DEV_RANDOM as the randomness source as well, if your script may be allowed to block and wait for "better" randomness. Please note that you may not reuse the IV for another encryption under the same key, you have to create a new one for any subsequent encryption.

How to create a encryption key?

eKey=openssl_random_pseudo_bytes(16)

When you were talking about using a password for encryption, you should use a password based key derivation function (pbkdf), to derive an encryption key from a password string.

$salt=openssl_random_pseudo_bytes(16);
$iterations= 10000; //in/decrease to your needs
$eKey=hash_pbkdf2("sha256", $password, $salt, $iterations, 20);

Note: You have to safe the salt to derive the encryption key again later! Again choose the hash function that fit your needs.

How to encrypt?

$encrypted=mcrypt_cbc(MCRYPT_BLOWFISH, $eKey, "Your input goes here", MCRYPT_ENCRYPT, $iv));

Unfortunately mcrypt does not support the Galios/Counter Mode (gcm) which provides authenticated encryption on its own. Therefore I will add a MAC manually. I will go here with the Encrypt-then-MAC paradigm and make use of the HMAC algorithmm. (When it comes to MACs, it is sometimes critical. E.g. you may not use the same key when using CBC-MAC and CBC Mode encryption, as it may compromise your message.) And, make sure to also include the $iv in the MAC calculation (thanks @Fleche for the comment)

$mac=hash_hmac('sha256', $iv.$encrypted, $eKey);

Again, choose a hash function that fit your needs and save the MAC and the encrypted message somehow together and send it to another party or wherever you want to decrypt it. E.g. as a base64_encoded string:

base64_encode($mac).":".base64_encode($encrypted)

How to decrypt?

First of all, as we use authenticated encryption, we have to verify the MAC against the received message. If this is verified successfully we can safely decrypt the message.

$ourMAC=hash_hmac('sha256', $iv.$encrypted, $eKey);

Now we verify that the received MAC $mac has the same value we calculated $ourMAC. If not, either the MAC was tampered with or the encrypted message and we will not proceed to decrypt it.

if (hash_equals($mac, $ourMAC) {
  $original_plaintext=mcrypt_cbc (MCRYPT_BLOWFISH, $eKey, $encrypted, MCRYPT_DECRYPT, $iv);
} else {
  //something bad happenend
  echo "MAC verification failed!";
}

Please note that I used here http://php.net/manual/de/function.hash-equals.php - instead of a simple string comparison- which runs in constant time to prevent against timing attacks (https://wiki.php.net/rfc/timing_attack) (thanks to Fleche pointing that out).

I hope that helps and provides some input for a more sophisticated answer, that may possibly arrive. Furthermore I want to add that http://php.net/manual/de/function.mcrypt-encrypt.php #1 example seems pretty solid as well and can be consulted as well.

  • 3
    This does not address several points in the question. Most critically, you don't address authentication, which is absolutely required. Additional, although you address key derivation, you don't demonstrate it in a canonically secure fashion. – Xander Mar 8 '15 at 19:58
  • 4
    The code is insecure and repeats many classical mistakes of self-made authentication schemes. For example, you've failed to protect the initialization vector, which makes it trivial for an attacker to manipulate the first block. You also use “naïve” string comparison when checking the MAC, which can enable the attacker to find out the expected MAC of an arbitrary message through timing. While the code may be better than what we usually see in the PHP world, I don't think it's helpful to have yet another collection of half-working answers. The question is specifically about the one correct way. – Fleche Mar 8 '15 at 22:27
  • 2
    What do you mean by protecting the IV? It should be unique and it should not be reused. That are the requirements of an IV in CBC mode. So what do you mean exactly. Furthermore you are talking about timing attacks for string comparison. Well, I'm no expert in that particular field, so please enlighten me how it should be done instead. I mean it's not that I did not expect downvotes, but instead you could just be a little bit more precise and help to actually increase the quality of the answer. I think it's at least a good starting point and I don't claim a perfect solution, yet. – evildead Mar 9 '15 at 10:40
  • 1
    Well, the comment area isn't really suited for long explanations, so you should probably create your own question instead of using this one. A quick follow-up: The IV must be included in the MAC calculation as well. If you only protect the integrity of the ciphertext, an attacker can manipulate the resulting plaintext simply by changing the IV. – Fleche Mar 12 '15 at 5:55
  • 4
    Standard string comparison is susceptible to timing attacks, because it stops as soon as it finds a non-matching character. So an attacker can try out different characters, and the duration of the string check tells them whether the character is correct. By repeating this, the attacker can systematically reconstruct the expected MAC of an arbitrary message. – Fleche Mar 12 '15 at 5:57

protected by Community Aug 11 '17 at 10:13

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.