いまさらTOTP

# はじめに（Introduction）

最近、某Web口座がやらかしたので、認証を強化せよ！という風潮が高まってきました。
要するに、IDとパスワード方式に加えて認証を追加する必要が出てきました。

最近では、スマホを利用した「Google Authenticator」や「Microsoft Authenticator」を利用して認証を行うサイトなんかも出てきています。
とりあえずそこで使われているTOTP（Time-Based One-Time Password）を見てみます。

# 多要素認証（Multi-factor authentication）

認証に用いる要素には大きく４つあるそうです。（いままで３つでしたが、最近４つめが出てきたらしい）

- __知識要素__（Knowledge factors）<br>
知っているもの、要するにパスワードです。
- __所持要素__（Possession factors）<br>
持っているもの
 - __切断されたトークン__（Disconnected tokens）<br>対象に接続せずにトークンを発行します。
 - __接続されたトークン__（Connected tokens）<br>カードリーダー、USBキーなどで、対象に接続する事で認証します。
 - __ソフトウェアトークン__（Software tokens）<br>ソフトウェアでトークンを発行します。スマホ上で追加できます。
- __固有の要素__（Inherent factors）<br>
生体認証と呼ばれるものです。
- __ロケーションベースの要素__（Location based factors）<br>近くにいるときは、PINの入力で認証、離れると他の要素も必要となります。

この４つ（３つ？）から、複数（２つ以上）の要素を利用することでセキュリティを高めること出来きます。
ちなみに、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）

```python
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）

使い方としては、ユーザー側はスマホで出ている６桁の数値をサーバー側に送ります。
サーバー側は、ユーザーの秘密と現在時刻（Unix時間）で６桁の数値を作成し、
送られてきた６桁の数値と同じであるかを判定します。

サーバー側で秘密を持つ必要がある、ハッシュ関数がSHA1であるなど、
いくつか懸念点がありますが、要素としては簡単に追加できそうです。


# 参照（References）

- Multi-factor authentication - Wikipedia<br>https://en.wikipedia.org/wiki/Multi-factor_authentication

- RFC 6238 - TOTP: Time-Based One-Time Password Algorithm<br>https://tools.ietf.org/html/rfc6238

- RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm<br>https://tools.ietf.org/html/rfc4226

- Key Uri Format · google/google-authenticator Wiki<br>https://github.com/google/google-authenticator/wiki/Key-Uri-Format