QRコードでシークレットを渡してワンタイムパスワード認証する(Google AuthenticatorとかAuthyみたいな)やつがやりたい


自前のシステムでも多要素認証やりたい

画面に表示されたQRコードをスマホアプリにぴっと読み込み。
すると、30秒ごとにワンタイムパスワードが生成されるようになります。
あれを自前のシステムでもやりたいです。

QRコードの内容

QRコードの内容は otpauth:// で始まるURIになっています。
まずはこれを作れればスマホアプリで読み込めますね。

otpauth URI スキームの構造

こういうものの仕様は、RFCにあるかと思えば見つかりません。stackexchangeに質問がありました。回答によると、otpauth URIはBlackberryのソースコードに由来するようです。

URIの一般構造(引用元 RFC 3986)を眺めながらソースを追ってみます。

   foo://example.com:8042/over/there?name=ferret#nose
   \_/   \______________/\_________/ \_________/ \__/
    |           |            |            |        |
 scheme     authority       path        query   fragment

ソースによると otpauthtotp の2つのスキームと、普通のURLが使えます。

それぞれのスキームごとにURIの使い道をまとめてみます。

scheme authority path query fragment
otpauth totp ユーザ名 シークレット(secretキー) -
otpauth hotp ユーザ名 カウンター(counterキー)、シークレット(secretキー) -
totp ユーザ名 - - シークレット
上記以外 - - ユーザ名(userキー) シークレット

シークレットはBase32エンコードされた文字列です。

サンプルURI

こんなURIを作ればよさそうです。

otpauth://totp/[email protected]?secret=NFWG65TFPFXXK===
otpauth://hotp/[email protected]?counter=100&secret=NFWG65TFPFXXK===
totp://[email protected]#NFWG65TFPFXXK===
http://example.com/[email protected]#NFWG65TFPFXXK===

QRコードにしてみました。

(QRコードは https://www.cman.jp/QRcode/qr_make/ で生成しました。感謝!)

読み込めますか?
Authyアプリでは、otpauthスキーマのtotpしか読み込めませんでした。

ここまでくれば

あとは、以下のことをすれば実装できそうです。

  • QRコード発行時
    • 暗号論的にランダムなシークレットを生成
    • シークレットを保管
  • ログイン時
    • Unix timeとシークレットからワンタイムパスワードを生成
    • ユーザが入力したワンタイムパスワードと、生成したワンタイムパスワードを比較して認証

気になること

シークレットを生のまま保管しておくのって、なんかパスワードを生のまま保管するみたいで気になります。

追記

さらに探したら、otpauth URIのフォーマットありました。

こんなフォーマットです。

otpauth://TYPE/LABEL?PARAMETERS

PARAMETERSには以下の項目があります。

パラメータ 必須? 既定値 説明
secret 必須 - シークレット
issuer 強く推奨 - シークレットの発行者
algorithm オプション SHA1 SHA1、SHA256、SHA512のいずれか
digits オプション 6 OTPの桁数
counter hotpの時必須 - カウンター
period totpのときオプション 30 totpの生成間隔(秒)

全部入りだとこんなURIになります。

otpauth://totp/[email protected]?secret=NFWG65TFPFXXK===&issuer=example&algorithm=sha256&digits=8&period=15