LoginSignup
3
1

More than 1 year has passed since last update.

多要素認証 TOTP のデモと Base32 エンコードについて

Posted at

TOTP サンプルアプリ

2要素認証の動作を確認したい場面があり、 npm の totp-generator をつかって、TOTP の簡単なサンプルアプリを作りました。
キーを入力すると、前後数分間の数字を表示します。

https://7z1gxd.csb.app/
:octocat: https://github.com/koseki/totp-demo/
image.png

TOTP のキーの受け渡し方法

いま普及している2要素認証のクライアントアプリは、TOTP のキーを Base32 でエンコードします。

オープンソース版 Google Authenticator の GitHub リポジトリに、QR コードを生成するための Key URI Format のドキュメントがあります。

REQUIRED: The secret parameter is an arbitrary key value encoded in Base32 according to RFC 3548. The padding specified in RFC 3548 section 2.2 is not required and should be omitted.

任意の長さのキーを Base32 (RFC3548) でエンコードします。= によるパディングは省略します。

otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

といった感じの URI で QR コードを生成します。

Base32 の仕組み

Base32 は入力を 5 バイト (= 40 ビット) ずつに分け、その 40 ビットを 5 ビットずつ 8 文字にエンコードします。

最後に 40 ビットに満たないデータが残った場合は、末尾1文字の余分なビットは 0 で埋めて、残りの文字を = でパディングします。

下図は、末尾のデータが 1 〜 5 バイトの場合のパディングの数と、末尾の文字になにがあり得るかを示しています。
base32.png

例えば最後に 2 バイト残ったら、16ビットを表現するには 4文字 20 ビット必要になります。4文字目は先頭の 1 ビットだけが有効で残りの 4ビットは 0 にします。残りの 4 文字は = でパディングします。

  • 末尾データが 1 バイトの場合 …… 2 文字必要なので、= は 6 個。最終文字の末尾 2 ビットが 0
  • 末尾データが 2 バイトの場合 …… 4 文字必要なので、= は 4 個。最終文字の末尾 4 ビットが 0
  • 末尾データが 3 バイトの場合 …… 5 文字必要なので、= は 3 個。最終文字の末尾 1 ビットが 0
  • 末尾データが 4 バイトの場合 …… 7 文字必要なので、= は 1 個。最終文字の末尾 3 ビットが 0

となり、最終文字が表す値は、それぞれ、4, 16, 8, 2 の倍数になります。

正規表現で書くと、以下のようになります。(https://stackoverflow.com/a/27362880 を改変)

^(?:[A-Z2-7]{8})*(?:[A-Z2-7][AEIMQUY4]={6}|[A-Z2-7]{3}[AQ]={4}|[A-Z2-7]{4}[ACEGIKMOQSUWY246]={3}|[A-Z2-7]{6}[AIQY]=)?$

ちなみに、Base64 は全く同じことを 24 ビット単位、6 ビット 1 文字で行います。3 バイト(8 * 3)を 4 文字(6 * 4)にエンコードします。末尾が 1 バイトなら = は 2 個、2バイトなら = 1 個となります。
base64.png

2要素認証クライアントの挙動の違いについて

いくつかの TOTP クライアントで挙動を確認しました。

  • Authy
  • iPhone の Google Authenticator
  • MS Authenticator
  • iPhone 標準のパスワードアプリ

パディングの有無

iPhone の Google Authenticator は、キーが = でパディングされていると、エラーで QR コードが読めません。= を消す必要があります。他はパディングがあっても読み込めていました。

末尾の不正文字

キーの末尾の文字が正しくない場合、Google Authenticator はエラーにします。
例えば、キーが4文字だった場合、上で説明したように末尾の文字は AQ のどちらかです。AQ でなければ、Google Authenticator は不正なキーとして QR コードを読み取りません。

Authy や MS Authenticator、npm の totp-generator は、余分なビットは切り捨てます。AAARAAAQ と同じキーになるようでした。

iPhone 標準のパスワードアプリは、余分なビットがあったら残りを 0 で埋めて桁を増やす動作になっていました。AAARAAARA と同じキーになります。

以下は Python の出力ですが (Python の Base32 は切り捨てる動作をするようです)、

>>> base64.b32decode('AAAQ====')
b'\x00\x01'
>>> base64.b32decode('AAAR====')
b'\x00\x01'
>>> base64.b32decode('AAARA===')
b'\x00\x01\x10'

Authy は、最初の2つが同じキーになります。iOS 標準パスワードアプリは、下の2つが同じキーになります。

不正なキーを生成する意味がないので実際には問題ないのでしょうが、動作に微妙な違いがあって面白かったので、まとめてみました。TOTP の QR コードを生成するときは、キーの長さは 40 bit の倍数にするのが無難そうです。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1