こんにちは、プラットフォーム開発部で認証認可基盤の開発を担当しているabcangです。
先日pixivのログイン画面で2段階認証が利用できるようになりました。現時点ではTOTP(Time-based One-Time Password)認証アプリとバックアップコードの2種類が2要素目の認証手段として利用できます。バックアップコードは、メインの2要素目の認証手段が使えなくなった際に代わりの認証手段として使用できるコードで、2段階認証を有効化したときに生成しています。
バックアップコードは長ければ長いほど安全ですが、長いといざ使うときに入力が大変ですし入力ミスもしやすくなります。しかし、逆にバックアップコードを短くしすぎるとセキュリティ的に問題になるため、利便性とセキュリティのバランスを取る必要があります。
今回はこのバックアップコードの仕様をどのように決めたかをご紹介します。
NIST SP 800-63Bを確認する
NIST SP 800-63は、アメリカ国立標準技術研究所(NIST)が発行している電子認証のガイドラインです。これに含まれるNIST SP 800-63Bには認証プロセスに関する技術要件や推奨事項が記載されており、認証機能を実装する際に非常に役に立ちます。また、OpenID Foundation Japanによって日本語訳版も公開されています。
バックアップコードはこのガイドラインのルックアップシークレットに該当します。認証には知識要素(Something you know)、所有要素(Something you have)、生体要素(Something you are)の3種類ありますが、ルックアップシークレットに該当するバックアップコードは所有要素に分類され、知識要素であるパスワードとは要素が異なっています。そのため、パスワード+バックアップコードを使う場合でも2要素を使用した認証ということになります。
pixivを含む多くのサービスではバックアップコードは1回しか使えないようになっていますが、このガイドラインにはSHALL(必須要件)として記載されています。そして、ルックアップシークレットのエントロピーに応じた保存方法やレート制限などのセキュリティ要件についても細かく記載されています。
エントロピーに応じたセキュリティ要件
NIST SP 800-63Bからルックアップシークレットのエントロピーに関する記載を抜粋したものが以下になります。
- ルックアップシークレットは最低20ビットのエントロピーを持つものとする(SHALL)
- 112ビット以上のエントロピーを持ったルックアップシークレットは,Section 5.1.1.2に記載されているようにApprove済み一方向関数でハッシュ化されるものとする(SHALL)
- 112ビット未満のエントロピーを持ったルックアップシークレットはSection 5.1.1.2に記載されているように,ソルトを追加したうえで適切な一方向の鍵導出関数を用いてハッシュされるものとする(SHALL)
- 64ビット未満のエントロピーを持つルックアップシークレットに対して,Verifierは,Section 5.2.2に記載されているように,SubscriberのアカウントにおけるAuthentication失敗回数を効果的に制限するレート制限の仕組みを実装するものとする(SHALL)
64ビットを境にレート制限が必須かどうかが、112ビットを境にハッシュ化するときにソルトが必須かどうかが変わってきます。
ビット数だと分かりづらいので文字数で換算してみます。1文字あたりのエントロピーのビット数は log_2(文字の種類数)
で算出でき、1文字あたりのビット数で割れば必要な文字数がわかります。他社事例で多かった「数字のみ10種類」「小文字英数字36種類」「小文字英数字32種類(小文字英数字36種類から紛らわしい4文字を除外)」の3パターンで換算したものが以下になります。
数字のみ10種類 | 小文字英数字32種類 | 小文字英数字36種類 | |
---|---|---|---|
1文字あたりのビット数 | 3.3ビット | 5ビット | 5.1ビット |
20ビットに必要な文字数 | 6.02文字(7文字) | 4文字 | 3.8文字(4文字) |
64ビットに必要な文字数 | 19.2文字(20文字) | 12.8文字(13文字) | 12.3文字(13文字) |
112ビットに必要な文字数 | 33.7文字(34文字) | 22.4文字(23文字) | 21.6文字(22文字) |
括弧書きした文字数は小数点以下を切り上げた場合の数字です。基準のビット数を下回らないようにするには、小数点以下を切り上げて考える必要があります。
数字のみだと種類数が少なく、エントロピーを高くするには多くの文字数が必要になります。そのため、文字数を少なくしたい場合には数字のみにするのは向いていないです。
小文字英数字については、112ビット以外は32種類と36種類の文字数の差はなさそうです。そして、レート制限必須化のラインである64ビットのエントロピーには、小文字英数字だと13文字以上が必要なことがわかります。バックアップコードは何文字かごとにハイフンやスペースで区切って見やすくすることが多く、それも考慮する場合は適度な数で割り切れる15文字や16文字にすることになりそうです。
大文字の英字も含めて文字種を増やせばバックアップコードの文字数を減らすことができそうですが、大文字を入力するにはShiftキーも押す必要があり、大文字と小文字が混在しているとなおさら入力しにくくなるというデメリットがあります。
検討した結果、以下のような理由から小文字英数字を12文字の長さで使用することに決めました。
- レート制限に関係なくエントロピーはなるべく高くしたい
- 小文字英数字で15文字や16文字の長さになると入力ミスをしやすくなりそうなので避けたい
小文字英数字の12文字だと64ビットを少し下回ってしまうためレート制限の実装が必須になります。よほどエントロピーが高くない限りはレート制限もしておくほうがより安全だろうと考えていたため、レート制限を実装することに抵抗感はありませんでした。また、112ビットに達していないためバックアップコードはソルト付きでハッシュ化して保存することになります。
紛らわしい文字を除外
数字と小文字英字の中には 1
(数字のいち)と l
(小文字のエル)のように紛らわしい文字の組み合わせがあります。バックアップコードの入力ミスを防ぐため、こういった文字を除外することにしました。
先程も登場しましたが、小文字英数字から4文字を除外した「小文字英数字32種類」を採用している他社事例がありました。英数字32文字と言うとBase32(RFC 4648)が一番有名だと思いますが、これは大文字を前提として数字の0、1、8、9を除外しています。また、英語版WikipediaにはBase32の亜種がいくつか載っており、手書きすると紛らわしくなる文字( v
u
r
、 2
z
)も考慮しているz-base-32や紛らわしい文字を同一の文字として扱うCrockford's Base32など様々なバリエーションがあることがわかりました。バックアップコードを厳密なBase32として扱いたいわけではなく、紛らわしい文字の扱いの参考にするために調べていました。
Base32やBase32の亜種では紛らわしい文字の組み合わせの片方だけを除外するケースが多く見られますが、ユーザーはどの文字が除外されているか知らないため、紛らわしい文字が含まれていた場合は迷いながら入力することになってしまいます。紛らわしい文字を同じ文字として扱えば入力に失敗することはありませんが、入力するときに迷ってしまうこと自体は解決できず、UXとしてはいまいちな気がしました。そう考えて、より見た目が似ている以下の4文字を除外することにしました。
0
(数字のれい)1
(数字のいち)o
(小文字のオー)l
(小文字のエル)
バックアップコードはダウンロードやスクリーンショットで保存してもらうことを想定していて、手書きで書き写すケースは稀だと考えたため、手書きのときに紛らわしくなりやすい他の文字はそのまま採用しています。
最終的な仕様
セキュリティ的な要件と利便性を考えて、pixivでは2段階認証のバックアップコードを「 0
1
o
l
)を除いた小文字英数字32種類」の12文字で構成することに決めました。32種類の文字を12文字の長さにする場合は log_2(32) * 12
で60ビットになるため、ソルト付きでハッシュ化して保存し、レート制限の仕組みを実装しています。
また、文字列が連続していると見にくいため、バックアップコードを表示する画面では4文字ごとにスペースで区切った xxxx xxxx xxxx
のようなフォーマットで表示しています。実際にバックアップコードを検証するときにはスペースを無視して処理しているため、スペースはエントロピーには影響を与えていません。
おわりに
セキュリティと利便性の両方を考慮してバックアップコードの仕様を決めることができました。今後もセキュリティを維持しながら利便性を向上できるように認証認可基盤の改善に取り組んで行きたいです。
ピクシブでは認証認可に興味のあるエンジニアを募集しています。