PHP + Google Authenticator(TOTP)2段階認証の実装方法

プログラム
スポンサーリンク

2019年7月、あるスマートフォン決済サービスが不正利用され多くの利用者の方が被害に遭われました。その決済サービスには、2段階認証やそれに準ずるセキュリティ対策を怠ったとして大きな批判が集まりました。高い安全性が求められるサービスでは、パスワード認証だけでは安全を確保することが難しくなっています。そこで今回は、PHP と Google Authenticator を使った2段階認証の実装方法をまとめてみました。

Google Authenticator(TOTP)2段階認証の仕組み

Google Authenticator は、RFC6238 Time-Based One-Time Password Algorithm(よく「TOTP」と略されます)に基づいて時間ベースのワンタイムパスワードを生成します。クライアント側で利用する TOTP生成アプリは Google Authenticator が有名ですが、TOTP に対応している Microsoft Authenticator なども利用できます。

TOTP の仕組みは実にシンプルで、サーバーとクライアントで同じ秘密鍵を共有し、その秘密鍵と時間を元に30秒間有効なワンタイムパスワードを生成し、一致していることで認証します。

2段階認証の方法は色々ありますが、TOTP は、クライアント側ではスマートフォンに TOTP生成アプリをダウンロードするだけで使えますし、仕組みがシンプルなのでサーバー側への実装も容易なのが特徴です。

TOTP認証の実装方法

サーバー側で TOTP認証を行うには、RFC6238 に基づいて、次の2点を実装します。

  • 秘密鍵を生成し表示する機能(よくQRコードで表示させます)
  • クライアントが生成したTOTPを検証する機能

また、この記事では割愛しますが、TOTPの総当たり攻撃対策(例:30秒ごとに1回のログインに制限、30秒ごとに3回までのログイン試行に制限など)も必要です。

PHPGangsta/GoogleAuthenticator

PHPでのTOTP認証の実装は、PHPGangsta/GoogleAuthenticator ライブラリを利用するのが便利です。TOTP認証に必要な実装がわずか250行ほどの1つのクラスにまとまっていて、コードも読みやすいので RFC6238 の技術仕様とあわせて読んでおくことをオススメします。

GoogleAuthenticator/PHPGangsta/GoogleAuthenticator.php

秘密鍵の生成と表示

まずはじめに、秘密鍵の生成と表示です。実際には秘密鍵はサーバー上でユーザーごとに保存しておきます。

<?php
require_once 'PHPGangsta/GoogleAuthenticator.php';

$ga = new PHPGangsta_GoogleAuthenticator();

// 秘密鍵の生成と表示
$secret = $ga->createSecret();
echo "秘密鍵:{$secret}\n\n";

実行すると次のように表示されます。

秘密鍵:EQXAPJ6ISAK5VM7U

QRコードの表示

秘密鍵はクライアント側で手入力もできますが、ユーザーの手間を軽減するために生成した秘密鍵をよくQRコードで表示します。

<?php
require_once 'PHPGangsta/GoogleAuthenticator.php';

$ga = new PHPGangsta_GoogleAuthenticator();

// 秘密鍵の生成
$secret = $ga->createSecret();

// サービス名
$title = 'TEST';

// ユーザー名
$name = 'foo';

// QRコードURLの生成と表示
$qrCodeUrl = $ga->getQRCodeGoogleUrl($name, $secret, $title);
echo "秘密鍵のQRコードURL:{$qrCodeUrl}\n\n";

実行すると次のように表示されます。

秘密鍵のQRコードURL:https://api.qrserver.com/v1/create-qr-code/?data=otpauth%3A%2F%2Ftotp%2Ffoo%3Fsecret%3DEQXAPJ6ISAK5VM7U%26issuer%3DTEST&size=200x200&ecc=M

$title と $name に指定した文字列は GoogleAuthenticator で QRコードを読み込むと次のように表示されます。TOTPに対応しているWebサービスでは、$title にサービス名、$name にユーザー名を指定することが多いようです。

ここで1つ問題となるのは、秘匿としなければならない秘密鍵をURLに含めなければならないことです。これは「URLに秘匿情報を含めない」の原則から外れてしまいます。そのため、実際には秘密鍵のQRコードは自分のサーバー上で生成し表示するのがよいでしょう。

関連記事:5分で出来る!PHPでQRコードを生成する方法

TOTPの検証

ユーザーが GoogleAuthenticator などで生成した TOTPを検証し、OKならそのユーザーを認証します。

$discrepancy で、サーバーとクライアントで許容する時間のずれを設定できますので、時間のずれによるTOTPの検証失敗が多い場合はこの値を大きくします。

<?php
require_once 'PHPGangsta/GoogleAuthenticator.php';

$ga = new PHPGangsta_GoogleAuthenticator();

// ユーザーごとに保存してある秘密鍵
$secret = 'EQXAPJ6ISAK5VM7U';

// ユーザーが生成したTOTP
$oneCode = filter_input(INPUT_POST, 'oneCode');

// サーバーとクライアントで許容する時間のずれ
// $discrepancy × 30秒 許容します(デフォルト値は1です)
$discrepancy = 2;

// TOTPの検証
$checkResult = $ga->verifyCode($secret, $oneCode, $discrepancy);
if ($checkResult) {
    echo 'OK';
} else {
    echo 'FAILED';
}

実際のWebサービスでは、ユーザーが TOTP2段階認証を設定する時に、GoogleAuthenticator などに登録した秘密鍵が正しいことを確認するため、一度 TOTPの検証を行なってから、TOTP2段階認証を有効にする実装が多いです。

おわりに

PHPGangsta/GoogleAuthenticator のおかげもあり、GoogleAuthenticator などを利用した TOTP2段階認証の実装はかなり簡単です。少し大変なのは、TOTPの総当たり攻撃対策や認証ログ出力の実装あたりだと思いますが、難しいものではありません。

2段階認証を実装した Webサービスが増えてくれるといいですね。

コメント

タイトルとURLをコピーしました