37

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
16

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.

1

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
1

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

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