Encryption, authentication and data integrity in PHP

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


5th March 2016 - Minneapolis (MN)

About me

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

Symmetric encryption

CBC encryption

CBC decryption

PBKDF2

HMAC

  • Hash-based message authentication code:

    HMAC(K,m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))
    where H is hash, K is key, opad is outer padding (0x5c) and ipad is inner padding (0x36)
  • PHP 5.1.2+:
    
    hash_hmac ($algo, $data, $key, $raw = false)
    
  • We will use SHA-256 as $algo

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)

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, 80000, 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, 80000, 64, true);
  $encKey  = substr($keys, 0, 32); // 256 bit encryption key
  $hmacNew = hash_hmac('sha256', $iv . $ciphertext, substr($keys, 32));
  if (!hash_equals($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+)

Best practices for encryption

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

Zend-crypt

zend-crypt is the cryptographic component of Zend Framework

  • 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/0cf0f

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.