PHPでJWTを使う際の、キーペア作成と処理時間について


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();
}
?>