Authenticated Encrypt with OpenSSL and PHP 7.1

One of the missing feature of the OpenSSL extension for PHP was the support for Authenticated Encryption. If you are wondering why the authentication is important for encryption, I suggest to have a look at this presentation.
The issue with OpenSSL was related to the API design of the functions openssl_encrypt and openssl_decrypt that didn’t return the authentication hash of the encrypted data.

string openssl_encrypt(
    string $data,
    string $method,
    string $password,
    [ int $options = 0 ],
    [ string $iv = "" ]
)

string openssl_decrypt(
    string $data,
    string $method,
    string $password,
    [ int $options = 0 ],
    [ string $iv = "" ]
)

The output of openssl_encrypt is the encrypted data, without the authentication hash. This hash is required to decrypt a message, because the algorithm needs to authenticate the data before proceed with decryption.

This issue will be solved from PHP 7.1 thanks to RFC openssl_aead proposed by Jakub Zelenka. The idea is to add some optional parameters to the previous OpenSSL functions. The new API is reported below (the new parameters are marked in gray):

string openssl_encrypt(
    string $data,
    string $method,
    string $password,
    [ int $options = 0 ],
    [ string $iv = "" ],
    [ string &$tag = NULL ],
    [ string $aad = "" ],
    [ int $tag_length = 16 ]
)

string openssl_decrypt(
    string $data,
    string $method,
    string $password,
    [ int $options = 0 ],
    [ string $iv = "" ],
    [ string $tag = "" ],
    [ string $aad = "" ]
)

The authentication hash is stored in the $tag variable. This value is filled by the openssl_encrypt function and returned as reference.

The other optional parameter $aad represents additional authentication data that you could use to protect the message against alterations, without the encryption part. For instance, if you need to encrypt an email leaving the header information in plaintext, like the sender and the receiver, you can pass the header in $aad.

The last optional parameter $tag_length is the length in bytes of the hash value, that is 16 by default. This value is related to the encryption algorithm used. I will give some details on it later.

To decrypt an authenticated message, you need to pass the $tag value to openssl_decrypt and optionally the additional authenticated data ($aad).

Until PHP 7.1 will not be available, you can test this new feature using a Release Candidate version of PHP 7.1, for instance 7.1.0RC05.

The OpenSSL extension provides the support for two authenticated encryption algorithm: GCM and CCM. I will show how to use it in the next sections.

Galois/Counter Mode (GCM)

The Galois/Counter Mode (GCM) is a mode of operation for symmetric key cryptographic block ciphers that provides encryption and authentication. If you are interested in the details of this algorithm you can read the Wikipedia page.
This is an algorithm used in many applications like IPsec, SSH and TLS 1.2. Used together with AES (AES-GCM) is included in the NSA Suite B Cryptography .
This algorithm is very fast because the execution can be parallelized. Moreover, the algorithm does not any patents and can be used without restrictions.

You can check if your OpenSSL extension supports the GCM mode using the openssl_get_cipher_methods function. If you see “-gcm” or “-GCM” at the end of a cipher name you can use the Galois/Counter Mode. You need to have at least OpenSSL 1.1 to support this algorithm.

Below is reported an example using aes-256-gcm algorithm (i.e. AES block cipher with 256 bit key):

$algo = 'aes-256-gcm';
$iv   = random_bytes(openssl_cipher_iv_length($algo));
$key  = random_bytes(32); // 256 bit
$data = random_bytes(1024); // 1 Kb of random data
$ciphertext = openssl_encrypt(
    $data, 
    $algo, 
    $key, 
    OPENSSL_RAW_DATA, 
    $iv, 
    $tag
);
// Change 1 bit in ciphertext
// $i = rand(0, mb_strlen($ciphertext, '8bit') - 1);
// $ciphertext[$i] = $ciphertext[$i] ^ chr(1);
$decrypt = openssl_decrypt(
    $ciphertext, 
    $algo, 
    $key, 
    OPENSSL_RAW_DATA, 
    $iv,
    $tag
);
if (false === $decrypt) {
    throw new Exception(sprintf(
        "OpenSSL error: %s", openssl_error_string()
    ));
}
printf ("Decryption %s\n", $data === $decrypt ? 'Ok' : 'Failed');

