Published on Oct 4, 2013, updated on Jun 10, 2015

Benchmarking symmetric cyphers in PHP - OpenSSL vs. Mcrypt

Symmetric (two-way) cyphers are simple and secure way of transferring unencrypted data over the internet. A typical use case would be sending URL link with parameter called "contractID". In some situations it is undesirable to display what is the actual value of the contract ID - user can be tempted to play with the value, or a competitor may learn how many contracts did you make yesterday :-)

Encrypting such values in PHP is actually very easy (though unforgettably rarely used) - works right out of the box just by enabling standard core PHP extension.

PHP does not offer too many ways of using symmetric ciphers - the most used are:

Both libraries offer symmetric algorithms, but each at different speed and capabilities.

Bellow are basic tests comparing encryption speed of both extensions for short (1-4 bytes) and longer strings (1 - 4 kB). A synthetic test of all supported OpenSSL encryption methods is included also in TEST 7. Tests were performed on apache with PHP 5.4.26 x86 VC9/TS with mcrypt 2.5.8 and openSSL 0.9.8y.

Mcrypt vs. openSSL - Short string 1-4 bytes in secs.
(avg. results of 50 runs)

 
1000 loops / ø 1 cycle
5000 loops / ø 1 cycle
TEST 1 - MCRYPT1 - encrypt only
0,268 / 0.000268
1,296 / 0.000259
TEST 2 - MCRYPT1 - encrypt + decrypt
0,511 / 0.000511
2,540 / 0.000508
TEST 3 - AES 2562 - encrypt only
0,006 / 0.000006
0,029 / 0.000006
TEST 4 - AES 2562 - encrypt + decrypt
0,013 / 0.000013
0,059 / 0.000011
TEST 5 - AES 1282 - encrypt only
0,006 / 0.000006
0,031 / 0.000006
TEST 6 - AES 1282 - encrypt + decrypt
0,013 / 0.000013
0,063 / 0.000012
 

Mcrypt vs. openSSL - Medium string 1-4 kB in secs. (avg. results of 50 runs)

 
1000 loops / ø 1 cycle
5000 loops / ø 1 cycle
TEST 1 - MCRYPT1 - encrypt only
0,504 / 0.000504
2,883 / 0.000577
TEST 2 - MCRYPT1 - encrypt + decrypt
1,004 / 0.001004
5,785 / 0.001157
TEST 3 - AES 2562 - encrypt only
0,033 / 0.000033
0,207 / 0.000041
TEST 4 - AES 2562 - encrypt + decrypt
0,086 / 0.000086
0,547 / 0.000109
TEST 5 - AES 1282 - encrypt only
0,022 / 0.000022
0,133 / 0.000026
TEST 6 - AES 1282 - encrypt + decrypt
0,065 / 0.000065
0,402 / 0.000080
 
1 - Mcrypt Blowfish with CFB mode
2 - OpenSSL AES 128 or (256) with CFB mode

Mcrypt vs. openSSL - 200 kB in secs. (avg. results of 50 runs)

 
avg. ø 1 cycle
TEST 1 - MCRYPT (blowfish, CFB) - encrypt only
0.0182
TEST 2 - MCRYPT (blowfish, CFB) - encrypt + decrypt
0.0344
TEST 3 - OpenSSL (AES 256 CFB) - encrypt only
0.0020
TEST 4 - OpenSSL (AES 256 CFB) - encrypt + decrypt
0.0052
TEST 5 - OpenSSL (AES 128 CBC) - encrypt only
0.0011
TEST 6 - OpenSSL (AES 128 CBC) - encrypt + decrypt
0,0040
   

Mcrypt vs. openSSL - 20 MB in secs. (avg. results of 50 runs)

 
avg. ø 1 cycle
TEST 1 - MCRYPT (blowfish, CFB) - encrypt only
1.7284
TEST 2 - MCRYPT (blowfish, CFB) - encrypt + decrypt
3.4628
TEST 3 - OpenSSL (AES 256 CFB) - encrypt only
0.1958
TEST 4 - OpenSSL (AES 256 CFB) - encrypt + decrypt
0.5648
TEST 5 - OpenSSL (AES 128 CBC) - encrypt only
0.1156
TEST 6 - OpenSSL (AES 128 CBC) - encrypt + decrypt
0,4062

