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

#PHPでもJWTを使うために、実測してみた。

　JWTは非常に便利です。
　流行ってるのもありますが、何より、ただ単にユーザ名と少しの情報をやりとりするのに、セッションは面倒すぎます。
　~~/tmpをクリアする人とか、バランシングするからDBに入れてーって言いながら、ご丁寧にジャーナル残してる人とか、まぁいろんな人を無視して、~~完結するAPIまわりが簡単に書けます。

- ログインしたらユーザ情報を署名してそのままトークンとして保存。
- トークンだけで、新しいトークンを返すことで有効期間延長もできる。
- ajaxでsession_idを渡したりどうの、なんて処理もしなくて済む。

　と至れりつくせりです。

　で、今回JWTをPHPから使う上で、一体PHPではどれぐらいのコストがかかるのだろう、と実測してみました。

## キーの作り方

```text
#楕円曲線 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
```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();
}
?>
```