If you need to store the encrypted value somewhere you need to store the $tag value concatenated with $ciphertext. This because you need to pass the $tag again to decrypt the ciphertext. If you store the $tag value you need to remember also the size of this value. I suggest to use the default value of 16 bytes to simplify the usage. The tag length for the GCM mode can be between 4 and 16 bytes.

During the decryption part, we can check for errors if the $decrypt value is false. For instance, if someone has altered the encrypted message we can recognize it because the authentication will fail. You can uncomment the lines 13 and 14 from the previous example to simulate a change in the $ciphertext. Unfortunately, in the case of authentication error the openssl_error_string function returns an empty string. I just opened this report at bugs.php.net.

If you want to use additional authenticated data you can pass the string to be authenticated in the $aad parameter, as reported in the following example:

$algo  = 'aes-256-gcm';
$iv    = random_bytes(openssl_cipher_iv_length($algo));
$key   = random_bytes(32); // 256 bit
$email = 'This is the secret message!';
$aad   = 'From: foo@domain.com, To: bar@domain.com'; 
$ciphertext = openssl_encrypt(
    $email, 
    $algo, 
    $key, 
    OPENSSL_RAW_DATA, 
    $iv, 
    $tag,
    $aad
);
// Change 1 bit in additional authenticated data
// $i = rand(0, mb_strlen($aad, '8bit') - 1);
// $aad[$i] = $aad[$i] ^ chr(1);
$decrypt = openssl_decrypt(
    $ciphertext, 
    $algo, 
    $key, 
    OPENSSL_RAW_DATA, 
    $iv,
    $tag,
    $aad
);
if (false === $decrypt) {
    throw new Exception(sprintf(
        "OpenSSL error: %s", openssl_error_string()
    ));
}
printf ("Decryption %s\n", $email === $decrypt ? 'Ok' : 'Failed');

In this case, if you uncomment the lines 15 and 16 you will have an authentication error regarding the $aad part. Notice that the encrypted message in this case is composed by $tag . $aad . $ciphertext. Basically, you need to store also the $aad in plaintext to be able to perform the authentication during the decryption of the message.

Counter with CBC-MAC (CCM)

Counter with CBC-MAC (CCM) is another authenticated encryption mode for symmetric block ciphers. The CCM mode is also used in many applications like IPsec and TLS 1.2 and is part of the IEEE 802.11i standard. CCM is an alternative implementations of the OCB mode, that was originally covered by patents. The CCM can be used without any restriction.
I will not show the details of the CCM algorithm, if you are interested you can read the Wikipedia page.
This encryption mode can be used in PHP 7.1 if your OpenSSL extension support the “-ccm” or “-CCM” in the name of the algorithm.

The previous example code works also in the case of CCM, you need only to replace the first line with :

$algo  = 'aes-256-ccm';

The only difference with GCM, related to the OpenSSL usage, is the size of $tag. CCM has no limits of tag’s length and also the resulted tag is different for each length.

Benchmark GCM vs. CCM

I provided a very simple benchmark script to test the performance of encryption and decryption using GCM and CCM mode. The result is that GCM is 3x faster than CCM. This results is as expected due to the differences between the two algorithms. That said, my suggestion is to use GCM in your code!

This is the script that I used for the benchmark:

$key = random_bytes(32);
$data = random_bytes(1024 * 1024 * 10); // 10 Mb
$iv = random_bytes(openssl_cipher_iv_length('aes-256-gcm'));