TEST 7 - Fastest OpenSSL algorithms are at the top (in secs, avg. 50 runs)

Fastest OpenSSL algorithms - 200 kB

Fastest OpenSSL algorithms - 2 MB

[AES-192-OFB] => 0.000*
[AES-256-OFB] => 0.000*
[AES-128-CBC] => 0.000*
[AES-256-CFB] => 0.000*
[BF-CFB] => 0.000*
[IDEA-ECB] => 0.000*
[AES-192-CFB] => 0.000*
[CAST5-CBC] => 0.000*
[RC4] => 0.000*
[CAST5-OFB] => 0.000*
[AES-128-OFB] => 0.000*
[AES-128-ECB] => 0.000*
[DESX-CBC] => 0.010
[DES-EDE3-CFB] => 0.010
[DES-OFB] => 0.010
[DES-ECB] => 0.010
[DES-EDE-CFB] => 0.010
[IDEA-OFB] => 0.010
[RC2-ECB] => 0.010
[RC2-OFB] => 0.010
[RC4-40] => 0.010
[RC2-CBC] => 0.010
[RC2-64-CBC] => 0.010
[IDEA-CFB] => 0.010
[RC2-40-CBC] => 0.010
[IDEA-CBC] => 0.010
[DES-CFB] => 0.010
[AES-256-ECB] => 0.010
[BF-CBC] => 0.010
[BF-ECB] => 0.010
[BF-OFB] => 0.010
[CAST5-CFB] => 0.010
[AES-256-CBC] => 0.010
[AES-128-CFB] => 0.010
[AES-192-CBC] => 0.010
[CAST5-ECB] => 0.010
[AES-192-ECB] => 0.010
[DES-CBC] => 0.010
[DES-EDE3-OFB] => 0.011
[DES-EDE] => 0.020
[RC2-CFB] => 0.020
[DES-EDE3] => 0.020
[DES-EDE-CBC] => 0.020
[DES-EDE3-CBC] => 0.020
[DES-EDE-OFB] => 0.022
[AES-128-CFB8] => 0.040
[AES-192-CFB8] => 0.040
[DES-CFB8] => 0.050
[AES-256-CFB8] => 0.050
[DES-CFB1] => 0.052
[DES-EDE3-CFB8] => 0.131
[DES-EDE3-CFB1] => 0.142
[AES-128-CFB1] => 0.344
[AES-192-CFB1] => 0.392
[AES-256-CFB1] => 0.428

* 0.000 - not measurable for the setup
[RC4-40] => 0.030
[RC4] => 0.030
[AES-192-CBC] => 0.040
[AES-256-CBC] => 0.040
[AES-128-ECB] => 0.040
[AES-192-ECB] => 0.050
[AES-192-OFB] => 0.050
[AES-128-OFB] => 0.050
[AES-128-CFB] => 0.050
[AES-256-OFB] => 0.050
[BF-ECB] => 0.060
[BF-CBC] => 0.060
[AES-128-CBC] => 0.060
[AES-256-ECB] => 0.060
[CAST5-CBC] => 0.060
[BF-OFB] => 0.060
[AES-256-CFB] => 0.060
[AES-192-CFB] => 0.060
[CAST5-CFB] => 0.070
[CAST5-ECB] => 0.070
[IDEA-CBC] => 0.070
[DES-CBC] => 0.070
[BF-CFB] => 0.070
[CAST5-OFB] => 0.070
[IDEA-ECB] => 0.076
[IDEA-OFB] => 0.080
[DESX-CBC] => 0.080
[DES-OFB] => 0.090
[IDEA-CFB] => 0.090
[DES-ECB] => 0.090
[DES-CFB] => 0.090
[RC2-CBC] => 0.100
[RC2-64-CBC] => 0.100
[RC2-ECB] => 0.100
[RC2-40-CBC] => 0.110
[RC2-CFB] => 0.140
[RC2-OFB] => 0.140
[DES-EDE-CBC] => 0.170
[DES-EDE3-CBC] => 0.170
[DES-EDE] => 0.170
[DES-EDE-OFB] => 0.180
[DES-EDE3-CFB] => 0.180
[DES-EDE3] => 0.180
[DES-EDE-CFB] => 0.190
[DES-EDE3-OFB] => 0.190
[AES-128-CFB8] => 0.370
[AES-192-CFB8] => 0.420
[AES-256-CFB8] => 0.470
[DES-CFB8] => 0.489
[DES-CFB1] => 0.570
[DES-EDE3-CFB8] => 1.270
[DES-EDE3-CFB1] => 1.380
[AES-128-CFB1] => 3.450
[AES-192-CFB1] => 3.870
[AES-256-CFB1] => 4.327

