ソフトウェアトークン生成器による2段階認証(Two-factor authentication)の仕様と実装について。
Googleではこの認証機能を 2-Step Verification と呼んでいる。一般的には Two-factor authentication、あるいは Multi-factor authentication と呼ばれている様子。
和訳の定義はまちまちだが、
- パスワード認証後に、追加認証キーを求めるものを「2段階認証」
- パスワードと同時に追加認証キーを求めるものを「2要素認証」
と呼んでいる様子。一応使い分けられているらしい。
2段階認証の主な提供種別。
- SMS
- 電話
- ハードウェアトークン
- ソフトウェアトークン
サービス提供者とすれば、SMSは通知コストがかかり、また100%到達しないことを考えると、ソフトウェアトークンでの対応が望ましい。ただし利用者にリテラシーを求めるため難しい。
またソフトウェアトークン時は機種変更時の秘密鍵紛失対応コストがかかると思われる(後述)
iPhoneアプリなどソフトウェアトークンを用いた2段階認証の手順を示す。
参考:http://www.ajaxtower.jp/gmail/2step-verify/index5.html
- 当該サイトにログオン
- 2段階認証設定画面からQRコードを取得
- iPhoneのソフトウェアトークンを起動し、QRコードを読み込み
- 認証APに表示されるワンタイムパスワード(6桁の数字列)を当該サイトに入力
- 安全な方法でサーバ側とクライアント側で同じ秘密鍵を共有する
- 共有した秘密鍵と現在時刻を用いて、クライアントがワンタイムパスワードを生成する
- サーバ側は、同じ計算方法でパスワードを生成し、クライアントから渡された値と一致するかどうか確認する
秘密鍵と時刻を用いてワンタイムパスワードを生成する方法は、RFC4226 で仕様化されているため、セキュリティを担保する要素は、秘密鍵の授受方法と保存方法のみ。
- 秘密鍵文字列をQRコード化して画面表示し、授受する
- QRコードの読み取りにはスマートフォンなどのカメラ機能が必要なため、当該スマートフォン上ですべて処理できる
- 文字列で受け渡すとメールなどで授受されるため、漏洩する可能性が高くなる
- クライアントから秘密鍵を取り出せないようにすることで、当該スマートフォンのみでワンタイムパスワードを生成できるようにする
- エクスポートできない
- 復元時にデータを復元しない
- 当該スマートフォンを紛失する、あるいは機種変更などで秘密鍵が失われるとログオンできなくなる
- 対処方法としては、
- 機種変更前に当該サイトでデバイス変更処理を行う
- 当該サイトがSMSでの2段階認証にも対応しているようであれば、設定しておく
- バックアップコードを保持しておく
- 秘密鍵文字列、あるいはQRコードをカメラで撮っておく
- サービス提供者に個人認証することで解除してもらう
QRコードをデコードすると以下の文字列が設定されている
- otpauth://totp/github.com/asufana?issuer=GitHub&secret=hc5qkbe7lj7crunq
- otpauth://(パスワードの生成方式)/(ユーザを一意に特定するURL)?secret=(秘密鍵文字列)
#このように秘密鍵を公開しても、この鍵で2段階認証を設定していなければリスクはない(サーバが提示する秘密鍵は設定毎に更新されるため)
以下の計算式で生成できる。
- ユーザとサーバしか知らない秘密鍵 + カウンタ値 = ワンタイムパスワード
- クライアントとサーバとで共有する
- 一般的にはサーバが随時提供する秘密鍵を、クライアント側が保持する形で共有する
-
HOTP:回数ベース RFC4226
-
クライアントはパスワードを生成した回数を、サーバは認証処理を行った回数を保持し、その値を一致させる
-
回数がずれる場合を考慮し、カウンタを同期させる機能が必要
-
TOTP:時刻ベース RFC6238
-
一定時間毎に現在時刻を用いてパスワードを生成する
-
クライアントとサーバがそれぞれ正しい時刻を設定していなければならない
カウンタ値の生成と、ワンタイムパスワードの生成ロジックは仕様化されているため、ソフトウェアトークン器は汎用的に提供することができる。
一般的なサービス事業者は、6桁/30秒でカウンタ生成している。
時刻ベースTOTP仕様での実装
- Base32で20文字以上(推奨)の文字列を生成
- UNIX時間(エポック)をインターバル秒で割る
- インターバル秒は多くのサービスサイトで30秒で設定されている
- 要は30秒間同じタイムコードを生成する
- シークレットキーとタイムコードからハッシュ関数とビット演算を行いワンタイムパスワード文字列を生成する
参考:https://github.com/kamranzafar/libotp
OTP.generate("12345678", "" + System.currentTimeMillis(), 6, "totp")
/**
* @param key 秘密鍵
* @param time 現在時刻
* @param returnDigits ワンタイムパスワード文字数
* @param crypto ハッシュアルゴリズム
*/
public static String generateTOTP(String key, String time, String returnDigits, String crypto) {
//パスワード文字数
int codeDigits = Integer.decode(returnDigits).intValue();
String result = null;
//これよくわからない。。
// Using the counter
// First 8 bytes are for the movingFactor
// Compliant with base RFC 4226 (HOTP)
while (time.length() < 16)
time = "0" + time;
//バイト列に変換
byte[] msg = hexStr2Bytes(time);
byte[] k = hexStr2Bytes(key);
//HMACダイジェストの取得
byte[] hash = hmac_sha(crypto, k, msg);
//最後のバイトをOxfでマスク
int offset = hash[hash.length - 1] & 0xf;
//オフセットしてビット演算
int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
//10のパスワード桁数分の乗数で割り、余りを得る
int otp = binary % ((int) Math.pow(10, codeDigits));
//余りがパスワードになる(桁不足していたら0で埋める)
result = Integer.toString(otp);
while (result.length() < codeDigits) {
result = "0" + result;
}
return result;
}
その他の言語実装例:Perl実装 Python実装 Ruby実装
2段階認証を適切に設定していたとしても、サービス提供会社の運用が甘ければどうしようもない。
- 5万ドルの価値がある Twitter アカウント「@N」は、どのようにして奪われたのか?
- 50,000 ドルの価値がある Twitter アカウントが盗まれたその経緯
- rebuildfm 37: N Factor Auth(Naoki Hiroshima)
ソフトウェアトークンであれば、USBキーやFeliCaカードなど物理デバイスなしでの認証強化が可能。鍵管理を一元化できればエンタープライズ環境でも活用できるのでは?
課題は鍵管理。各個人で管理させるには展開と紛失時対応コストが大きくなる。
- Bookmarkletを使ってQRコードを読み取り
- その秘密鍵をサーバサイドに配置したトークン生成器に渡す
- サーバサイドでワンタイムパスワードを生成
- 個人ポータルなどと組み合わせたらいい感じになりそう