#PHPでもJWTを使うために、実測してみた。
JWTは非常に便利です。
流行ってるのもありますが、何より、ただ単にユーザ名と少しの情報をやりとりするのに、セッションは面倒すぎます。
~~/tmpをクリアする人とか、バランシングするからDBに入れてーって言いながら、ご丁寧にジャーナル残してる人とか、まぁいろんな人を無視して、~~完結するAPIまわりが簡単に書けます。
- ログインしたらユーザ情報を署名してそのままトークンとして保存。
- トークンだけで、新しいトークンを返すことで有効期間延長もできる。
- ajaxでsession_idを渡したりどうの、なんて処理もしなくて済む。
と至れりつくせりです。
で、今回JWTをPHPから使う上で、一体PHPではどれぐらいのコストがかかるのだろう、と実測してみました。
キーの作り方
#楕円曲線 256bit
openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-pair.pem
openssl ec -in ec256-key-pair.pem -outform PEM -pubout -out ec256-key-pub.pem
openssl ec -in ec256-key-pair.pem -outform PEM -out ec256-key-pri.pem
#楕円曲線 384bit
openssl ecparam -genkey -name secp384r1 -noout -out ec384-key-pair.pem
openssl ec -in ec384-key-pair.pem -outform PEM -pubout -out ec384-key-pub.pem
openssl ec -in ec384-key-pair.pem -outform PEM -out ec384-key-pri.pem
#楕円曲線 512bit
openssl ecparam -genkey -name secp521r1 -noout -out ec512-key-pair.pem
openssl ec -in ec512-key-pair.pem -outform PEM -pubout -out ec512-key-pub.pem
openssl ec -in ec512-key-pair.pem -outform PEM -out ec512-key-pri.pem
#RSA 2048bit
openssl genpkey -algorithm RSA -out rsa2048-key-pri.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in rsa2048-key-pri.pem -out rsa2048-key-pub.pem
実測結果
対象文字列 10文字
アルゴリズム | トークン長さ | 署名時間(ms) | 検証時間(ms) |
---|---|---|---|
ES256 | 211 | 1.32 | 1.35 |
ES384 | 253 | 2.02 | 2.30 |
ES512 | 301 | 4.09 | 5.06 |
RS256 | 457 | 4.08 | 0.33 |
RS512 | 457 | 3.91 | 0.33 |
対象文字列 100文字
アルゴリズム | トークン長さ | 署名時間(ms) | 検証時間(ms) |
---|---|---|---|
ES256 | 331 | 1.12 | 1.30 |
ES384 | 373 | 2.00 | 2.32 |
ES512 | 421 | 4.11 | 5.05 |
RS256 | 577 | 3.98 | 0.35 |
RS512 | 577 | 3.90 | 0.33 |
対象文字列 500文字
アルゴリズム | トークン長さ | 署名時間(ms) | 検証時間(ms) |
---|---|---|---|
ES256 | 863 | 1.18 | 1.36 |
ES384 | 904 | 2.09 | 2.46 |
ES512 | 952 | 4.03 | 5.30 |
RS256 | 1110 | 4.14 | 0.41 |
RS512 | 1110 | 4.08 | 0.35 |
考察
- RSAは検証が超早い
- 常識的に考えてヘッダに1KBも割けないので、安全な範囲でRSAでトークンのやりとりをするのであればjsonにして100文字前後のデータで。
- 楕円曲線暗号の、256bitは割と安定したスコア。
- トークン長さと強度の無難さでいえば、ES384ぐらいが便利か。(RSA 3072bit相当)
参考 ベンチマークに使ったphp
jwt-test.php
<?php
require 'vendor/autoload.php';
use Namshi\JOSE\SimpleJWS;
//jwt-test.php
$keys = ["ES256"=>"ec256","ES384"=>"ec384","ES512"=>"ec521","RS256"=>"rsa2048","RS512"=> "rsa2048"];
$testobjs = ["1234567890",
"1234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980",
"12345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980123456798012345679801234567980"];
foreach($testobjs as $obj){
print "object length:" . strlen($obj) ."\n";
foreach($keys as $name=>$key){
$prikey="file:///home/xxxx/temp2/$key-key-pri.pem";
$token = sign($prikey,$name,$obj);
print "|" . $name . "|" . strlen($token) . "|a";
$start = microtime(true);
for($i=0;$i<1000;$i++){
$token = sign($prikey,$name,$obj);
}
$end = microtime(true)-$start;
print number_format($end,2) . "|";
foreach($keys as $namex=>$keyx){
$pubkey="file:///home/xxxx/temp2/$keyx-key-pub.pem";
$v = validate($pubkey,$namex,$token);
if($v===$obj){
//print " VALID as $namex\t\t\t";
$start = microtime(true);
for($i=0;$i<1000;$i++){
$dmy = validate($pubkey,$namex,$token);
}
$end = microtime(true)-$start;
print number_format($end,2) . "|\n";
}else{
//print " INVALID \n";
}
}
}
}
function validate($pubkey,$type,$data){
$jws = SimpleJWS::load($data);
$public_key = openssl_pkey_get_public($pubkey);
if ($jws->isValid($public_key, $type)) {
$payload = $jws->getPayload();
return $payload["data"];
}else{
return "";
}
}
function sign($prikey,$type,$data){
$date = new DateTime('+7 days');
$jws = new SimpleJWS(array(
'alg' => $type,
'exp' => $date->format('U')
));
$jws->setPayload(
["data"=>$data]
);
$privateKey = openssl_pkey_get_private($prikey,"");
$jws->sign($privateKey);
return $jws->getTokenString();
}
?>