当シリーズについて
この記事は、筆者が計画しているシリーズ「セルフホスティング奮闘記」の第3回です。このシリーズでは、筆者が新しくレンタルしたサーバーで3つのセルフホスティング(self-hosting)サービスをあれこれ構築した過程を記録しています。しかし、リストを見るとわかるように、このシリーズは 1/3 からいきなり 3/3 に飛んでいます。これは、2/3 にあたる Matrix サーバーがすでに天に召されてしまったからです(いくつかの「大人の事情」もありましたし、Dendrite サーバーが本当に使いにくかったというのもあります)。残るサービスは、分散型マイクロブログSNSの Misskey と、パスワードマネージャーの Bitwarden(実際にはサーバーサイドとして Vaultwarden を使用)です。
背景
筆者の過去のパスワード管理は、絶対にやってはいけない間違いをすべてやらかしていたと言えます。弱いパスワード、すべてのウェブサイトやサービスでのパスワードの使い回し、誕生日などに関連するパスワードの設定などです。そして最終的に、000webhost で平文パスワードの流出が発覚した際、ついに筆者は「神功大成」し、ネット上での丸裸状態を実現してしまいました。これが、数十個のサイトのパスワードを強制的に変更させられた初めての経験でした。
しかし、明らかにその時は十分に教訓を得ておらず、少し強めのパスワードに変更しただけで、相変わらずすべてのサイトで使い回していました。そのため、ある年(何年かは忘れましたが)、筆者は再び神功大成し、ネット上で二度目の丸裸状態に陥ることになりました。違いといえば、今回は変更しなければならないパスワードの数が100を超えていたことです。


2回目の情報漏洩の後、筆者は現状を根本から変える決心をしました。当然のことながら、サイトやサービスごとに異なるパスワードを設定する必要があり、できるだけランダムである(つまり、エントロピーが大きい)のがベストです。これほど多くのパスワードを一つ一つ覚えるのは現実的ではありません。以前、雑誌で読んだ方法に、一つのマスターパスワード+各サイトの名前を使い、頭の中で計算してパスワードを作るというものがありました(例えば、マスターパスワードが abcdefg で、サイトのドメインが google なら、agbocodgelfeg というパスワードを作るなど)。しかし、これでもソーシャルエンジニアリングの被害に遭うリスクがあり、その上、パスワードを入力するたびにこんな計算をするのは脳のリソースを使いすぎます。
もう一つの方法は、各サイトでランダムに生成された(十分なエントロピーを持つ)パスワードを使用し、パスワードマネージャーを使って記憶・自動入力させるというものです。以前からそうしなかったのは、適切なパスワードマネージャーのソリューションが見つからなかったからです。「大昔」のブラウザ内蔵のパスワードマネージャーはローカルに直接保存されるため、スマホや他の人のPCからはアクセスできませんでしたし、Yubikey などの純粋なハードウェアソリューションには紛失のリスクがありました。「新時代」のブラウザのパスワードマネージャーは Google アカウントや、さらには中国のサービスプロバイダーのアカウントを使って同期するため、非常に不安でした。LastPass や 1Password のようなエンドツーエンド暗号化を謳うソリューションであっても、クラウド事業者が管理するデータベースにデータが保存されますし、一部の企業では過去に情報漏洩の事故が発生しています。
最終的に、筆者はデータを完全に自己管理できる2つのパスワード管理ソリューション、KeePass と Bitwarden を見つけました。その主な原理は、まず十分に長い「マスターパスワード」を設定し、ユーザーはこのマスターパスワードだけを覚えておくというものです。このマスターパスワードを利用してパスワードボルトの他の部分全体を暗号化する(実際にはマスターパスワードから一連の共通鍵と公開鍵/秘密鍵のペアを派生させる)ことで、サーバー側が平文のパスワードを知ることなく、ユーザーが平文のパスワードを取り出してウェブサイトに入力できるようになります。
KeePass:純ローカルソリューションの試み
筆者が数年前に使用していたソリューションは KeePass 2 でした。KeePass は Windows-only のローカル向けオープンソースパスワードマネージャーであり、自動入力機能は基本的にありません(グローバルショートカットキーを提供し、キーボードエミュレーションでパスワードを入力する仕組みです)。同期は WebDAV を使用して行い、自動入力は一般的に非公式のブラウザ拡張機能である Kee を使用します。また、スマホ向けには Keepass2android などのアプリが提供されており、同期と自動入力を実現しています。

