こんにちは、PR TIMESで学生インターンをしている田中湧大です。
今回は、PHPでパフォーマンスの観点から署名アルゴリズムについて検証してみたのでその紹介をしたいと思います。
検証しようと思った背景
現在、PR TIMESでは各マイクロサービスが独自のsessionを発行しており、様々な問題が発生しているため、認証基盤をJWTを用いたものに置き換えようと改善を進めています。
その過程でJWTに署名するアルゴリズムを選択する必要がありました。
PR TIMESのサービスはPHPで実装されています。なのでPHP上で候補となるアルゴリズムの署名と検証のベンチマークを取ることにしました。
ベンチマークについて
比較対象のアルゴリズム
まず比較対象のアルゴリズムを列挙します。
- RSA 2048 bit
- EdDSA
- ECDSA 384 bit (prime256v1)
これらは全て非対称鍵暗号(公開鍵暗号)と呼ばれている暗号方式です。
メインで比較したい対象はRSAとEdDSAです。
RSAは一般的にこれ以上と推奨される2048 bitとしました。
ECDSAは少なくとも384 bitが必要とされています。
Warning: openssl_pkey_new(): private key length is too short; it needs to be at least 384 bits, not 256
実行環境
- Apple M1 Pro, Memory 32GB
- PHP 8.2
計測方法
RSAとECDSAはopenssl、EdDSAはsodiumで鍵生成・署名・検証を行いました。
これは使用予定のJWTライブラリがEdDSAを使用する場合はsodiumで処理を行っていたためです。
sodiumのインストール
本題から逸れてしまいますがPR TIMESではsodiumを新たにインストールする必要があったのでその方法について軽く紹介します。
sodiumはPHP 7.2.0以降、PHP本体にバンドルされています。インストール方法については
- configure オプション –with-sodium[=DIR]でPHPをコンパイルする。
- yumなどのパッケージ管理システム経由からRemi Repositoryで提供されている
php-sodium
をインストール。 - PECL経由で
libsodium
をインストールする。
などが挙げられますが、今回はPECL経由でインストールすることにしました。理由は主に以下の2つです。
- 本番環境ではPHPはyum経由でインストールされているため、コンパイルは少し手間がかかる。
- 開発環境がarm64の場合、Remi Repositoryにarm64向けパッケージが提供されていないこと。
話を戻します。
それぞれの鍵は以下のようなコードで生成できます。(実運用ではopensslで秘密鍵を生成することになるかと思います。)
// EdDSAの秘密鍵と公開鍵を作成
$eddsa_sign_keypair = sodium_crypto_sign_keypair();
$eddsa_sign_secretkey = sodium_crypto_sign_secretkey($eddsa_sign_keypair)
$eddsa_sign_publickey = sodium_crypto_sign_publickey($eddsa_sign_keypair);
// RSAの秘密鍵と公開鍵を作成
$rsa_key_pair = openssl_pkey_new([
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
]);
openssl_pkey_export($rsa_key_pair, $rsa_private_key_pem);
$details = openssl_pkey_get_details($rsa_key_pair);
$rsa_public_key_pem = $details['key'];
// ECDSA(prime256v1)の秘密鍵と公開鍵を作成
$ec_prime256v1_key_pair = openssl_pkey_new([
"private_key_bits" => 384,
"private_key_type" => OPENSSL_KEYTYPE_EC,
"curve_name" => "prime256v1"
]);
openssl_pkey_export($ec_prime256v1_key_pair, $ec_prime256v1_private_key_pem);
$details = openssl_pkey_get_details($ec_prime256v1_key_pair);
$ec_prime256v1_public_key_pem = $details['key'];
鍵生成後、各アルゴリズムの署名・検証を各10万回行ったタイムを計測しました。
計測のために以下のような簡単なベンチマーククラスを実装しました。
class Benchmark
{
static public function run(Closure $f, $trial_count)
{
$start_date = hrtime(true);
for ($i = 0; $i < $trial_count; $i++) {
$f();
}
return hrtime(true) - $start_date;
}
}
計測結果
sign/sは1秒あたりの署名回数、verify/sは1秒あたりの検証回数です。
アルゴリズム | sign/s | verify/s |
---|---|---|
RSA 2048bit | 1153.4 | 37790.9 |
EdDSA | 54611.9 | 20373.0 |
ECDSA 384bit (prime256v1) | 31057.5 | 14498.0 |
考察
署名についてはEdDSAが最も良いパフォーマンスを出してくれました。RSAと比較して約50倍差です。これは大方予想通りでした。
一方で検証については2倍以下の差ではあるもののRSAの方が勝る結果となりました。EdDSAは速くて優れている!と思い込んでいた自分にとっては意外な結果でした。
ECDSAについてはEdDSAには届かないものの優れたパフォーマンスを示してくれました。
この結果から今回の認証基盤に用いるJWTの要件的に署名と検証はどちらも行うことと、セキュリティ的にも優れていることからそのバランスが優れているEdDSAを採用することにしました。
最後に
今回は、PHPでパフォーマンスの観点から署名アルゴリズムについて検証してみました。
EdDSA(ed25519)は鍵長が短くGitHubのssh-keyにも推奨されている方式です。GitHubが推奨しているから優秀な方式に違いない!と思っていたところのこの結果だったので、実際に計測する重要性を学びました。
これから認証基盤の改善も進めていく予定です。