Encryption, authentication and data integrity in PHP

by Enrico Zimuel / @ezimuel
Senior Software Engineer
Zend a Rogue Wave Company

About me

Developer since 1996. Senior Software Engineer at Zend a Rogue Wave Company. 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-crypt of ZF2

Encryption in PHP

  • Mcrypt extension, symmetric encryption (outdated)
  • OpenSSL extension, public key and symmetric encryption

Mcrypt (outdated)

  • Ciphers: rijndael (AES), Twofish, Blowfish, DES, 3DES, etc
  • Block modes: CBC, CFB, CTR, OFB, NOFB, NCFB
  • Padding: zero padding, padded with '\0'
  • No Authentication!
  • Suggestion: don't use it if you are not an expert!

OpenSSL

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) {
  $ivsize = openssl_cipher_iv_length('aes-256-cbc');
  $iv     = openssl_random_pseudo_bytes($ivsize);
  // Encryption key generated by PBKDF2 (since PHP 5.5)
  $keys    = hash_pbkdf2('sha256', $key, $iv, 10000, 64, true);
  $encKey  = substr($keys, 0, 32); // 256 bit encryption key
  $hmacKey = substr($keys, 32);    // 256 bit hmac key
  $ciphertext = openssl_encrypt(
    $text,
    'aes-256-cbc',
    $encKey,
    OPENSSL_RAW_DATA,
    $iv
  );
  $hmac = hash_hmac('sha256', $iv . $ciphertext, $hmacKey);
  return $hmac . $iv . $ciphertext;
}

AES decryption + HMAC-SHA256


function AES_decrypt($text, $key) {
  $hmac       = substr($text, 0, 64);
  $ivsize     = openssl_cipher_iv_length('aes-256-cbc');
  $iv         = substr($text, 64, $ivsize);
  $ciphertext = substr($text, $ivsize + 64);
  // Generate the encryption and hmac keys
  $keys    = hash_pbkdf2('sha256', $key, $iv, 10000, 64, true);
  $encKey  = substr($keys, 0, 32); // 256 bit encryption key
  $hmacNew = hash_hmac('sha256', $iv . $ciphertext, substr($keys, 32));
  if (!compareStrings($hmac, $hmacNew)) { // to prevent timing attacks
	  return false;
  }
  return openssl_decrypt(
    $ciphertext,
    'aes-256-cbc',
    $encKey,
    OPENSSL_RAW_DATA,
    $iv
  );
}
Note: hash_equals() requires PHP5.6+

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

Use hash_equals() to prevent timing attacks (PHP 5.6+)

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;
  • encrypt the key using a public key algorithm;
  • encrypt the message with a symmetric cipher;
  • send the ciphertext and the encrypted key;

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 = openssl_random_pseudo_bytes(32);
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',
                                       $passphrase);
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',
                                       $passphrase);
$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 with zend-crypt

zend-crypt is the cryptographic component of ZF2

  • Encrypt-then-authenticate (Zend\Crypt\BlockCipher)
  • File encryption/decryption
  • 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

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());
 

References: articles and books

References: blog and websites

Thanks!

Rate this talk at joind.in/talk/8ea10

Contact me: enrico [at] zend.com

Follow me: @ezimuel



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.