KeePass を大体 2~3 年使っていましたが、その中で筆者は以下のようないくつかの問題に直面しました:
- 定期同期がよく不安定になり、頻繁に手動同期が必要だった(外出先でスマホを見たら、PC上のあるパスワードが同期されていないことに気づくのは非常に絶望的です)
- Kee 拡張機能の使い勝手が悪く、よく入力できなかったり、自動で認識・入力されなかったり、保存できなかったりした(実のところ Bitwarden の方がもっと使いにくいのですが)
- Keepass2android がよくバックグラウンドでキルされ、キルされると毎回全体(20+桁)のマスターパスワードを打ち直さなければならなかった
そのため、筆者は友人の勧めで Bitwarden について知ることとなりました。
Bitwarden と Vaultwarden
Bitwarden は、オープンソースと SaaS が並行して提供されているパスワードマネージャープロジェクトです。そのメインデータベースはクラウド上にあり、各クライアントは HTTP REST API を通じてサーバーからボルトを同期します。KeePass のように、あるPCのデータベースをメインとして扱い、そのPCがメインボルトを WebDAV 経由でクラウドストレージに同期してから他のデバイスが同期しに行く、という方式ではありません。また、Webインターフェイスも提供されているため、クライアントが全くインストールされていない他の人のPCからでも、一時的にパスワードを取り出してログインすることができます。
Bitwarden はそのセキュリティホワイトペーパーの中で、ユーザーのパスワードボルトの安全性をどのように保証しているかを説明しています。主な仕組みとしては、マスターパスワードとメールアドレスから Master Key を派生させ、この Master Key のハッシュを使ってサーバーと認証を行います(つまり、サーバー上のそのアカウントにとってのパスワードは、マスターパスワードや Master Key そのものではなく、Master Key のハッシュということになります。前者の2つがサーバーに送信されることは決してありません)。そして同時に、この Master Key を利用して一つの共通鍵を保護します。暗号化された共通鍵と、その鍵を利用して暗号化されたパスワードボルトがサーバー上に保存されます。サーバーは Master Key を持っていないため、この共通鍵を取得できず、結果として平文のパスワードボルトを見ることはできません。
しかし、私は Bitwarden の公式サーバーを使いたくありませんでしたし、公式の Bitwarden インスタンスソフトで自鯖で構築する気もありませんでした。なぜなら、公式の実装はリソースをかなり消費しますし、筆者には OIDC などの豊富な機能は必要なかったからです。ですが、コミュニティで有志がメンテナンスしている非公式の Bitwarden サーバープロジェクト「Vaultwarden」がありました。これは Rust で書かれており、リソースの消費が比較的少なくなっています。
サーバーのインストール
本体のインストール
今回も筆者はコンテナのオーケストレーションに Docker Compose を使用しました。この部分の公式チュートリアルはこちらです:https://github.com/dani-garcia/vaultwarden/wiki/Using-Docker-Compose
いきなりですが、docker-compose.yml ファイルの内容を記載します:
version: "3.6"
services:
vaultwarden:
image: vaultwarden/server:latest
restart: always
volumes:
- "./data:/data/"
ports:
- "127.0.0.1:9001:80"
- "127.0.0.1:9002:3012"
environment:
- SIGNUPS_ALLOWED=true
- WEBSOCKET_ENABLED=true
- DOMAIN=https://分配给vaultwarden的域名
ここでいくつか注意点があります:
- データベースはデフォルトの SQLite 実装を使用し、現在のフォルダの
dataサブフォルダをマウントしています。事前に作成して権限を調整しておく必要があります - 公式サイトにある Caddy の設定を削除し、代わりに 80 番と 3012 番ポート(それぞれ HTTP と WebSocket)をホストマシンの 9001 番と 9002 番にマッピングしました(バインドアドレスを
127.0.0.1に指定することで外部からの接続を禁止しています)。これは、私の環境では Caddy をホストマシン上に置いて各サイトを統合管理しているためです - 注意点として、これは最初に使うファイルです。自分のユーザー登録が完了したら、後で戻ってきてユーザー登録をオフにする必要があります。
本体には他に設定が必要なもの(.env など)はなく、ビルドも不要です。そのまま docker compose up して問題ないことを確認したら、Ctrl+C で抜けて docker compose start すればOKです。

リバースプロキシの設定
Vaultwarden のようなセキュリティが極めて重要なアプリケーションにおいては、筆者は HTTPS の使用を強く推奨します。公式サイトの設定もすべて HTTPS の設定を含んでいます。筆者は変わらずホストマシン上の Caddy を使ってリバースプロキシと SSL 機能を提供しています。Caddyfile に以下の内容を追記します:
vaultwardenに割り当てたドメイン {
handle {
reverse_proxy 127.0.0.1:9001
}
handle /notifications/hub {
reverse_proxy 127.0.0.1:9002
}
}
ドメイン名が上記の docker-compose.yml のものと一致していることに注意してください。追記後に Caddy の設定をリロードすれば、ブラウザから自分の Vaultwarden インスタンスのアドレスにアクセスできるようになります。

ユーザー登録とデータインポート
インスタンスにアクセスしたら、「アカウントの作成」をクリックして自分のユーザーを作成し、パスワードボルトに入ります。

