Secure Data Encryption in Web Applications with PHP

A guide to developing robust and cost-saving applications with the appropriate cryptography features.

Why and How to Encrypt?

There are two paradigms where cryptography is most useful for businesses:

  1. Network communications (end-to-end encryption)
  2. Storing sensitive information (data-at-rest encryption)

Encrypting network communications is absolutely essential to the security of anyone who wishes to use your website or application. The standard and most reliable form of network encryption is called Transport Layer Security (TLS), which was preceded by and older standard called Secure Socket Layer (SSL).

Websites that use SSL or TLS are accessible by typing https://domain.com into your browser instead of just http://domain.com. Consequently, the shorthand way to refer to HTTP over TLS is simply HTTPS.

Contrasted with network cryptography, storing sensitive information is a much more challenging and interesting problem to solve, and is the focus of this paper.


HTTPS Made Easy

TLS is not a simple protocol and it has a lot of important steps. Obscuring the needlessly technical details, HTTPS essentially works like this:

  1. At the very beginning, your server generates a pair of related cryptographic keys (RSA, ECDSA, EdDSA): One private key and one public key.
  2. When deploying HTTPS, you submit your public key and some identifying information to a trusted Certificate Authority to give you a digitally signed certificate, which contains the information you provided (including your public key), that you present to your customer's web browsers.
  3. When a customer visits your website, their browser will process the certificate your server offers and verify that the digital signature is legitimate and provided by a trusted certificate authority.
  4. If all is well, your customer's browser will initiate the remainder of the handshake protocol, which only a server in possession of the appropriate private key for the certified public key can participate in without causing errors.
  5. Once a TLS session has been established, your customers' communications with your front-end server can promise confidentiality and authenticity, barring any unknown vulnerabilities in the TLS protocol or the software on either endpoint.

HTTPS is Fast and Free

The performance implications of HTTP over TLS are best answered by the website, Is TLS Fast Yet? (Yes, and it has been for quite some time.)

Additionally, the added cost to acquire a TLS certificate signed by a Certificate Authority trusted by all major browser vendors can be as low as $0, from one of these certificiate providers:

Next-generation webserver software, such as Caddy Server, will transparently and automatically provide end-to-end encryption by integrating with LetsEncrypt.

HTTPS is Essential to Developing Secure Business Solutions

You need HTTPS in order to be secure. Entire classes of vulnerabilities related to session management in PHP and other web programming languages are only possible in plaintext protocols and only remediable by strong end-to-end cryptography like TLS offers.


Secure Cryptographic Storage in PHP Applications

Whether you're striving for compliance requirements (HIPAA, PCI-DSS, FIPS, Sarabnes Oxley, etc.) or just want to be sure you're providing the highest level of protection for your customers, using the proper cryptographic solution for your storage requirements is essential.

Secure Password Handling and Storage

The safest way to handle passwords is with a slow, salted one-way hashing function designed specifically for passwords. Namely:

  • Argon2 (winner of the Password Hashing Contest, still pending final tweaks)
  • bcrypt (current default for password_hash() and password_verify(), PHP 5.5+)
  • scrypt (only available in PHP through PHP extensions in PECL)

At minimum, all of these functions take a password, a salt, and a cost factor, then applies a sophisticated one-way transformation of the password and salt (repeatedly, based on the cost factor). Refer to our blog post on the subject to learn more about the technical ins and outs of password hashing and user authentication in PHP.

Hashing passwords is not a bulletproof solution, especially for users with extremely weak password choices. However, properly implemented password hashing does slow attackers down significantly and gives your customers time to change their passwords to prevent further compromise.


On-Demand Encryption and Decryption

If your server-side application needs to be able to access information on-demand, but keep it encrypted at rest, the typical solution is to employ symmetric-key encryption.

A common choice for symmetric-key encryption in PHP is to use the AES (Advanced Encryption Standard) block cipher. AES is a good choice, provided the following considerations and precautions be taken by the encryption library developer:

1. What block cipher mode does the library use?

Don't ever use ECB mode. ECB mode (the default mode) is a build block for other, more secure encryption modes; it should not be ever be used directly and by itself to encrypt information. The weakness of ECB mode is not obvious: because of the way block ciphers work, if you have any 16-byte (in the case of AES) block of plaintext repeat and encrypt your message with ECB mode, your ciphertext will repeat. A clever attacker can abuse this property of ECB mode to learn more about the contents of your message.

CBC mode (Cipher Block Chaining), unlike ECB mode, requires an additional input aside from the plaintext message and the encryption key. This input is called an Initialization Vector (IV). The IV must be unique per message and unpredictable. It is not considered a secret (you can send it with the ciphertext), but you should still use a CSPRNG to generate the IV.

