2段階認証でよく使われる TOTP(時間ベースのワンタイムパスワード)は、利用をはじめる時に Google Authenticator などの認証アプリに、サーバーで生成した秘密鍵を登録します。この登録する秘密鍵はQRコードで表示することが多いのですが、その際に秘密鍵などを otpauth URI という形式にしてQRコードに埋め込む必要があります。そこで今回は、PHP で otpauth URI(TOTP)を作成する方法をまとめてみました。
otpauth URI(TOTP)のフォーマット
TOTP の otpauth URI フォーマットは次の通りです。「ラベル」と呼ばれる <発行者名>:<アカウント名> と、secret 以降の各種パラメータで構成されます。
参考資料:Key Uri Format · google/google-authenticator Wiki · GitHub
ラベル <発行者名>:<アカウント名>
<発行者名>:<アカウント名> の部分は「ラベル」と呼ばれ、秘密鍵と関連するアカウントを識別するために使われます。
ここで指定した発行者名(サービス名やプロバイダー名を指定します)とアカウント名が認証アプリに表示されます。(アプリによっては表示されない場合もあります)日本語で指定した発行者名やアカウント名が表示できる認証アプリもありますが、QRコードで表示したときに otpauth URI として認識しない認証アプリもありますので、英数文字のみで指定しておくのがよいでしょう。
発行者名とアカウント名は、「:」(コロン)で区切り、URLエンコードする必要があります。発行者名とアカウント名に「:」(コロン)を含めることはできないので注意です。
PHPでの実装は次のようになります。「:」(コロン)も含めてURLエンコードすることにも注意してください。
$issuer = 'Sample'; $accountname = 'foo@example.com'; $label = urlencode($issuer . ':' . $accountname);
パラメータ
secret(秘密鍵)
secret パラメータに秘密鍵を Base32(RFC3548 に準ずる、ただし セクション2.2 の「=」パディングは不要)でエンコードして指定します。
注意が必要なのは、PHPGangsta/GoogleAuthenticator など、TOTP認証ライブラリで生成した秘密鍵を指定する場合は、すでにライブラリ側で Base32 でエンコードしてくれているので、その場合は秘密鍵をそのまま指定します。
PHPでの実装は次のようになります。
// 秘密鍵 $secret = '7$#vy+bs$m'; // 秘密鍵を2進数(バイナリ)に変換 $binary_secret = ''; foreach(str_split($secret) as $chara) { $binary_secret .= sprintf('%08b', ord($chara)); } // 5ビットずつ配列に分割 $binary_secret_array = str_split($binary_secret, 5); // base32に変換 $base32alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $base32_secret = ''; foreach($binary_secret_array as $bin) { $bin_pad = str_pad($bin, 5, 0); // 5ビットに満たない場合は0パディング $base32_secret .= $base32alphabet[base_convert($bin_pad, 2, 10)]; } // base32でエンコードした秘密鍵 echo $base32_secret;
issuer(発行者名)
issuer パラメータには、ラベルにも指定した発行者名(サービス名やプロバイダー名)を、URLエンコードして指定します。issuer パラメータに指定する発行者名は、ラベルで指定した発行者名と同じでなければなりません。
PHPでの実装は次のようになります。
$issuer = 'Sample'; $issuer_urlencoded = urlencode($issuer);
algorithm(アルゴリズム)
algorithm パラメータには、ハッシュアルゴリズムを指定します。「SHA1」(デフォルト値)、「SHA256」、「SHA512」が指定できます。各認証アプリの対応状況は確認できませんでしたが、Google Authenticator では algorithm パラメータは無視されます。
digits(長さ)
digits パラメータには、認証アプリで生成するワンタイムパスワードの長さ(文字数)を指定します。「6」(デフォルト値)または「8」を指定できますが、認証アプリによっては「8」を指定すると、otpauth URI として読み取ってくれない認証アプリもありますので、「6」を指定しておくのが無難です。
period(期間)
digits パラメータには、認証アプリでワンタイムパスワードを更新するまでの期間(単位:秒)を指定します。「30」秒がデフォルト値です。ほとんどの認証アプリで digits パラメータは無視されますので、「30」を指定しておくのがよいでしょう。
パラメータへの対応状況
各認証アプリがどのパラメータに対応しているか調べてみました。
ラベルと秘密鍵以外のパラメータは無視されたり、デフォルト値以外を指定すると読み取りができない場合もありますので、それらのパラメータにはデフォルト値を指定しておけば問題ないと思います。
Google Authenticator | Microsoft Authenticator | Authy | Duo Mobile | |
---|---|---|---|---|
ラベル(発行者名) | ○ | ○ | ○ | 無視 |
ラベル(アカウント名) | ○ | ○ | ○ | ○ |
secret(秘密鍵) | ○ | ○ | ○ | ○ |
issuer(発行者名) | ○ | ○ | 無視 | 無視 |
algorithm(アルゴリズム) | 無視 | ? | ? | ? |
digits(長さ) | ○ | 無視 | ○ | 6以外は読取不可 |
period(期間) | 無視 | 無視 | 無視 | 無視 |
PHP での実装まとめ
PHP で otpauth URI(TOTP)を作成するもろもろの実装をまとめると次のようになります。base32でのエンコード方法は上の secret(秘密鍵)をご参照ください。
$issuer = 'Sample'; $accountname = 'foo@example.com'; $label = urlencode($issuer . ':' . $accountname); $base32_secret = // base32でエンコードした秘密鍵 $issuer_urlencoded = urlencode($issuer); // otpauth URI(TOTP) $uri = "otpauth://totp/{$label}?secret={$base32_secret}" . "&issuer={$issuer_urlencoded}&algorithm=SHA1&digits=6&period=30";
これらの実装をPHPクラスにまとめてみました。よければ使ってみてください。
https://github.com/sizaki30/TotpOtpauthUriMaker
おわりに
作成した otpauth URI(TOTP)を、QRコードで表示すれば Google Authenticator などの認証アプリで読み取りできるようになります。
コメント