筆者はすでにデータをインポートしているため、パスワードボルト内にパスワードがあります。もしあなたが以前に他のパスワード管理ツール(KeePass や Chrome 内蔵のマネージャーなど)を使っていたなら、上部のツールをクリックし、左側で「データのインポート」に切り替えることができます。インポートするフォーマットを選択すると、そのフォーマットのファイルを取得するためのチュートリアルがポップアップ表示されるので、その通りに操作すればOKです。

同時に、筆者はすべてのパスワードのインポートと設定が完了した後、万が一に備えて暗号化されたパスワードボルトファイルもエクスポートしておきました。
ユーザー登録と設定が完了したら、このインスタンスは一人インスタンスなので、新規ユーザーの登録を無効にする必要があります。docker-compose.yml の SIGNUP_ALLOWED の後ろにある true を false に変更し、再び docker compose down && docker compose up すれば完了です。
クライアントの設定と使用
Vaultwarden は非公式のサーバーサイドですが、Bitwarden のクライアントプロトコルとほぼ完全に互換性があるため、Bitwarden のクライアントをそのまま使用すれば大丈夫です。筆者がインストールしたのは、Windows と Android プラットフォームのクライアントアプリ、そして Chrome の拡張機能です。インストール後、この3つは非常によく似たインターフェースを提供しています。セルフホスト環境を選択し、サーバーのアドレスを入力してから、メールアドレスとマスターパスワードを使ってログインするだけです。
Windows や Android のアプリは、指紋認証を使って素早くパスワードボルトをロック解除するように設定できます。また、Chrome 拡張機能は Windows クライアントに接続し、Windows クライアントを中継して指紋認証によるロック解除を実現することも可能です。しかし、安全性を考慮して筆者は基本的にこれらの機能をオンにせず、Android での指紋認証によるロック解除だけを残しました(スマホで毎回30桁近いパスワードを入力するのは本当に苦痛ですが、PCならまだマシだからです)。また、タイムアウトによるパスワードボルトの自動ロックや自動同期なども設定できます。
Android で自動入力機能を使用するには、Bitwarden クライアントの自動入力権限をオンにする必要があります。「設定」から一番上の「自動入力サービス」を選択し、指示に従って設定すればOKです。
クライアントの設定が終わったら、当然パスワードマネージャーの2つの重要な機能、アイテムの自動作成と自動入力について話さなければなりません。
Bitwarden のアイテム自動作成は、控えめに言って災難としか言いようがありません。筆者は先ほど、Kee 拡張機能もこの機能については少しひどいと触れました。通常、Kee の場合はウェブページでパスワードが送信されたことを検知すると、Chrome の拡張機能バーで自身をハイライトし、ユーザーがクリックした後にアイテムを新規作成または更新するかどうかを尋ねてきます。一方、Bitwarden の拡張機能は通常、パスワードを送信した後、ウェブページの上部に通知バーをポップアップさせ、アイテムを新規作成・更新するかどうかを尋ねます。問題なのは、この通知バーが必要な時にはほとんどポップアップせず、逆に不要な時(例えばすでに作成済みで、元のパスワードのままログインした時など)に限ってポップアップしてくることです。さらに、この通知バーは DOM を通じて直接ウェブページ上にレンダリングされるため、一部のフルスクリーンの Web アプリでは画面全体が下に押し下げられてしまいます。筆者はこの数ヶ月の使用の中で、このアイテム自動作成機能がまともに成功したことはほとんどありません。
しかし、Chrome での自動入力に関しては Kee よりも少しマシです。Kee は多くの場合、ウェブページに入力可能なフィールドを検出しても入力してくれず、ユーザーが入力フィールドを右クリックし、一致するアイテムを検索して選ばなければなりません。Bitwarden の場合は、ロック解除状態であれば大抵はパスワードフィールドを検出して自動入力してくれます。ただし、ウェブページにアクセスした時にロック状態だった場合は、ロックを解除した後(この時、サイトがすぐにマッチングされ、対応するパスワードアイテムが表示されます)、表示されたパスワードアイテムを手動で一度クリックすれば自動入力されます。Bitwarden のパスワードマッチングの柔軟性は Kee とほぼ同じですが、ブラウザ拡張機能側でのサポートがより強力です(高度な設定のすべてをわざわざ KeePass デスクトップアプリ内で調整する必要がありません)。単一のアイテムに複数の URL を設定したり、URL に対して異なるマッチング方式を設定したりすることもでき、全体的に満足できる出来です。