$start = microtime(true);
$ciphertext = openssl_encrypt($data, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
$gcmTimeEnc = microtime(true) - $start;

$start = microtime(true);
$decrypt = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
$gcmTimeDec = microtime(true) - $start;
if ($decrypt !== $data) {
  throw new Exception("Decryption failed for GCM");
}

$iv = random_bytes(openssl_cipher_iv_length('aes-256-ccm'));
$start = microtime(true);
$ciphertext = openssl_encrypt($data, 'aes-256-ccm', $key, OPENSSL_RAW_DATA, $iv, $tag);
$ccmTimeEnc = microtime(true) - $start;

$start = microtime(true);
$decrypt = openssl_decrypt($ciphertext, 'aes-256-ccm', $key, OPENSSL_RAW_DATA, $iv, $tag);
$ccmTimeDec = microtime(true) - $start;
if ($decrypt !== $data) {
  throw new Exception("Decryption failed for CCM");
}

printf("GCM (enc): %.4f, GCM (dec): %.4f\n", $gcmTimeEnc, $gcmTimeDec);
printf("CCM (enc): %.4f, CCM (dec): %.4f\n", $ccmTimeEnc, $ccmTimeDec);

I executed this script using PHP7.1RC5, CPU Intel Core i7, 8 GB RAM, 256 GB SSD, Ubuntu 16.04.

Note: All the PHP scripts used in this post are available here.

Sign and verify a file using OpenSSL

If you need to sign and verify a file you can use the OpenSSL command line tool. OpenSSL is a common library used by many operating systems (I tested the code using Ubuntu Linux).

I was working on a prototype to sign the source code of open source projects in order to release it including the signature. More or less the same idea implemented in Git to sign tag or a commit. Git uses GnuPG, I wanted to do the same using OpenSSL to be more general.

Sign a file

To sign a file using OpenSSL you need to use a private key. If you don’t have an OpenSSL key pair you can create it using the following commands:

openssl genrsa -aes128 -passout pass:<phrase> -out private.pem 4096
openssl rsa -in private.pem -passin pass:<phrase> -pubout -out public.pem

where <phrase> is the passphrase used to encrypt the private key stored in private.pem file. The private key is stored in private.pem file and the public key in the public.pem file.

For security reason, I suggest to use 4096 bits for the keys, you can read the reason in this blog post.

When you have the private and public key you can use OpenSSL to sign the file. The default output format of the OpenSSL signature is binary. If you need to share the signature over internet you cannot use a binary format. You can use for instance Base64 format for file exchange.

You can use the following commands to generate the signature of a file and convert it in Base64 format:

openssl dgst -sha256 -sign <private-key> -out /tmp/sign.sha256 <file>
openssl base64 -in /tmp/sign.sha256 -out <signature>

where <private-key> is the file containing the private key, <file> is the file to sign and <signature> is the file name for the digital signature in Base64 format.
I used the temporary folder (/tmp) to store the binary format of the digital signature. Remember, when you sign a file using the private key, OpenSSL will ask for the passphrase.

The <signature> file can now be shared over internet without encoding issue.

Verify the signature

To verify the signature you need to convert the signature in binary and after apply the verification process of OpenSSL. You can achieve this using the following commands:

openssl base64 -d -in <signature> -out /tmp/sign.sha256
openssl dgst -sha256 -verify <pub-key> -signature /tmp/sign.sha256 <file>

where <signature> is the file containing the signature in Base64, <pub-key> is the file containing the public key, and <file> is the file to verify.

If the verification is successful, the OpenSSL command will print “Verified OK” message, otherwise it will print “Verification Failure”.

I created a gist containing two bash scripts to facilitate the signature and verification tasks.
The sign.sh script is able to generate the signature of a file using the following command syntax:

sign.sh <file> <private_key>

where <file> is the file to sign and <private_key> is the file containing the private key to use for the signature. The signature will be stored in the signature.sha256 file using the Base64 format.

To verify a signature you can use the verify.sh script with the following syntax:

verify.sh <file> <signature> <public_key>

where <file> is the file to verify, <signature> is the file containing the signature (in Base64), and <public_key> is the file containing the public key to be used to verify the digital signature.

Install PHP 7 on Ubuntu 14.04

Update: if you are using Ubuntu 15.04, you can have a look at this script by Maulik Mistry.

I just installed PHP 7.0.0-dev (based on PHPNG) on my GNU/Linux box (Ubuntu 14.04) and I found some errors during the procedure. I decided to write this note to help people using the same Ubuntu environment.

Before to start I would like to remind that PHP 7 is still in development and SHOULD NOT BE USED in production environments. I installed to try and experiment the new features of the language. The first stable release of PHP 7 is scheduled by the end of the year, with a projected release date of November 2015.

To install PHP 7 we need to clone the php-src repository, configure and compile. Let’s create a php7 folder in the home directory and clone the project:

mkdir $HOME/php7
cd $HOME/php7
git clone https://git.php.net/repository/php-src.git

After that, we need to prepare and configure the compiler. We need to execute the following commands:

cd php-src
./buildconf
./configure \
    --prefix=$HOME/php7/usr \
    --with-config-file-path=$HOME/php7/usr/etc \
    --enable-mbstring \
    --enable-zip \
    --enable-bcmath \
    --enable-pcntl \
    --enable-ftp \
    --enable-exif \
    --enable-calendar \
    --enable-sysvmsg \
    --enable-sysvsem \
    --enable-sysvshm \
    --enable-wddx \
    --with-curl \
    --with-mcrypt \
    --with-iconv \
    --with-gmp \
    --with-pspell \
    --with-gd \
    --with-jpeg-dir=/usr \
    --with-png-dir=/usr \
    --with-zlib-dir=/usr \
    --with-xpm-dir=/usr \
    --with-freetype-dir=/usr \
    --with-t1lib=/usr \
    --enable-gd-native-ttf \
    --enable-gd-jis-conv \
    --with-openssl \
    --with-mysql=/usr \
    --with-pdo-mysql=/usr \
    --with-gettext=/usr \
    --with-zlib=/usr \
    --with-bz2=/usr \
    --with-recode=/usr \
    --with-mysqli=/usr/bin/mysql_config

During the execution of configure command, I found a couple of errors.

Error: Your t1lib distribution is not installed correctly.

I fixed using:

sudo apt-get install libt1-dev

Error: Unable to locate gmp.h

I tried to install the libgmp-dev library:

sudo apt-get install libgmp-dev

but the library was already installed, so I search for it:

locate gm.h

and I found it on /usr/include/x86_64-linux-gnu/gmp.h (I’m using a 64bit version).

I tried to symlink it to /usr/include/gmp.h (the default location for include):

ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/include/gmp.h 

and finally the configure execution was successful. If you find other errors on your environment I suggest to read this post by Maciej Zgadzaj, where he reported a list of possible errors and solutions for Ubuntu systems.

Now it’s time to compile PHP 7 with the following commands:

make
make install

The first command compile the PHP. I took about 9 minutes to compile PHP 7 on my computer (Intel i5-2500 at 3.3Ghz). The second command install the PHP modules and configuration in $HOME/php7/usr folder.

We have almost done the installation, we just need to create the php.ini file in the $HOME/php7/usr/etc folder. We can easily create it using vi with the following commands:

mkdir $HOME/php7/usr/etc
vi $HOME/php7/usr/etc/php.ini

We can use the following content for the php.ini:

max_execution_time=600
memory_limit=128M
error_reporting=0
display_errors=0
log_errors=0
user_ini.filename=
realpath_cache_size=2M
cgi.check_shebang_line=0

zend_extension=opcache.so
opcache.enable_cli=1
opcache.save_comments=0
opcache.fast_shutdown=1
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.use_cwd=1
opcache.max_accelerated_files=100000
opcache.max_wasted_percentage=5
opcache.memory_consumption=128
opcache.consistency_checks=0

And finally we can test PHP using the command line interface (CLI):

$HOME/php7/php-src/sapi/cli/php -v

You will get a result like this:

PHP 7.0.0-dev (cli) (built: xxx) 
Copyright (c) 1997-2015 The PHP Group
Zend Engine v3.0.0-dev, Copyright (c) 1998-2015 Zend Technologies

If you want to learn about the new features of PHP 7 I suggest to read the following resources: