Encryption, authentication and data integrity in PHP

by Enrico Zimuel / @ezimuel
Senior PHP Engineer - Zend Technologies

About me

Software Engineer since 1996. Senior PHP Engineer at Zend Technologies, since 2008. I did research in computer science at the Informatics Institute of Amsterdam University. Open source contributor of Apigility and Zend Framework. Author of articles and books about web programming and applied cryptography. Co-founder of PUG Torino (Italy).

Table of contents

  • Encryption in PHP
  • Symmetric encryption and block ciphers
  • Why encryption is not enough
  • Authentication and data integrity
  • Public key cryptography and digital signature
  • Examples using Zend Framework 2

Encryption in PHP

  • Mcrypt extension, symmetric encryption
  • OpenSSL extension, public key and symmetric encryption


  • Ciphers: rijndael (AES), Twofish, Blowfish, DES, 3DES, check with mcrypt-list-algorithms()
  • Block modes: CBC, CFB, CTR, OFB, NOFB, NCFB
  • Padding: zero padding, padded with '\0'
  • No Authentication!


  • Ciphers: RSA, AES, CAMELLIA, DES, RC2, RC4, check with openssl_get_cipher_methods()
  • Block modes: CBC, CFB, CTR, ECB, XTS
  • Padding: PKCS#1, No padding, SSLV23, PKCS1_OAEP
  • Authentication: AES-128-CBC-HMAC-SHA1, AES-256-CBC-HMAC-SHA1 (check on your box)

Best practices for encryption

Why we need authentication?

  • Encryption only provides confidentiality
  • An attacker can easily alter the encrypted message
  • Padding Oracle Attacks can be applied to retrieve the plaintext without the key!

Padding Oracle Attack

  • Side channel attack which is performed on the padding of a cryptographic message
  • Discovered in 2001 by Serge Vaudenay
  • Works on ECB, CBC, OAEP modes for all the block ciphers
  • In 2010 the attack was applied to several web application frameworks, including JavaServer Faces, Ruby on Rails and ASP.NET.
  • Can be prevented using authentication (for instance, HMAC)

Attack the CBC with PKCS#7

  • The Oracle is the encryption box to attack
  • We send 2 blocks of ciphertext, C'1 || C2, where C'1 is random and C2 is a real ciphertext block
  • The Oracle returns 1 if the padding is valid, 0 if not
  • We try C'1[i] = 0..255, until Oracle is valid, for i = 15..0
  • If C'1[i] = x produces a valid padding, we can retrieve the i-byte of the plaintext

Attack the intermediate state

P'2 = I2 ⊕ C'1I2 = P'2 ⊕ C'1

We know C'1[i] = x ⇒ we know I2[i]

I2 is the same as in the real ciphertext ⇒ P2[i] = C1[i] ⊕ I2[i]

AES encryption + HMAC-SHA256