Conclusion:

  • OpenSSL offers significantly faster algorithms. This is the right choice for heavy traffic sites.
  • Mcrypt can use rotating ciphers when using random seed in initiation vector (IV). This is GREAT feature because makes attacker's job much tougher. In another words - that same string results into different encrypted string. This feature is similar to SSHA hashes used e.g. by LDAP servers.
  • Choosing AES 256 over AES 128 should be safer choice while loosing only insignificant overhead.
  • The fastest openSSL algorithms are RC-*, AES-*-OFB, AES-*-CFB algorithms, even though the performance difference amongst first 20 ciphers is negligable.
  • two openSSL algorithms seems to be broken (DES-CFB1, DES-EDE3-CFB1) while returning invalid decrypted text. I am not sure if I did not set some parameter correctly or this is really a bug or has been fixed in newer versions of PHP. The CFB1 based algos seems to be the slowest.
  • [edit 2015-06] Mcrypt actually uses long time abandoned C-library, while OpenSSL is actively maintained.

Testing scenario

For those interested, bellow is a fully reproducable testing scenario.

PHP Encryption / Decryption class


/**
* Encryption / decryption PHP class for symmetric (two-way) ciphers
*/
class Data{

	const
		CYPHER = 'blowfish',
		MODE   = 'cfb',
		SALT   = '7d9!y8y4=h7v2v8v*1|1';

	/**
	* Encrypt string using mcrypt module
	* @param string $plaintext Text to be encrypted
	* @param string $password User entered password
	*/
	public static function encryptMcrypt($plaintext, $password = ''){
		$td = mcrypt_module_open(self::CYPHER, '', self::MODE, '');
		$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
		$key = self::SALT.trim($password);
		mcrypt_generic_init($td, $key, $iv);
		$crypttext = mcrypt_generic($td, $plaintext);
		mcrypt_generic_deinit($td);
		$crypttext = $iv.$crypttext;
		return $crypttext;
	}

	/**
	* Decrypt string using mcrypt module
	* @param string $crypttext Text to be decrypted
	* @param string $password User entered password
	*/
	public static function decryptMcrypt($crypttext, $password = ''){
		$td        = mcrypt_module_open(self::CYPHER, '', self::MODE, '');
		$ivsize    = mcrypt_enc_get_iv_size($td);
		$iv        = substr($crypttext, 0, $ivsize);
		$crypttext = substr($crypttext, $ivsize);
		$key = self::SALT.trim($password);
		mcrypt_generic_init($td, $key, $iv);
		return mdecrypt_generic($td, $crypttext);
	}

	/**
	* Encrypt string using openSSL module
	* @param string $textToEncrypt
	* @param string $encryptionMethod One of built-in 50 encryption algorithms
	* @param string $secretHash Any random secure SALT string for your website
	* @param bool $raw If TRUE return base64 encoded string
	* @param string $password User's optional password
	*/
	public static function encryptOpenssl($textToEncrypt, $encryptionMethod = 'AES-256-CFB', $secretHash = self::SALT, $raw = false, $password = ''){
		$length = openssl_cipher_iv_length($encryptionMethod);
		$iv = substr(md5($password), 0, $length);
		return openssl_encrypt($textToEncrypt, $encryptionMethod, $secretHash, $raw, $iv);
	}

	/**
	* Decrypt string using openSSL module
	* @param string $textToDecrypt
	* @param string $encryptionMethod One of built-in 50 encryption algorithms
	* @param string $secretHash Any random secure SALT string for your website
	* @param bool $raw If TRUE return base64 encoded string
	* @param string $password User's optional password
	*/
	public static function decryptOpenssl($textToDecrypt, $encryptionMethod = 'AES-256-CFB', $secretHash = self::SALT, $raw = false, $password = ''){
		$length = openssl_cipher_iv_length($encryptionMethod);
		$iv = substr(md5($password), 0, $length);
		return openssl_decrypt($textToDecrypt, $encryptionMethod, $secretHash, $raw, $iv);
	}
}
Following scripts were used to generate results:

