[php] Simplest two-way encryption using PHP

What is the simplest way of doing two way encryption in common PHP installs?

I need to be able to encrypt data with a string key, and use the same key to decrypt on the other end.

The security isn't as big of a concern as the portability of the code, so I'd like to be able to keep things as simple as possible. Currently, I am using an RC4 implementation, but if I can find something natively supported I figure I can save a lot of unnecessary code.

The answer is


PHP 7.2 moved completely away from Mcrypt and the encryption now is based on the maintainable Libsodium library.

All your encryption needs can be basically resolved through Libsodium library.

// On Alice's computer:
$msg = 'This comes from Alice.';
$signed_msg = sodium_crypto_sign($msg, $secret_sign_key);


// On Bob's computer:
$original_msg = sodium_crypto_sign_open($signed_msg, $alice_sign_publickey);
if ($original_msg === false) {
    throw new Exception('Invalid signature');
} else {
    echo $original_msg; // Displays "This comes from Alice."
}

Libsodium documentation: https://github.com/paragonie/pecl-libsodium-doc


Encrypting using openssl_encrypt() The openssl_encrypt function provides a secured and easy way to encrypt your data.

In the script below, we use the AES128 encryption method, but you may consider other kind of encryption method depending on what you want to encrypt.

<?php
$message_to_encrypt = "Yoroshikune";
$secret_key = "my-secret-key";
$method = "aes128";
$iv_length = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_length);

$encrypted_message = openssl_encrypt($message_to_encrypt, $method, $secret_key, 0, $iv);

echo $encrypted_message;
?>

Here is an explanation of the variables used :

message_to_encrypt : the data you want to encrypt secret_key : it is your ‘password’ for encryption. Be sure not to choose something too easy and be careful not to share your secret key with other people method : the method of encryption. Here we chose AES128. iv_length and iv : prepare the encryption using bytes encrypted_message : the variable including your encrypted message

Decrypting using openssl_decrypt() Now you encrypted your data, you may need to decrypt it in order to re-use the message you first included into a variable. In order to do so, we will use the function openssl_decrypt().

<?php
$message_to_encrypt = "Yoroshikune";
$secret_key = "my-secret-key";
$method = "aes128";
$iv_length = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_lenght);
$encrypted_message = openssl_encrypt($message_to_encrypt, $method, $secret_key, 0, $iv);

$decrypted_message = openssl_decrypt($encrypted_message, $method, $secret_key, 0, $iv);

echo $decrypted_message;
?>

The decrypt method proposed by openssl_decrypt() is close to openssl_encrypt().

The only difference is that instead of adding $message_to_encrypt, you will need to add your already encrypted message as the first argument of openssl_decrypt().

That is all you have to do.


Use mcrypt_encrypt() and mcrypt_decrypt() with corresponding parameters. Really easy and straight forward, and you use a battle-tested encryption package.

EDIT

5 years and 4 months after this answer, the mcrypt extension is now in the process of deprecation and eventual removal from PHP.


IMPORTANT this answer is valid only for PHP 5, in PHP 7 use built-in cryptographic functions.

Here is simple but secure enough implementation:

  • AES-256 encryption in CBC mode
  • PBKDF2 to create encryption key out of plain-text password
  • HMAC to authenticate the encrypted message.

Code and examples are here: https://stackoverflow.com/a/19445173/1387163


Important: Unless you have a very particular use-case, do not encrypt passwords, use a password hashing algorithm instead. When someone says they encrypt their passwords in a server-side application, they're either uninformed or they're describing a dangerous system design. Safely storing passwords is a totally separate problem from encryption.

Be informed. Design safe systems.

Portable Data Encryption in PHP

If you're using PHP 5.4 or newer and don't want to write a cryptography module yourself, I recommend using an existing library that provides authenticated encryption. The library I linked relies only on what PHP provides and is under periodic review by a handful of security researchers. (Myself included.)

If your portability goals do not prevent requiring PECL extensions, libsodium is highly recommended over anything you or I can write in PHP.

Update (2016-06-12): You can now use sodium_compat and use the same crypto libsodium offers without installing PECL extensions.

If you want to try your hand at cryptography engineering, read on.


First, you should take the time to learn the dangers of unauthenticated encryption and the Cryptographic Doom Principle.

  • Encrypted data can still be tampered with by a malicious user.
  • Authenticating the encrypted data prevents tampering.
  • Authenticating the unencrypted data does not prevent tampering.

Encryption and Decryption

Encryption in PHP is actually simple (we're going to use openssl_encrypt() and openssl_decrypt() once you have made some decisions about how to encrypt your information. Consult openssl_get_cipher_methods() for a list of the methods supported on your system. The best choice is AES in CTR mode:

  • aes-128-ctr
  • aes-192-ctr
  • aes-256-ctr

There is currently no reason to believe that the AES key size is a significant issue to worry about (bigger is probably not better, due to bad key-scheduling in the 256-bit mode).

Note: We are not using mcrypt because it is abandonware and has unpatched bugs that might be security-affecting. Because of these reasons, I encourage other PHP developers to avoid it as well.

Simple Encryption/Decryption Wrapper using OpenSSL

class UnsafeCrypto
{
    const METHOD = 'aes-256-ctr';

    /**
     * Encrypts (but does not authenticate) a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded 
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    {
        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = openssl_random_pseudo_bytes($nonceSize);

        $ciphertext = openssl_encrypt(
            $message,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $nonce
        );

        // Now let's pack the IV and the ciphertext together
        // Naively, we can just concatenate
        if ($encode) {
            return base64_encode($nonce.$ciphertext);
        }
        return $nonce.$ciphertext;
    }

    /**
     * Decrypts (but does not verify) a message
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string
     */
    public static function decrypt($message, $key, $encoded = false)
    {
        if ($encoded) {
            $message = base64_decode($message, true);
            if ($message === false) {
                throw new Exception('Encryption failure');
            }
        }

        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = mb_substr($message, 0, $nonceSize, '8bit');
        $ciphertext = mb_substr($message, $nonceSize, null, '8bit');

        $plaintext = openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $nonce
        );

        return $plaintext;
    }
}

Usage Example

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

Demo: https://3v4l.org/jl7qR


The above simple crypto library still is not safe to use. We need to authenticate ciphertexts and verify them before we decrypt.

Note: By default, UnsafeCrypto::encrypt() will return a raw binary string. Call it like this if you need to store it in a binary-safe format (base64-encoded):

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);

var_dump($encrypted, $decrypted);

Demo: http://3v4l.org/f5K93

Simple Authentication Wrapper

class SaferCrypto extends UnsafeCrypto
{
    const HASH_ALGO = 'sha256';

    /**
     * Encrypts then MACs a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded string
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    {
        list($encKey, $authKey) = self::splitKeys($key);

        // Pass to UnsafeCrypto::encrypt
        $ciphertext = parent::encrypt($message, $encKey);

        // Calculate a MAC of the IV and ciphertext
        $mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);

        if ($encode) {
            return base64_encode($mac.$ciphertext);
        }
        // Prepend MAC to the ciphertext and return to caller
        return $mac.$ciphertext;
    }

    /**
     * Decrypts a message (after verifying integrity)
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string (raw binary)
     */
    public static function decrypt($message, $key, $encoded = false)
    {
        list($encKey, $authKey) = self::splitKeys($key);
        if ($encoded) {
            $message = base64_decode($message, true);
            if ($message === false) {
                throw new Exception('Encryption failure');
            }
        }

        // Hash Size -- in case HASH_ALGO is changed
        $hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
        $mac = mb_substr($message, 0, $hs, '8bit');

        $ciphertext = mb_substr($message, $hs, null, '8bit');

        $calculated = hash_hmac(
            self::HASH_ALGO,
            $ciphertext,
            $authKey,
            true
        );

        if (!self::hashEquals($mac, $calculated)) {
            throw new Exception('Encryption failure');
        }

        // Pass to UnsafeCrypto::decrypt
        $plaintext = parent::decrypt($ciphertext, $encKey);

        return $plaintext;
    }

    /**
     * Splits a key into two separate keys; one for encryption
     * and the other for authenticaiton
     * 
     * @param string $masterKey (raw binary)
     * @return array (two raw binary strings)
     */
    protected static function splitKeys($masterKey)
    {
        // You really want to implement HKDF here instead!
        return [
            hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
            hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
        ];
    }

    /**
     * Compare two strings without leaking timing information
     * 
     * @param string $a
     * @param string $b
     * @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
     * @return boolean
     */
    protected static function hashEquals($a, $b)
    {
        if (function_exists('hash_equals')) {
            return hash_equals($a, $b);
        }
        $nonce = openssl_random_pseudo_bytes(32);
        return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
    }
}

Usage Example

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

Demos: raw binary, base64-encoded


If anyone wishes to use this SaferCrypto library in a production environment, or your own implementation of the same concepts, I strongly recommend reaching out to your resident cryptographers for a second opinion before you do. They'll be able tell you about mistakes that I might not even be aware of.

You will be much better off using a reputable cryptography library.


Examples related to php

I am receiving warning in Facebook Application using PHP SDK Pass PDO prepared statement to variables Parse error: syntax error, unexpected [ Preg_match backtrack error Removing "http://" from a string How do I hide the PHP explode delimiter from submitted form results? Problems with installation of Google App Engine SDK for php in OS X Laravel 4 with Sentry 2 add user to a group on Registration php & mysql query not echoing in html with tags? How do I show a message in the foreach loop?

Examples related to security

Monitoring the Full Disclosure mailinglist Two Page Login with Spring Security 3.2.x How to prevent a browser from storing passwords JWT authentication for ASP.NET Web API How to use a client certificate to authenticate and authorize in a Web API Disable-web-security in Chrome 48+ When you use 'badidea' or 'thisisunsafe' to bypass a Chrome certificate/HSTS error, does it only apply for the current site? How does Content Security Policy (CSP) work? How to prevent Screen Capture in Android Default SecurityProtocol in .NET 4.5

Examples related to encryption

mcrypt is deprecated, what is the alternative? Remove 'b' character do in front of a string literal in Python 3 How to resolve the "EVP_DecryptFInal_ex: bad decrypt" during file decryption How to decrypt Hash Password in Laravel RSA encryption and decryption in Python How to fix Invalid AES key length? gpg decryption fails with no secret key error 7-Zip command to create and extract a password-protected ZIP file on Windows? How do I encrypt and decrypt a string in python? AES Encrypt and Decrypt

Examples related to cryptography

Failed to install Python Cryptography package with PIP and setup.py C# RSA encryption/decryption with transmission How do you Encrypt and Decrypt a PHP String? Example of AES using Crypto++ How to encrypt/decrypt data in php? How to decrypt a SHA-256 encrypted string? Simplest two-way encryption using PHP Padding is invalid and cannot be removed? Given final block not properly padded Getting RSA private key from PEM BASE64 Encoded private key file

Examples related to encryption-symmetric

How do you Encrypt and Decrypt a PHP String? How to encrypt/decrypt data in php? Simplest two-way encryption using PHP PHP AES encrypt / decrypt