はじめに(Introduction)
最近、某Web口座がやらかしたので、認証を強化せよ!という風潮が高まってきました。
要するに、IDとパスワード方式に加えて認証を追加する必要が出てきました。
最近では、スマホを利用した「Google Authenticator」や「Microsoft Authenticator」を利用して認証を行うサイトなんかも出てきています。
とりあえずそこで使われているTOTP(Time-Based One-Time Password)を見てみます。
多要素認証(Multi-factor authentication)
認証に用いる要素には大きく4つあるそうです。(いままで3つでしたが、最近4つめが出てきたらしい)
-
知識要素(Knowledge factors)
知っているもの、要するにパスワードです。 -
所持要素(Possession factors)
持っているもの -
切断されたトークン(Disconnected tokens)
対象に接続せずにトークンを発行します。 -
接続されたトークン(Connected tokens)
カードリーダー、USBキーなどで、対象に接続する事で認証します。 -
ソフトウェアトークン(Software tokens)
ソフトウェアでトークンを発行します。スマホ上で追加できます。 -
固有の要素(Inherent factors)
生体認証と呼ばれるものです。 -
ロケーションベースの要素(Location based factors)
近くにいるときは、PINの入力で認証、離れると他の要素も必要となります。
この4つ(3つ?)から、複数(2つ以上)の要素を利用することでセキュリティを高めること出来きます。
ちなみに、TOTPは、所持要素のソフトウェアトークンとなります。
※:__二段階認証__とは異なります。
OTP(One-Time Password)
RFC6238とRFC4226を見ます。
HTOP(HMAC-Based One-Time Password)のパラメータの一つに、時間を設定する事でTOTP(Time-Based One-Time Password)を実現しているようです。
HOTP(HMAC-Based One-Time Password)
C
は、8バイトのカウンターです。クライアントとサーバーで同期をとる必要があります。
K
は、クライアントとサーバー間で共有された秘密です。
Digit
は、桁数です。
Step 1:
まず、K
とC
からHMAC-SHA1を求めます。
HS = HMAC-SHA-1(K,C)
HS
は20バイトのハッシュ値です。
Step 2:
HS
から31ビット取得します。
まず、HS
の最後から4ビットから数値を求めます。
この値をOffset
と呼び、0~15までの数値が得られます。
HS
のOffset
から4バイト取得します。
この4バイトから、後方31ビットを取得します。
これを、Sbits
とします。
Step 3:
Sbits
を数値にします。
数値の下Digit
桁が、ワンタイムパスワードとなります。
TOTP(Time-Based One-Time Password)
HOTPのC
を時間としたものです。
X
は、タイムステップを秒単位で表したものです。(デフォルト値は30です。)
T0
は、タイムステップのカウントを開始するUnix時間です。(デフォルト値は0です。)
時間を計算します。
T = (Unix時間 - T0) / X
(小数点以下切り捨て)
T
を8バイトのデータにして、HOTPを行います。
実装例(Sample)
import hashlib
import hmac
def hotp(K, C, digestmod=hashlib.sha1, Digit=6):
hmacsha1 = hmac.new(K, C.to_bytes(8, 'big'), digestmod)
HS = hmacsha1.digest()
Offset = HS[len(HS)-1] & 0x0f
Sbits = bytes([HS[Offset] & 0x7f]) + HS[Offset+1:Offset+4]
Snum = int.from_bytes(Sbits, 'big')
D = Snum % 10**Digit
return ("{{:0{:d}d}}".format(Digit)).format(D)
def totp(K, C, digestmod=hashlib.sha1, Digit=6):
T0 = 0
X = 30
T = int((C - T0) / X)
return hotp(K, T, digestmod, Digit)
Key Uri Format
「Google Authenticator」や「Microsoft Authenticator」に読み込ませるQRコードを作ります。
otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
とりあえず、アカウント名(accountname)と発行者(issuer)と共有の秘密をBase32エンコードしたものがあればできそうです。
otpauth://totp/<発行者>:<アカウント名>?secret=<秘密のBase32エンコード>&issuer=<発行者>
一応、パラメータにアルゴリズム(algorithm)とか桁数(digits)など設定できるらしいんですが、こんな記述が・・・使えないらしい。
Currently, the algorithm parameter is ignored by the Google Authenticator implementations.
現在、アルゴリズム パラメータは Google Authenticator の実装では無視されています。
参考例
とりあえず、Gistにサンプルコードを作成しました。
https://gist.github.com/tnakagawa/829c1cf098b54240116dc103eaa72f4c
まとめ(Summary)
使い方としては、ユーザー側はスマホで出ている6桁の数値をサーバー側に送ります。
サーバー側は、ユーザーの秘密と現在時刻(Unix時間)で6桁の数値を作成し、
送られてきた6桁の数値と同じであるかを判定します。
サーバー側で秘密を持つ必要がある、ハッシュ関数がSHA1であるなど、
いくつか懸念点がありますが、要素としては簡単に追加できそうです。
参照(References)
-
Multi-factor authentication - Wikipedia
https://en.wikipedia.org/wiki/Multi-factor_authentication -
RFC 6238 - TOTP: Time-Based One-Time Password Algorithm
https://tools.ietf.org/html/rfc6238 -
RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm
https://tools.ietf.org/html/rfc4226 -
Key Uri Format · google/google-authenticator Wiki
https://github.com/google/google-authenticator/wiki/Key-Uri-Format