こちらはエキサイトホールディングス Advent Calendar 2021の13日目の記事です。今回はPKCE実装で使用するcode_verifierとcode_challengeをPHPを使って実装してみたいと思います。
PKCE(ピクシー)とは?
PKCE(Proof Key for Code Exchange by OAuth Public Clients)とはRFC7636で定義されている、認可コード横取り攻撃の対策として提案された仕様です。 認可コード横取り攻撃とは、認可コードを不正に取得することによって、アクセストークンなどのトークンを不正に取得する攻撃です。
PKCEに関しては、下記2つの記事がわかりやすかったです。
OAuth2.0拡張仕様のPKCE実装紹介 〜 Yahoo! ID連携に導入しました - Yahoo! JAPAN Tech Blog
LINEログインをPKCE対応する | LINE Developers
今回は、PKCEに必要なcode_verifierとcode_challengeをPHPを使って実装します。
code_verifierの生成
code_verifierの仕様は、RFC7636 に準拠して作成します。
NOTE: The code verifier SHOULD have enough entropy to make it
impractical to guess the value. It is RECOMMENDED that the output of
a suitable random number generator be used to create a 32-octet
sequence. The octet sequence is then base64url-encoded to produce a
43-octet URL safe string to use as the code verifier.
- 推測困難な乱数値を使うために、適当な乱数生成器を用いる。
- 十分なエントロピー(256ビット以上)を持たせるために32オクテットの値を利用。
- 構成文字: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
- 文字数:43文字〜128文字
上記仕様を満たすように実装をしていきます。
乱数ジェネレーターを使用して32オクテットの値を生成
十分なエントロピーを持っている値を生成します。
適切な乱数ジェネレーターを使用して32オクテットの値を生成します。(1オクテット = 8ビット)
PHPの openssl_random_pseudo_bytes();
は、疑似ランダムなバイト文字列を生成します。
openssl_random_pseudo_bytes(32)
=> string(32) "Gs�<NKD�3[mm�X�t��=7Hm�"!�k�H"
ここで、「1バイトが必ずしも8ビットとは限らないのでは?」と思った方もいるかもしれません。
openssl_random_pseudo_bytes();
はC言語で実装されています。
C言語では、<limits.h>
ヘッダで定義されるCHAR_BITマクロでchar型のビット数を定義しています。
CHAR_BITマクロは少なくとも1バイトが8ビット以上に定義されることが保証されているので、1バイト文字列が8ビット以上であると考えて良さそうです。
Base64でエンコードを行う。
base64は、A-Z(26)、a-z(26)、0-9(10)、+, /(2)、パティングを表す=を使って表現されます。
base64_encode(openssl_random_pseudo_bytes(32)
=> string(44) "euG+/gkfxtLrZakr5GqZojfSBB4t9lLKogDNW4/gauM="
使用可能な構成文字に対応をする。
URL safeにする必要があるので、+, /を置き換えて、=を削除します。
str_replace('=', '', strtr(base64_encode(openssl_random_pseudo_bytes(32)), '+/', '-_'));
=> string(43) "jIhzUc6JtFe1OQSg4v6-VbJuqhVZBalkq0FEEzwYoxs"
code_verifierを生成する関数
code_verifierを生成する関数の完成です。
private function generateCodeVerifier(int $byteLength = 32){
return str_replace('=', '', strtr(base64_encode(openssl_random_pseudo_bytes($byte_length)), '+/', '-_'));
}
読みやすさを意識して、説明変数を使うと下記のようになります。
private function generateCodeVerifier(int $byteLength = 32)
{
$randomBytesString = openssl_random_pseudo_bytes($byteLength);
$encodedRandomString = base64_encode($randomBytesString);
$urlSafeEncoding = [
'=' => '',
'+' => '-',
'/' => '_',
];
return strtr($encodedRandomString, $urlSafeEncoding);
}
code_challengeの生成
code_verifierをSHA256で暗号化し、Base64URL形式にエンコードすることでcode_challengeを生成できます。
また、「=」の削除、+を-に、/を_に置換します。
code_challengeを生成する関数
private function generateCodeChallenge($code_verifier)
{
$hash = hash('sha256', $code_verifier, true);
return str_replace('=', '', strtr(base64_encode($hash), '+/', '-_'));
}
まとめ
PKCE実装のPHPでの実装例があまりなかったので、作成してみました。
より良い書き方や改善点があればコメントいただけると幸いです。
弊社のアドベントカレンダー他記事もよろしくお願いしますー!
https://qiita.com/advent-calendar/2021/excite-hd