I recently stumbled over a gathering of people debating whether Twofish was a worse choice than AES, because “well, it said on Wikipedia that Twofish was slightly slower than AES”. It stood clear to me that the person bringing this argument was 1) only concerned about speedy crypto (and not so much about data safety – do we trust AES still?), 2) not aware of the different algorithmic complexities of these two ciphers, and 3) completely oblivious to the fact that depending on how much of a coder vs. software engineer jack-ass a developer is, his/her implementation will perform better or worse than that of another developer.
Without further ado, to quickly illustrate the point in question that ciphers can perform wildly different from one implementation to the other, I present you with a snippet of PHP code that utilizes the mcrypt library:
<?php // pseudo-random binary data function rnd( $length ) { while( @$i++ * 16 < $length ) @$tmp .= md5( mt_rand(), true ); return substr( $tmp, 0, $length ); } // our suite of cipher setups to test, incl. extra ciphers for good measure $suite = array( ' Twofish/256'=>array('c'=>MCRYPT_TWOFISH, 'key'=>32, 'iv'=>16), ' AES/128'=>array('c'=>MCRYPT_RIJNDAEL_128, 'key'=>16, 'iv'=>16), ' AES/256'=>array('c'=>MCRYPT_RIJNDAEL_256, 'key'=>32, 'iv'=>32), ' 3DES/168'=>array('c'=>MCRYPT_3DES, 'key'=>21, 'iv'=>8), ' Serpent/256'=>array('c'=>MCRYPT_SERPENT, 'key'=>32, 'iv'=>16), 'Blowfish/448'=>array('c'=>MCRYPT_BLOWFISH, 'key'=>56, 'iv'=>8) ); /* we run the entire cipher suite twice; first with 50 1mb blocks and then with 3200 16kb blocks, just to see how each cipher's preparatory key schedule affects its overall performance */ $sizes = array( '50'=>rnd(1024*1024), '3200'=>rnd(16*1024) ); $key = rnd( 56 ); $iv = rnd( 32 ); // here we go foreach( $sizes as $times => $data ) { echo "\n$times runs of " . strlen( $data ) . " bytes each\n---\n"; foreach( $suite as $descr => $cipher ) { echo "$descr\t"; $k = substr( $key, 0, $cipher['key'] ); $i = substr( $iv, 0, $cipher['iv'] ); $count = $times; $then = microtime( true ); while( $count-- ) $enc = mcrypt_encrypt( $cipher['c'], $k, $data, MCRYPT_MODE_CBC, $i ); printf( "%.4fs\n", microtime(true) - $then ); } } ?>
Let’s see how this goes on my server machine, equipped with a 1ghz Sempron running OpenBSD 4.8/i386 with PHP 5.3.3 and libmcrypt 2.5.8, all built with GCC 4.2.1:
50 runs of 1048576 bytes each --- Twofish/256 2.9234s AES/128 7.5719s AES/256 9.6267s 3DES/168 28.6203s Serpent/256 4.3912s Blowfish/448 3.1937s 3200 runs of 16384 bytes each --- Twofish/256 4.1996s AES/128 8.5839s AES/256 11.9875s 3DES/168 32.6580s Serpent/256 6.4841s Blowfish/448 6.1409s
We can instantly see that in the libmcrypt implementation, Twofish performs more than twice as fast as AES/128, and more than three times as fast as AES/256. Building upon this, we can also see that in this case Twofish even performs faster than Blowfish, despite it being far simpler algorithmically. And lastly, there is the interesting detail in the second run of tests, in how the various ciphers’ key schedules make themselves reminded – in particular the highly intricate key schedule of the Blowfish cipher.