CTR mode (Counter mode) is similar to CBC mode, except the IV is called a nonce and, instead of generating 16 bytes of random data for each message, you can safely start at 00000000 00000000 and increase it for each message, so long as you never use the same nonce twice for the same encryption key. A block cipher in CTR mode behaves like a stream cipher, which is for many use cases (e.g. file encryption) more ideal than block ciphers.


2. How is the plaintext padded?

When working with block ciphers (i.e. AES in CBC mode but not AES in CTR mode), you need to pad your plaintexts prior to encryption, in order to make your message size a multiple of the block size. If you use OpenSSL instead of mcrypt, this will be done for you. From our experience, the two most popular padding schemes in PHP applications are:

  • Null padding, which appends 00 bytes until the plaintext reaches the end of the current block (mcrypt's default).
  • PKCS#7 padding, which looks like this (OpenSSL's default for CBC mode; XX denotes a byte of plaintext):
    • XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 0c0c0c0c 0c0c0c0c 0c0c0c0c
    • XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 10101010 10101010 10101010 10101010
    • XXXXXXXX XXXXXXXX XXXXXXXX XXXXXX01
    • XXXXXXXX XXXXXXXX XXXXXXXX XXXX0202
    • XXXXXXXX XXXXXXXX XXXXXXXX XX030303
    The number of padding bytes is used to determine the padding character that every byte should contain. If the plaintext message lines up precisely to the block size, then an entire block of padding is added to the message.

When in doubt, go with a stream cipher (such as ChaCha20, XSalsa20, or AES in CTR mode); otherwise, PKCS#7 is the conservative choice.


3. Message Authentication.

We have written about this topic before, but it bears repeating: Encryption without message authentication is NOT secure.

Two reliable choices for message authentication are HMAC (keyed hash message authentication code) and Poly1305.

Authenticated encryption modes such as Galois/Counter Mode do not require a separate MAC.

Always calculate a MAC (hash-based or otherwise) of the ciphertext, not the plaintext (and the IV/nonce if you transmit it with the ciphertext), and attach it to your message. Encrypt then MAC, verify the MAC before decrypting.

A short list of authenticated encryption options (ignoring key-size) that can be secure:

  • ChaCha20 + Poly1305
  • XSalsa20 + Poly1305
  • Salsa20 + Poly1305
  • AES-GCM
  • AES-CTR + HMAC-SHA2 (e.g. SHA-256, SHA-384, SHA-512)
  • AES-CBC + HMAC-SHA2

Note that the above list explicitly excludes naked AES-CTR and AES-CBC modes, but does include naked AES-GCM mode. This is very important and a common oversight that can completely undermine the security of your application.


4. Cryptographic Side Channels

Does your cryptography implementation leak information that an attacker could use? For example: timing information on MAC validation, cache-timing attack vulnerabilitiess on the S-boxes in AES, or even monitoring the power usage of the physical device performing the encryption can all cripple the security guarantees of your cryptosystem.

The security of a cryptography feature depends not only on the security guarantees of the cryptographic primitives (the AES block cipher, the SHA-2 hash function family, etc.), but also how each of these building blocks are used. Unless you are attempting novel cryptanalysis, focusing on the cryptography building blocks can weaken the security of your system by ignoring the weak parts of your implementation.


PHP Symmetric Cryptography Library Recommendations

In order of preference:

Libsodium (available in PECL)

Libsodium is a portable, cross-compilable, installable, packageable fork of NaCl, the industry standard for high-speed and side-channel resistant cryptography. Libsodium is available to PHP developers through a PHP extension in PECL.

Unless you have some insurmountable obstacle preventing you from installing PECL libsodium into your project, this should be your first and only choice for new cryptography development in 2015.

Relevant functions:

  • \Sodium\crypto_secretbox() - authenticated symmetric key encryption with Xsalsa20 + Poly1305
  • \Sodium\crypto_secretbox_open() - authenticated symmetric key decryption with Xsalsa20 + Poly1305
  • \Sodium\randombytes_buf() - cryptographically secure pseudorandom number generator, for nonces

Defuse Security's Authenticated Cryptography Library (PHP 5.4.0+)

Rather than reinvent the wheel, we recommend using defuse/php-encryption by Defuse Security. In addition to offering this library intense scrutiny, we have also contributed a new file encryption API for version 2.0.0 (not yet released), among other features.

Relevant functions:

  • \Defuse\Crypto\Crypto::encrypt() - encrypt a string (will have version tagging in 2.0.0)
  • \Defuse\Crypto\Crypto::decrypt() - decrypt a string (will have version tagging in 2.0.0)

Relevant functions (coming in v2.x):

  • \Defuse\Crypto\Crypto::legacyDecrypt() - decrypt a string before version tagging was implemented
  • \Defuse\Crypto\File::encryptFile() - encrypt a file
  • \Defuse\Crypto\File::decryptFile() - decrypt a file

OpenSSL (PHP 5.4.0+)

If you're stuck deciding whether to use OpenSSL or mcrypt for symmetric key encryption, go with OpenSSL. It provides more intelligent defaults (i.e. PKCS#7 padding on AES in CBC mode), better performance and cache-timing resistance (thanks to OpenSSL's support for AES-NI).

