- RFCはこれ。Javaでの実装例もアリ: RFC 6238 - TOTP: Time-Based One-Time Password Algorithm
- RFCも十分短いけど、Wikipediaのほうはさらに簡潔: Time-based One-time Password Algorithm - Wikipedia, the free encyclopedia
- 日本語での参照情報: Googleの2段階認証で使われているOTPの仕様が気になった - r-weblife
RFCに書いてある実装使うとかでサクッとワンタイムパスワードを生成できるので、もっと普及するといいですね。その場合はシークレットの扱いは適切にお願いします。
認証の概要
サーバーと共有しているシークレットキーと現在時刻からハッシュを生成してそれが合ってるかをみる、というのが大筋の流れ。
ワンタイムパスワードの生成
Wikipediaに書いてある通りだけど、少し補足説明。
TimeCounter = (unixtime(now) - unixtime(T0)) / TimeStep # T0はエポック開始時点なので実質0, TimeStepはサーバーと共有する。30秒が推奨されてる。
HOTP(K,C) = Truncate(HMAC(K,C)) & 0x7FFFFFFF # HMACはRFC2104で、TruncateのマナーはRFC4226でそれぞれ定義されている
TOTP = HOTP(SecretKey, TimeCounter) # SecretKeyは事前にサーバーから提供されたもの
TOTP-Value = TOTP mod 10^ditits_number # 剰余オペレーターを通して、指定桁数になるよう整形
- TruncateについてはHOTP1で定義されている
- 有効期間は30秒を推奨とRFCに書いてある
- ハッシュ関数の推奨はRFCにはない。実質、SHA-1がデファクト。RFC上では、HMAC-SHA-1ではなく、HMAC-SHA-256 or HMAC-SHA-512が使われることも、実装によってはありうるとある。けど、TOTPのベースになっているHOTPのRFCではSHA-1が使われることが想定されているので、それに則っているのかな
- 当然、Secretキーをサーバー側と共有しないといけなくて、シークレットの受け渡しは以下の2つがよく使われる
- QRコード
- ユーザーに表示して手入力してもらう
実際の運用でのシークレットの渡し方
例えば以下はFacebookで2段階認証を有効にした場合に表示されるQRコードをデコードしたもの。
otpauth://totp/Facebook:johndoe?secret=16DIGITS_SECRET&digits=6&issuer=Facebook
Qiitaの場合は以下。
otpauth://totp/johndoe?secret=16DIGITS_SECRET&issuer=Qiita
Qiitaのほうはdigits
も省略してるけど、6桁がよく使われるからだろうね。
アプリケーションでのワンタイムパスワードの生成
という感じで実装側はとくに難しいところはないので、アプリケーションを差別化するのは難しそう。
IIJ SmartkeyやAuthyや、そもそもGoogle Authenticatorも汎用TOTPコードジェネレーターとして動くので、どれでも好きなの使えばよい、が、そのTOTPコードジェネレーターが秘密鍵を適切に管理してるかは気にしたほうがいい。平文で適切なパーミッションなしで保存などされていたら台無し。Authyだとデバイスが代わっても大丈夫!復元可能!と言ってるけど、それってつまりシークレットを外部のサーバーに保存してるということなので、それが許容されるひとは使えばいいと思う。
セキュリティトークンとの比較
比較 | TOTP/HOTP | セキュリティトークン |
---|---|---|
開始するコスト | 安い | 高い |
耐タンパー性 | 低い(rootedなデバイスで第三者のアプリケーションからSecretKeyを覗き見されたらどうする?) | 高い(独立したセキュリティトークンはたいてい耐タンパー性が高くなるように設計されている、はず) |