// adjust test parameters as needed
$loops = 1000;
$stringMultiplyFactor = 1;

// show test settings
echo 'LOOPS: '.$loops;
echo '
MULTIPLY STRING LENGTH FACTOR: '.$stringMultiplyFactor; echo '
======================
'; echo '
TEST 1. mcrypt - encrypt only'; $t = microtime(true); for($i=0;$i < $loops;++$i){ $txt = Data::encryptMcrypt(str_repeat($i, $stringMultiplyFactor)); } echo '
result: '.round(microtime(true)-$t, 3).'
'; echo '
TEST 2. mcrypt - encrypt + decrypt'; $t = microtime(true); for($i=0 ;$i < $loops; ++$i){ $txt = Data::encryptMcrypt(str_repeat($i, $stringMultiplyFactor)); $txt2 = Data::decryptMcrypt($txt); if(str_repeat($i, $stringMultiplyFactor)!=$txt2){ exit('error: '.str_repeat($i, $stringMultiplyFactor).' != '.$txt2); } } echo '
result: '.round(microtime(true)-$t, 3).'
'; echo '
TEST 3. openssl - encrypt only AES 256'; $t = microtime(true); for($i=0; $i < $loops; ++$i){ $txt = Data::encryptOpenssl(str_repeat($i, $stringMultiplyFactor)); } echo '
result: '.round(microtime(true)-$t, 3).'
'; echo '
TEST 4. openssl - encrypt + decrypt'; $t = microtime(true); for($i=0;$i < $loops; ++$i){ $txt = Data::encryptOpenssl(str_repeat($i, $stringMultiplyFactor)); $txt2 = Data::decryptOpenssl($txt); // .9 if(str_repeat($i, $stringMultiplyFactor)!=$txt2){ exit('error: '.str_repeat($i, $stringMultiplyFactor).' != '.$txt2); } } echo '
result: '.round(microtime(true)-$t, 3).'
'; echo '
TEST 5. openssl - encrypt only / AES 128'; $t = microtime(true); for($i=0; $i < $loops; ++$i){ $txt = Data::encryptOpenssl(str_repeat($i, $stringMultiplyFactor), 'AES-128-CBC'); } echo '
result: '.round(microtime(true)-$t, 3).'
'; echo '
TEST 6. openssl - encrypt + decrypt'; $t = microtime(true); for($i=0; $i < $loops; ++$i){ $txt = Data::encryptOpenssl(str_repeat($i, $stringMultiplyFactor), 'AES-128-CBC'); $txt2 = Data::decryptOpenssl($txt, 'AES-128-CBC'); if(str_repeat($i, $stringMultiplyFactor)!=$txt2){ exit('error: '.str_repeat($i, $stringMultiplyFactor).' != '.$txt2); } } echo '
result: '.round(microtime(true)-$t, 3).'
'; echo '
TEST 7 - syntethic test - loop benchmark all openSSL AES supported algorithms
'; // collect all supported algorithms $a = openssl_get_cipher_methods(); $a = array_flip($a); $a = array_change_key_case($a, CASE_UPPER); $a = array_keys($a); $failing = array(); $sort = array(); foreach($a as $c => $method){ echo '
'.++$c.'. openssl - encrypt + decrypt ... ['.$method.']'; $t = microtime(true); for($i=0;$i < $loops; ++$i){ $txt = Data::encryptOpenssl(str_repeat($i, $stringMultiplyFactor), $method); $txt2 = Data::decryptOpenssl($txt.'*', $method); if(str_repeat($i, $stringMultiplyFactor)!=$txt2){ $failing[$method] ='
failed checksum ----> error: '.str_repeat($i, $stringMultiplyFactor).' != '.$txt2; } } $t = microtime(true)-$t; echo ' ... result: '.number_format($t, 5); $sort[$method] = number_format($t,3); } asort($sort); // fastest at the top echo '
Sorted results from TEST 7:
'.print_r($sort, true).'
'; // any errors? if($failing){ echo '
TEST 7 - Crashing openSSL ciphers:
'.print_r($failing, true).'
'; }

Got a question?

Synet.sk

Professional development of web applications and custom solutions. Consultancy services.

Demo

Contact


https://synet.sk