function AES_encrypt($text, $key) {
  // Padding PKCS#7
  $text    = PKCS7_pad($text, 16);
  // Random IV
  $iv      = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
  // Encryption key generated by PBKDF2 (since PHP 5.5)
  $keys    = hash_pbkdf2('sha256', $key, $iv, 10000, 64);
  $encKey  = substr($keys, 0, 32); // 256 bit encryption key
  $hmacKey = substr($keys, 32);    // 256 bit hmac key
  // Encryption
  $ciphertext = mcrypt_encrypt('rijndael-128', $encKey, $text, 'cbc', $iv);
  // $ciphertext = openssl_encrypt($text, 'AES-256-CBC', $encKey, OPENSSL_NO_PADDING, $iv);
  $hmac = hash_hmac('sha256', $iv . $ciphertext, $hmacKey);
  return $hmac . $iv . $ciphertext;

PKCS#7 Padding

function PKCS7_pad($text, $blockSize)
  $pad = $blockSize - (strlen($text) % $blockSize);
  return $text . str_repeat(chr($pad), $pad);

function PKCS7_unpad($text)
  $end  = substr($text, -1);
  $last = ord($end);
  $len  = strlen($text) - $last;
  if (substr($text, $len) == str_repeat($end, $last)) {
    return substr($text, 0, $len);
  return false;

AES decryption + HMAC-SHA256

function AES_decrypt($text, $key) {
  $hmac = substr($text, 0, 64);  // 64 bytes HMAC size
  $iv   = substr($text, 64, 16); // 16 bytes IV size
  $text = substr($text, 80);
  // Generate the encryption and hmac keys
  $keys    = hash_pbkdf2('sha256', $key, $iv, 10000, 64);
  $encKey  = substr($keys, 0, 32); // 256 bit encryption key
  $hmacNew = hash_hmac('sha256', $iv . $text, substr($keys, 32));
  if (!compareStrings($hmac, $hmacNew)) { // to prevent timing attacks
    return false;
  // Decryption
  $result = mcrypt_decrypt('rijndael-128', $encKey, $text, 'cbc', $iv);
  // $result = openssl_decrypt($text, 'AES-256-CBC', $encKey, OPENSSL_NO_PADDING, $iv);
  return PKCS7_unpad($result);

Timing attacks?

A timing attack is a side channel attack in which the attacker attempts to compromise a cryptosystem by analyzing the time taken to execute cryptographic algorithms

From Wikipedia

Compare strings to prevent timing

function compareStrings($expected, $actual)
  $expected    = (string) $expected;
  $actual      = (string) $actual;
  $lenExpected = strlen($expected);
  $lenActual   = strlen($actual);
  $len         = min($lenExpected, $lenActual);

  $result = 0;
  for ($i = 0; $i < $len; $i++) {
    $result |= ord($expected[$i]) ^ ord($actual[$i]);
  $result |= $lenExpected ^ $lenActual;

  return ($result === 0);

Public key encryption


Digital signature

Hybrid cryptosystem

Public key algorithms are too slow to encrypt a full text message

We need a different approach (Hybrid cryptosystem):

  • generate a random key (session key);
  • encrypt the key using a public key algorithm;
  • encrypt the message with a symmetric cipher;
  • store the encrypted key before the ciphertext;

Generate public and private keys

// Generate public and private keys
$keys = openssl_pkey_new(array(
  "private_key_bits" => 4096,
  "private_key_type" => OPENSSL_KEYTYPE_RSA,

// Store the private key in a file
$passphrase = 'test';
openssl_pkey_export_to_file($keys, 'private.key', $passphrase);

// Store the public key in a file
$details   = openssl_pkey_get_details($keys);
$publicKey = $details['key'];
file_put_contents('public.key', $publicKey);

Hybrid encryption

$message    = 'This is the secret message';
$passphrase = 'test'; // to read the private key

// Encryption
$key = mcrypt_create_iv(32, MCRYPT_DEV_URANDOM);
openssl_public_encrypt($key, $encryptedKey, $publicKey);
$ciphertext = AES_encrypt($message, $key);
file_put_contents('encrypted.msg', $encryptedKey . $ciphertext);

// Decryption
$ciphertext = file_get_contents('encrypted.msg');
$encKey     = substr($ciphertext, 0, 512);
$ciphertext = substr($ciphertext, 512);
$privateKey = openssl_pkey_get_private('file:///path/to/private.key',
openssl_private_decrypt($encKey, $key, $privateKey);
$result = AES_decrypt($ciphertext, $key); // equal to $message

Digital signature

  • How to provide authentication and non-repudiation?
  • A digital signature is a mathematical scheme for demonstrating the authenticity of a digital message or document
  • Commonly used for software distribution, financial transactions, and in other cases where it is important to detect forgery or tampering

Signature with OpenSSL

// Compute the signature
$passphrase = 'test';
$privateKey = openssl_pkey_get_private('file:///path/to/private.key',
$data = file_get_contents('/path/to/file_to_sign');
openssl_sign($data, $signature, $privateKey, "sha256");
printf("Signature : %s\n", base64_encode($signature));

// Verify the signature
$publicKey = openssl_pkey_get_public('file:///path/to/public.key');
$result = openssl_verify($data, $signature, $publicKey, "sha256");

echo $result === 1 ? 'Signature verified' : 'Signature not valid';

Cryptography in ZF2

Zend\Crypt of ZF2 offers crypto features with a simple API:

  • Encrypt-then-authenticate (Zend\Crypt\BlockCipher)
  • File encryption/decryption (from ZF 2.4)
  • Public key and digital signature (Zend\Crypt\PublicKey)
  • Key derivation function (e.g. PBKDF2 since PHP 5.3)
  • Secure password hashing (e.g. bcrypt since PHP 5.3)
  • HMAC and Hash functions
  • Installing using composer
    "require": { "zendframework/zend-crypt": ">=2.3" }

AES + HMAC-SHA256 in ZF2

use Zend\Crypt\BlockCipher;

$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey('encryption key');
$ciphertext  = $blockCipher->encrypt('this is a secret message');

Public key crypto in ZF2

use Zend\Crypt\PublicKey\RsaOptions;
use Zend\Crypt\PublicKey\Rsa;

// Generate public and private key
$rsaOptions = new RsaOptions(array( 'pass_phrase' => 'test' ));
$rsaOptions->generateKeys(array( 'private_key_bits' => 2048 ));
file_put_contents('private_key.pem', $rsaOptions->getPrivateKey());
file_put_contents('public_key.pub', $rsaOptions->getPublicKey());

// Sign and verify
$rsa = Rsa::factory(array(
  'private_key' => 'path/to/private_key',
  'pass_phrase' => 'passphrase of the private key'
$file   = file_get_contents('path/file/to/sign');
$sign   = $rsa->sign($file, $rsa->getOptions()->getPrivateKey());
$verify = $rsa->verify($file, $sign, $rsa->getOptions()->getPublicKey());

To summarize

References: articles and books

References: blog and websites


The slides of this talk are available here:

The code presented in this talk is on github:

Rate this talk at:

Creative Commons License
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.