要在PHP中客观比较AES与Twofish的加解密性能,必须建立一套可重复、抗干扰的测量流程。AES作为当前对称加密的事实标准,在OpenSSL扩展中拥有原生实现,并且多数现代CPU都提供AES-NI硬件加速指令。Twofish虽然也是AES评选时的最终候选算法之一,但PHP核心并未内置支持,通常需要借助phpseclib这类纯PHP库来实现。两者在实现层级上的差异,会直接反映在耗时数据上。如果只是简单地在代码前后调用一次microtime(true),得到的往往是噪声大于信号的结果。

本文的目标不是给出一个固定不变的“AES比Twofish快多少倍”的结论,而是提供一套完整的测量方法,包括环境准备、采样策略、数据处理和误区排查。通过这套方法,开发者可以在自己的服务器上得到符合实际部署环境的性能数据,为加密方案选型提供依据。
测量前的环境准备与算法实现
测量开始之前,需要确认PHP环境已启用openssl扩展。可以通过php -m | grep openssl检查。AES加解密直接使用openssl_encrypt和openssl_decrypt即可,无需额外安装。Twofish则推荐使用phpseclib3,通过Composer安装:composer require phpseclib/phpseclib。phpseclib3的命名空间为phpseclib3CryptTwofish,注意路径中的反斜杠表示命名空间分隔符,在代码中必须原样保留。
下面的代码展示了AES-256-CBC的加解密基本实现。密钥长度为32字节,IV固定为16字节,明文数据使用random_bytes生成。实际测量时建议将明文数据预先固定,避免每次循环都重新生成随机数据,把随机数生成器的开销混入加解密耗时。
<?php $data = random_bytes(1024); // 1KB明文 $key = random_bytes(32); // AES-256密钥 $iv = random_bytes(16); // CBC模式IV $start = hrtime(true); $ciphertext = openssl_encrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); $encryptTime = hrtime(true) - $start; $start = hrtime(true); $plaintext = openssl_decrypt($ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); $decryptTime = hrtime(true) - $start; var_dump($plaintext === $data); // 验证解密正确
Twofish的代码则稍显不同。phpseclib3采用面向对象风格,需要先实例化密码对象、设置密钥和IV,再调用encrypt和decrypt方法。下面的示例同样使用CBC模式和1KB数据。需要注意的是,phpseclib内部会根据模式进行填充处理,默认使用PKCS7填充,这与OpenSSL的OPENSSL_RAW_DATA加CBC模式行为一致。
<?php
require 'vendor/autoload.php';
use phpseclib3CryptTwofish;
$data = random_bytes(1024);
$key = random_bytes(32);
$iv = random_bytes(16);
$cipher = new Twofish('cbc');
$cipher->setKey($key);
$cipher->setIV($iv);
$start = hrtime(true);
$ciphertext = $cipher->encrypt($data);
$encryptTime = hrtime(true) - $start;
$start = hrtime(true);
$plaintext = $cipher->decrypt($ciphertext);
$decryptTime = hrtime(true) - $start;
var_dump($plaintext === $data);
在正式测量之前,务必先运行几轮加解密验证正确性。尤其是Twofish的CBC模式,IV长度必须为16字节,否则phpseclib会抛出异常。AES的密钥长度可选择16、24或32字节,分别对应AES-128、AES-192和AES-256。为了公平对比,建议统一使用256位密钥,即32字节。
使用microtime与多次采样测量耗时
测量加解密耗时最常用的工具是hrtime(true),它返回纳秒级的时间戳,精度比microtime(true)更高,并且不受系统时钟调整的影响。单次测量几乎没有意义,因为操作系统调度、缓存命中率、PHP垃圾回收等因素都会造成数倍甚至数十倍的波动。正确做法是将同一操作重复执行多次,收集每次的耗时,然后取中位数或平均值。
中位数比平均值更适合性能测量。假设循环100次,其中一次因为CPU被其他进程抢占导致耗时异常放大,平均值会被严重拉高,而中位数则能保持稳定。下面的benchmark函数封装了预热、多次采样和排序取中位数的逻辑。预热步骤非常重要,它能让PHP的实时编译缓存、函数查找表以及OpenSSL内部状态先稳定下来。
<?php
function benchmark(callable $fn, int $iterations = 100): float {
// 预热一次,消除首次调用开销
$fn();
$times = [];
for ($i = 0; $i < $iterations; $i++) {
$start = hrtime(true);
$fn();
$times[] = hrtime(true) - $start;
}
sort($times);
// 返回中位数,单位毫秒
return $times[intdiv(count($times), 2)] / 1e6;
}
// 准备固定数据
$data = str_repeat('A', 1024);
$key = str_repeat('K', 32);
$iv = str_repeat('I', 16);
$ciphertext = openssl_encrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$aesEncrypt = benchmark(function () use ($data, $key, $iv) {
openssl_encrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
});
$aesDecrypt = benchmark(function () use ($ciphertext, $key, $iv) {
openssl_decrypt($ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
});
echo "AES加密中位数: {$aesEncrypt} msn";
echo "AES解密中位数: {$aesDecrypt} msn";
对于Twofish,需要注意对象实例化不能放入被测闭包内。new Twofish('cbc')、setKey和setIV的耗时属于初始化成本,不应计入加解密耗时。正确的做法是在benchmark外部完成初始化,只在闭包内调用encrypt或decrypt方法。下面的示例演示了完整的Twofish测量过程。
<?php
require 'vendor/autoload.php';
use phpseclib3CryptTwofish;
$data = str_repeat('A', 1024);
$key = str_repeat('K', 32);
$iv = str_repeat('I', 16);
$cipher = new Twofish('cbc');
$cipher->setKey($key);
$cipher->setIV($iv);
$ciphertext = $cipher->encrypt($data);
$twofishEncrypt = benchmark(function () use ($cipher, $data) {
$cipher->encrypt($data);
});
$twofishDecrypt = benchmark(function () use ($cipher, $ciphertext) {
$cipher->decrypt($ciphertext);
});
echo "Twofish加密中位数: {$twofishEncrypt} msn";
echo "Twofish解密中位数: {$twofishDecrypt} msn";
数据块大小对耗时影响非常明显。1KB数据下,AES可能只需要几微秒,而100MB数据则需要数百毫秒。建议分别测量小数据块(如64字节)、中等数据块(如1KB)和大数据块(如1MB),以便观察算法在标量与批量场景下的表现差异。另外,循环次数至少设置为100次,对于极快的操作可以增加到1000次,以保证统计稳定性。
结果分析与性能优化建议
在典型服务器环境中,AES-256-CBC的单次加解密耗时通常为微秒级。如果CPU支持AES-NI指令集,OpenSSL会自动调用硬件加速路径,1KB数据的加密耗时可能低至2到5微秒。Twofish由于缺乏原生实现,phpseclib3的纯PHP代码在同样的数据量下可能需要数十微秒甚至上百微秒。两者的差距通常在5到30倍之间,具体取决于PHP版本、服务器CPU型号以及phpseclib的版本。
以下表格给出了一组参考数据,来自一台支持AES-NI的x86_64服务器,PHP 8.2,OpenSSL 3.0,phpseclib 3.0,数据块大小为1KB,循环100次取中位数。实际结果可能与你的环境有差异,但总体趋势是一致的。
| 操作 | 中位数耗时(毫秒) |
|---|---|
| AES加密 | 0.0038 |
| AES解密 | 0.0041 |
| Twofish加密 | 0.0620 |
| Twofish解密 | 0.0643 |
如果测量结果显示Twofish异常慢,先检查是否误将对象实例化或随机数据生成放入了循环。例如把new Twofish('cbc')写在闭包内部,耗时会立刻增加数十倍。另一个常见问题是使用了random_bytes每次生成新明文,这会把CSPRNG的耗时错误地归到加密头上。对于AES,还应注意不要使用OPENSSL_ZERO_PADDING,除非你手动处理填充,否则解密结果可能包含多余的