また、パスワードの検索/マッチングウィンドウにおいて、実はマッチング(検索ボックスを空にしたまま、現在の URL とマッチングさせる)と検索(現在の URL を無視し、検索ボックスの入力内容でアイテムを探す)の2つのビューでは、表示される内容とクリック時のロジックが異なります。
- マッチングビューの場合、アイテムの最初のアイコンは長方形の「表示」で、クリックするとアイテムの詳細や修正などができます。アイテムの左側をクリックすると、直接アイテムが入力されます。
- 検索ビューの場合、アイテムの最初のアイコンは外向きの矢印で、クリックするとそのアイテムの URL に直接ジャンプします。アイテムの左側をクリックすると表示ビューに入ります(マッチングビューで「表示」ボタンをクリックするのと同じ)。入力をするには、まず表示ビューに入り、その中で入力を選択するか、URL を保存してから入力する必要があります。
アイテムを新規作成したり編集したりする際には、拡張機能が提供している便利なパスワード生成機能を使用できます。パスワードフィールド内の矢印アイコンをクリックするだけです。すでにフィールド内にパスワードが入っている状態でジェネレーターをクリックすると、古いパスワードを上書きするかどうかの警告が出ます。

この画面で、パスワードの後ろにある矢印をクリックするとパスワードが再生成され、重なっている正方形のアイコンは当然コピーです。筆者は普段、まずこの画面でパスワードをコピーしておき、それから上部の「選択」をクリックします。こうすることで生成されたパスワードが一つ前の画面(アイテムの作成/編集画面)のパスワードフィールドに入ります。その他の項目も入力して保存すれば完了です。その後、登録画面で自動入力すれば、新しいパスワードを入力させることができます(ただし、パスワード変更の際はこうしてはいけません。変更画面で古いパスワードを入力する必要がある場合、おすすめの操作手順は:変更画面にアクセスして古いパスワードを自動入力する->新しいパスワードを生成してコピーする->パスワードマネージャー内の古いパスワードを上書きする。この時絶対に自動入力しないように注意する->アイテムを保存する->ウェブページの新しいパスワードフィールドに新パスワードを貼り付けて送信する、となります)。もし間違えて入力してしまった場合は、クリップボードにあるものを貼り付けて修正します。
また、TOTP(時間ベースのワンタイムパスワード)による二要素認証を有効にできるウェブサイトでは、TOTP キーを Bitwarden のアイテムに貼り付けておくこともできます。そうすれば、アイテムの詳細情報を開いたときに、現在の TOTP ワンタイムパスワードを確認することができます。
Android 側では、Bitwarden の使用感は Keepass2Android よりも優れていると筆者は感じています。主な理由は自動入力の成功率がはるかに高いことで、ほとんどのアプリやサイトで正しく自動入力ドロップダウンメニューがポップアップしてくれます。
ディスカッション
筆者がパスワードマネージャー環境を構築し、移行する過程で、「パスワード」という古くからの認証条件がその地位を失いつつあることに気づきました。一方で TOTP やメール認証などの二要素/多要素補助認証が発展し、もう一方では中国国内の SMS 認証や、海外の FIDO2/WebAuthn などのパスワードレスログインの仕組みも主流になり始めており、単にパスワードだけを使った認証方式は間違いなく廃れていくでしょう。同時に、OpenID Connect などの SSO(シングルサインオン)ソリューションの発展により、インターネット全体の認証が集中化する傾向にあります。皆が利用している認証サービス(Google、Facebook など)が信頼できるという前提の下では、これは良い傾向であると考えられます。小さな会社ほど不適切な情報セキュリティの運用/実践が行われやすく、大手企業のサービスは少なくとも技術面では基準を満たしているからです。
また、ここでユーザーの視点から各種認証方法に対する評価についても書いておこうと思います。
まず、認証要素の分類についてですが、一般的な認証は主に以下の3つの観点から行われます:
- 知っていること(Something you know = 知識情報):パスワード、またはローカル PIN(PIN とパスワードの違いについては後述します);
- 持っていること(Something you have = 所持情報):スマートカード、スマホ、SMS、メール、スマートバンド、USB セキュリティキーなど;
- 自身の特性(Something you are = 生体情報):指紋、顔、虹彩など、一般的な生体認証;
上記はユーザー側における「認証要素」の区分です。同時に、異なる検証環境に向けて、様々な認証「アーキテクチャ(体系)」も開発されてきました(これら「認証アーキテクチャ」と上記の「認証要素」は大部分において直交関係にあることに注意してください):
- 個人環境、Web / App 環境:各サイトが直接検証を行うか、OIDC などを用いたフェデレーション認証を利用
- 個人環境、PC / スマホの起動時:以前は通常ローカルで直接認証していましたが、現在はデバイス/システム提供者によるフェデレーション認証を利用することが増えています
- 企業環境、Web 環境:集中認証には伝統的に SAML や CAS、さらには LDAP が使われることが一般的ですが、最近のものであれば OAuth2/OIDC が導入されることもあります
- 企業環境、PCの起動とローカルサービス:Kerberos、LDAP、スマートカード、Active Directory などの集中認証。最近では Windows Hello for Business もあります
- ダイヤルアップ接続/VPN 認証とアカウンティング:RADIUS+CHAP の集中認証
様々な既存の手段を列挙したところで、筆者がそれぞれについて評価してみます。様々な認証アーキテクチャがもたらす階層を一旦脇に置くと、ユーザーは最終的にどこかで認証要素を使って自身の身元を証明する必要があります。ここでは実のところ、2つのことが行われています:
- 識別 Identification:ユーザーが「自分は誰か」を宣言する
- 認証 Authentication:ユーザーが、自分が本当にその宣言した人物であることを証明する
異なる認証要素を使用する際、上記2つの段階の組み合わせ関係も異なります。例えば、FIDO2 を使う場合はこの2つを合わせて行えますが、パスワードを使用する場合は分ける必要があり、最初のステップはユーザーがユーザー名やメールアドレスなどを提供することで完了します。以下では、議論の対象となる手段が識別と認証を同時に行う場合を除き、主に「認証」の段階に焦点を当てて議論します。
また、各種の「認証要素」は必ずしも単独で使われるわけではなく、しばしば組み合わせて使用されます。第一要素があり、さらに補助として第二要素を用います。さらには、IP アドレス(個人的には IP アドレスを認証要素として使うことには極反対ですが……NFS、お前のことだぞ)、位置情報、デバイス情報など、より補助的な性質を持つ要素も導入し、総合的に認証の意思決定を行うこともあります。
知っていること = 知識情報
この部分で最も主要なのはパスワードですが、周知の通り、パスワードには多方面で欠点があります。複雑なパスワードは忘れやすく、簡単なパスワードはブルートフォース攻撃、覗き見、衝突攻撃を受けやすいです。また、パスワード自体が容易に不正使用されるリスクがあり、複数のサイトで同じパスワードを使い回しているとクレデンシャルスタッフィング(パスワードリスト攻撃)を受ける可能性があります。しかし、これらすべてをもってしても、パスワードの最大の利点である「シンプルさ」には敵いません。ユーザーにとっても提供側にとっても、パスワードによる認証は最もシンプルな手段です(勝負できるのは SMS/メール OTP くらいでしょう)。
もちろん、パスワード認証を誤って実装/使用してしまう開発者やユーザーも依然として存在します。ここで筆者が遭遇した2つのケースを挙げてみます:
- ハッシュ化やソルト付与を正しく行っていない:平文で保存しているもの(ハハハ、000webhostオメェは...)や、ソルトを付与していないものがあります。実際のところ、現在最も良い方法は、Argon2 などのオープンソースで成熟したパスワードのソルト付きハッシュ化ソリューションを直接使用することです;
- ハッシュ化されたパスワードをサーバーに送信する:これでは「パスワードのハッシュ」をパスワードとして扱っているのと同じです……パスワードはサーバー上で一度平文として処理されなければなりません(もちろん、通信プロセスは暗号化されている必要があります);
- パスワードを付箋に書く:物理的な直接攻撃に対する防御力ゼロです;
したがって、比較的バランスの取れた解決策は、この記事で説明したようなパスワードマネージャーを使用し、各サイトで異なる十分なエントロピーを持つパスワードを設定し、マネージャーに自動記憶・入力させることです。ユーザーはマスターパスワードを1つだけ覚え、自身のデバイス上のパスワードボルトをしっかり保管するだけで済みます。もう一つの解決策は、Yubikey などを使用し、静的長パスワードモードで非常に長いパスワードを設定して、ボタンを押すだけで自動入力させる方法です。この方法では各サイトで同じパスワードを使うことになりますが、バックエンドで直接平文保存されていない限り、パスワードが十分に長いため、レインボーテーブルによるクラックは容易ではありません。とはいえ、筆者はやはりこのような静的長パスワードでは強度が足りないと考えています。
「知っていること」に基づくもう一つの認証要素として PIN(Person Identification Number)があります。その定義には諸説ありますが(キャッシュカードの暗証番号も PIN ですが、明らかにここでの定義とは異なります)、筆者はここでは Microsoft 側で見かけた定義を採用します。すなわち 「デバイスのローカルで使うものを PIN と呼び、クラウドに保存できたりデバイス間で移行できたりするものをパスワードと呼ぶ」 というものです。PIN の検証プロセス全体はデバイスのローカルで行われます。さらに、あるデバイスの PIN を使って別のデバイスの検証を行うことはできず、PIN が外部に送信されることも一切ないため、セキュリティ要件をいくらか緩和することができます(例えば、通常は4~6桁の数字のみ、または英数字の組み合わせで PIN として使用できます)。
上記の定義からもわかるように、PIN はデバイスの起動時のロック解除など、ローカルの用途にしか使えません。同時に、PIN は2つ目の鍵をロック解除するためにも使えます。つまり、「持っていること」という認証要素の一環として、例えば PIN で長い秘密鍵をロック解除し、システムがその秘密鍵を使ってサーバーにログインする、といった具合です。読者の皆さんは、SSH 秘密鍵の Passphrase と似たようなものだと捉えてもらえば良いでしょう。
持っていること = 所持情報
この部分の認証手段は、一般的にユーザーが「何らかの物品(アイテム)」を所持していることに基づき、その物品を介して認証を行います。この「物品」の持つ2つの一般的な特性、すなわち 「携帯している情報量が多い」 (例:4096 bit 長の秘密鍵 vs 通常のパスワード)ことと、「比較的複雑な計算を実行できる」 (秘密鍵による計算やチャレンジ&レスポンス応答が可能である)ことにより、「持っていること」による認証方式はより堅牢で柔軟なものになります。欠点としては、ユーザーがこの「物品」を厳重に保管しなければならないことです。そうしないと、アカウントの復旧が非常に困難になります。というのも、「持っていること」に基づくこれらの認証を簡単にキャンセルしたりバイパスしたりすることは許可できないからです。もしそれが可能なら、その価値自体が失われてしまいます。
具体的な手段について話すと、ここからはまさに百花繚乱です。同じ使用シーンのものをできるだけまとめて説明します:
- SSH 公開鍵/秘密鍵:公開鍵・秘密鍵の非対称暗号に基づく、非常に使い勝手の良い第一認証要素ですが、識別(Identification)は実現できません(ユーザー名は依然として提供する必要があります);
- FIDO2/WebAuthn:筆者が比較的気に入っているデバイス認証ソリューションです。デバイス上にはデバイスから出ることのない秘密鍵があり、登録時にウェブサイトに対して一種の「公開鍵」のような情報を送信します。識別/認証時にはこの公開鍵を提示するとともに、秘密鍵による署名を通じてウェブサイトからのチャレンジに応答します。
この「デバイス」は独立したハードウェアが担うこともでき(使用時に USB や NFC を経由して PC・スマホと通信します)、普通の PC やスマホ上のセキュリティチップ、さらにはソフトウェアによるエミュレーション(例えば Windows Hello)が担うこともできますが、安全性は順に低下していきます。
また、FIDO2/WebAuthn は識別(Identification)の機能も統合しているため、ユーザー名の入力を省略し、真のパスワードレスログインを実現できます(もちろん、これを第二要素として使うことも可能です)。
ユーザーの体感としては、サイトにログインする必要があるときに USB デバイスを挿してボタンを押すか、PC の指紋リーダーに触れるだけでログインが完了する、という感じです; - U2F(Universal 2nd Factor):もう一つのデバイス認証ソリューションで、Google が初期によく使っていました。現在は FIDO CTAP1 と呼ばれていますが、個人的には FIDO2 と比べて大きな優位性はないと感じています;
- TOTP(Time-based One Time Password、時間ベースのワンタイムパスワード):RFC 6238 で比較的詳細に定義されており、主な原理は銀行の電子パスワード生成器に似ています。登録時にウェブサイトとデバイスで一連のシード(種)を共有します(スマホなら QR コードをスキャン、PC なら文字列を直接コピペするだけで済みます)。その後、サイト側とデバイス側は互いに一切通信することなく、時間に基づいてパスワード(30秒ごとに切り替わる6桁のパスワード)を計算できます。ユーザーがログインする際、デバイス上の「現在のパスワード」を送信し、サーバー側は時間から計算したパスワードとそれを照合するだけで済みます。そのアルゴリズム設計により、1つまたは複数組のワンタイムパスワードから別のワンタイムパスワードを計算したり、複数組のワンタイムパスワードからシードを逆算したりすることは不可能となっており、一定の安全性が保証されています。TOTP はよく第二認証要素として使われます;
FIDO2/WebAuthn がない状況であれば、TOTP も悪くないと筆者は考えています。 - 各種「トークン」/「デバイスロック」:GitHub Mobile や Battle.net Authenticator などのことです。スマホ上のアプリと通信して認証を行い、ユーザーがスマホ上でタップして承認します。本質的にはスマホ上のログインセッションを利用しており、スマホ上のログインの安全性は他の方法で保障されます(例えば QQ のデバイスロックは電話番号を使い、Battle.net Authenticator に至っては(中国で)身分証情報を使用します)。個人的には好きではありません。というのも、非常に長持ちするログインセッションに依存することになる上、移行の際にも不便だからです。
- 各種 USB セキュリティキー:本質的には独自プロトコルの FIDO2/WebAuthn です。独自プロトコルであるがゆえに、筆者は好きではありません。
- スマートカード:ここでは特に Windows 上のスマートカード、つまり Windows Smart Card Minidriver を実装したデバイスを指します。一般的には ISO/IEC 7816-4 を実装したスマートカード/リーダーが使われますが、Yubikey もこのドライバーを実装していますし、ソフトウェアエミュレーションのソリューションすら存在します。このシステムは企業内でよく使われます。比較的クローズドなため詳しくなく、評価は控えますが、筆者はクローズドなソリューションに対してあまり良い印象を持っていません;
まとめると、Web / App 環境において筆者が好む認証手段は FIDO2/WebAuthn と TOTP であり、できれば FIDO2 でパスワードレス認証を行えるのがベストです。しかし、FIDO2/WebAuthn には様々なモードがあり、すべてのサイトがすべてのモードをサポートしているわけではありません。例えば、多くのサイトでは「platform」モードの WebAuthn を使えないため、Yubikey などの専用ハードウェアでのログインしかサポートされず、スマホの指紋認証や Windows Hello の顔認証を利用できなかったりします。もう一方で TOTP は第二要素としてしか使えませんが、非常にシンプルであり、Bitwarden 内に組み込むことさえ可能です。残念ながら、中国で FIDO2/WebAuthn を実装しているアプリは多くなく、より多く使われているのは以下で述べる「変種」です。
さらにいくつかの「変種」があります(これら2つをここに分類するのが適切かどうかは分かりませんが):
- メールワンタイムパスワード認証:ログイン時にメールアドレスを入力すると、サイトがそのメールアドレス宛にワンタイムパスワード(例えば6桁の数字)やリンクを含むメールを送信し、ユーザーがそのパスワードやリンクを入力してシステムにログインします;
本質的には認証をメール側に押し付けているため、統合認証(フェデレーション)のようなニュアンスがあります。欠点は、メールアドレスが攻撃されやすい弱点になってしまうことです。
メールアドレスはパスワード変更時の(多くの場合唯一の)認証基準としても頻繁に使われていることを考えれば、それをログインに使うのも普通のことではあります。 - SMS / 音声通話ワンタイムパスワード認証:メールと同様ですが、違いはワンタイムパスワードやリンクの送信に SMS や音声通話が使われることです。
長所として、国内(中国)ではスマホが乗っ取られにくいことが挙げられます(SIM スワップは国内では本人自身が実店舗の窓口に行く必要があるため不可能に近く、本質的には「自身の特性」の認証に近いと言えます)。海外では SIM スワップのリスクがやや高いです。短所は、電話番号自体がプライバシーの属性を持っていること、一人がそれほど多くの電話番号を持てるわけではないこと、そしてサイト側の SMS ゲートウェイには料金がかかることです。
これら2つの変種は概して強力な認証力を持ち、単一要素認証として使われることがよくあります(中国国内では特に電話番号ログインが多く、パスワードすら省略されます)。また、ログイン環境が異常な時にこの2つの要素を補助的な検証として用いたり、第一要素が使えない時(パスワードを忘れた時など)の救済措置として使われたりするのもよく見かけます。
メールについては一旦置いておくとして、実のところ筆者は SMS ワンタイムパスワード認証を激しく憎んでいます:
- 電話番号はプライバシー漏洩を引き起こす可能性があります。特に中国の情報セキュリティの現状を考慮すると尚更です;
- 一人がそれほど多くの電話番号を持つことはあまりないため、複垢が困難になりますが、筆者は一人で複数のアカウントを持てることは本来ごく当たり前のことだと考えています(銀行決済などのセンシティブな業務を除き);
- 開発者としては、SMS ゲートウェイにお布施(少なからぬ費用)を払う必要があります;
- そもそも、複数のサイトをまたぐこれほど強力な認証力を、(自分ではコントロールできない)一つのものに紐付けること自体が身の毛もよだつような話です。一方で電話番号のコントロール権は通信キャリアにあり、現在(中国では)詐欺防止で回線停止措置が厳しすぎて頭がおかしくなりそうですが、筆者は国家の政策にあまり口出しする度胸はないので、「わかる人にはわかる」という評価に留めます。もう一方で、紛失や電話番号の変更があった場合、一体その電話番号でどれだけのサービスに登録したかを追跡するのは困難であり、ユーザーの移行コストは甚大です;
- 電話番号は多くの場合、インターネット実名制の一環となっていますが、筆者は中国国内で現在推し進められているインターネット実名制に対して、終始一貫して明確に、強く反対しています;
自身の特性 = 生体情報
ここは主に生体認証手段、すなわち「その人自身が持つ、他の人と区別できる、できれば生涯変わらない特徴」のことです。よく使われるものには指紋、顔、掌紋、虹彩などがあります。成熟したソリューションも多く存在しますが、ここでは詳しく述べません。Windows Hello や Apple の Touch ID、Face ID などはすべてこれに分類されます。
筆者がどうしても我慢ならないのは、多くの国内メーカーが狂ったように生体認証(特に顔)を濫用して認証を行い、さらには公共の場でそのような操作を行わせていることです(WeChat Pay や Alipay の顔認証決済など)。これはセキュリティの脆弱性であると同時に、プライバシー漏洩の危機でもあります。筆者が考える生体認証の越えてはならない一線は、一般のアプリにおいては(つまり警察/税関/出入国管理/銀行のような特殊業務を除き、そもそも警察が前科のない人の高精度な特徴データを保存すべきかどうかも議論の余地がありますが)、いかなる生体特徴データもクラウドに上げるべきではない、ということです。スマホ上で指紋や顔の認識を行う場合、通常はこの問題はありません(指紋の場合はスマホの CPU にすら入らず、独立したセキュリティチップ上で完全に処理されます)が、街中にある自動販売機は明らかにあなたの顔をクラウドに送信しています。
認証アーキテクチャ
各認証要素についての辛口評価が終わったところで、次に各認証アーキテクチャ(体系)を見てみましょう:
- 各ポイントでの直接認証:ローカルデバイスやパスワードのみを使用するサイトであればまだ良いですが、Web フロント、複数サイトの連携、多要素認証といった状況においては、あまり経済的ではありません;
- Web シーン、内部統合認証:Federated 統合 ID 認証環境下では、一般的に統一された「アイデンティティプロバイダ」(IdP、Identity Provider)が一つ存在し、ユーザー情報の保存とユーザー認証サービスを提供します。「内部」とは、このサービスに接続する各サイト/アプリが同じ事業体(エンティティ)によって管理されていることを意味します。例えば、ある学校の IdP と学校内の各種ネットワークサービス(「統合 ID 認証」)などです。
ここでは伝統的に SAML や CAS、さらには LDAP など、すでに大量のソリューションが存在しています。個人的にはこの3つはどれも複雑度が高いため好きではありません。特に SAML と LDAP は天地がひっくり返るほどの複雑さで、LDAP は完全に「クトゥルフ神話における旧支配者」レベルです。最近のトレンドは OAuth2 をベースに作られたもの(OIDC(OpenID Connect)など)が多く、開発体験は一般的にこちらの方が優れています。ユーザー体験に大きな差は出ないはずですが、実際の状況では実装の質によってバラツキが生じます。例えば、南京大学の統合 ID 認証で採用されている金智教育(Wisedu)社の IDS6 IdP の OAuth2 実装は非常にひどいものでした(あいつらの LDAP 実装もそこまで良いとも思えませんが)。 - Web シーン、サードパーティ統合認証:上記との違いは、ここでの IdP は一般的に独立した事業体であり、純粋に認証業務のみを提供している点です。よくある「QQ でログイン」「Google でログイン」がこれに該当し、この場合は一般的に OAuth2 をベースにした仕組みが使われます。本質的には、これらの有名な IdP が迅速かつ安全なログインサービスを提供していることになります(特に多くのユーザーのデバイス上には長期的な Google/Facebook のセッションが存在するため、ワンクリックで遷移してログインすることが可能です)。総じて悪くありません。
- 企業環境、PCの起動とローカルサービス:Kerberos、LDAP、スマートカード、Active Directory などの集中認証。これらのプロトコルはどれも天地を揺るがし鬼神を泣かせるほどの複雑さを持っています。138ページもある Kerberos の RFC 4120 を私はどうしても理解できませんでしたし、LDAP に至っては言うまでもありません(ITU-T が作ったあの X シリーズの代物を見るのは、10億人の殺人鬼に直面するよりも恐ろしい……怖すぎるッ!! そして LDAP は X.500 DAP をベースに生まれたのです)。しかし一方で、エンタープライズ級™ シーンのアプリケーションはとうの昔にこれらに対して経路依存性を形成してしまっていますし、もう一方では複雑であること以外にこれらのプロトコルには大きな致命傷もないので、そこまで問題にはなりません。
- ダイヤルアップ接続/VPN 認証とアカウンティング:RADIUS+EAP+PAP/CHAP の集中認証:臭くてたまらないクソ、完全にダイヤルアップ会社がその場しのぎで考え出した怪物の現代における歴史的残骸です……こんなシンプルな要求をどうやったらここまで複雑で硬直したものにできるのか想像もつきません。幸い FreeRADIUS はまだなんとか使えるので軽く deploy してみました。
最後に
Vaultwarden について語る記事で、その半分のスペースを各種認証要素やソリューションの論評に費やしてしまいましたが、まあそれも理にかなっているでしょう。私の論評は主観的な色合いが強く、またゴッドビューに立っている(例えば2023年の視点から1990年代に発明されたプロトコルを見ているなど)ため、皆さんは話半分で楽しんでいただければ幸いです。しかし、一人のユーザーとして(たまには実装者として)、これらの論評がいくつかのペインポイントを反映し、より多くの実装者がより合理的な認証ソリューションをサポートし、より多くのユーザーが適切な認証要素を選択できるようになることを願っています。
Comments NOTHING