Relevant functions:

  • openssl_encrypt() - encrypt (with "aes-256-ctr" mode)
  • openssl_decrypt() - decrypt (with "aes-256-ctr" mode)
  • hash_hmac() - calculate a MAC of the ciphertext
  • hash_equals() (PHP 5.6+ or hash-compat) - verify the MAC in constant-time
  • random_bytes() (PHP 7.0+ or random_compat) - generate IVs/nonces and encryption keys

Indexing Encrypted Information

An uncommon problem that many companies encounter is, "How can we encrypt information in a database and still index it for fast retrieval?" For example, if you need to be able to retrieve patient records by social security number in a health care application.

If you have millions of records, decrypting each record one-by-one to perform a simple record lookup would be expensive for both the application and the database server. And storing such a sensitive record is a no-go (unless you enjoy apologizing to your customers and offering them one year of free identity theft protection).

Instead, create a blind index of this information for indexing purposes. First, encrypt the information and store it in one field (say: ssn). Then, store a separate HMAC of the plaintext with a separate key in another field (e.g. ssn_lookup) and create the database index on the second field. (This idea was proposed in an MSDN blog post by Raul Garcia. If you are developing a .NET / SQL Server solution to this problem, Raul's blog post is probably more relevant.)

The key for HMAC indexing purposes should be only available to the application server that needs to query the database, which should be on separate hardware, and never checked into version control.


Online Encryption, Offline Decryption

As cool as symmetric cryptography is, there are many situations where performing encryption online but only being able to decrypt it with a key that the server does not possess is more desirable. For example, backing up your application or security notification logs.

The solution is to use asymmetric cryptography, also known as public key cryptography.

Specifically, we want to use a sealing API: encrypt information with a public key such that only the private key can decrypt it.

PHP Asymmetric Cryptography Library Recommendations

In order of preference:

Libsodium

You definitley want Libsodium here, not OpenSSL, for two reasons:

  1. Simplicity
  2. Security

PHP's bindings for the OpenSSL seal API only supports RSA + RC4 encryption, whereas the Libsodium implementation uses the much safer Extended Salsa20 (Xsalsa20) stream cipher with a Poly1305 authentication tag, negotiated with Curve25519 public keys.

Relevant functions:

  • \Sodium\crypto_box_seal()
  • \Sodium\crypto_box_seal_open()

Further reading:


OpenSSL

As stated above, OpenSSL should be considered a last resort here. The PHP documentation for OpenSSL seal API isn't great and the API itself wasn't designed for simplicty or clarity. Complicated APIs lead to increased risk for mistakes.

Relevant functions:

  • openssl_seal()
  • openssl_open()

Where to Learn More about Developing Cryptography Features

Even though we have restricted the scope of this paper to the narrow use case of encrypting and decrypting messages in a server-side PHP web application, we managed to cover a lot of ground. There are, of course, numerous other use-cases for cryptography that we did not address.

In addition to maintaining a curated list for learning about application security on Github, we frequently publish new posts on our blog concerning security engineering and in particular cryptography.

If you have a specific encryption or authentication requirement for your company's infrastructure, our team of application security experts offers technology consulting services and we can assist you in the design and implementation of cryptography features.

Need Technology Consultants?

Will tomorrow bring costly and embarrassing data breaches? Or will it bring growth, success, and peace of mind?

Our team of technology consultants have extensive knowledge and experience with application security and web/application development.

We specialize in cryptography and secure PHP development.

Let's Work Together Towards Success

Our Security Newsletters

Want the latest from Paragon Initiative Enterprises delivered straight to your inbox? We have two newsletters to choose from.

The first mails quarterly and often showcases our behind-the-scenes projects.

The other is unscheduled and gives you a direct feed into the findings of our open source security research initiatives.

Quarterly Newsletter   Security Announcements