サイトアイコン 茶栗栗屋

Vaultwarden/Bitwarden パスワードマネージャー + 認証についてディスカッション 【セルフホスティング奮闘記3/3】

当シリーズについて

この記事は、筆者が計画しているシリーズ「セルフホスティング奮闘記」の第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 年使っていましたが、その中で筆者は以下のようないくつかの問題に直面しました:

そのため、筆者は友人の勧めで 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的域名

ここでいくつか注意点があります:

本体には他に設定が必要なもの(.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.ymlSIGNUP_ALLOWED の後ろにある truefalse に変更し、再び 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つのビューでは、表示される内容とクリック時のロジックが異なります。

アイテムを新規作成したり編集したりする際には、拡張機能が提供している便利なパスワード生成機能を使用できます。パスワードフィールド内の矢印アイコンをクリックするだけです。すでにフィールド内にパスワードが入っている状態でジェネレーターをクリックすると、古いパスワードを上書きするかどうかの警告が出ます。

この画面で、パスワードの後ろにある矢印をクリックするとパスワードが再生成され、重なっている正方形のアイコンは当然コピーです。筆者は普段、まずこの画面でパスワードをコピーしておき、それから上部の「選択」をクリックします。こうすることで生成されたパスワードが一つ前の画面(アイテムの作成/編集画面)のパスワードフィールドに入ります。その他の項目も入力して保存すれば完了です。その後、登録画面で自動入力すれば、新しいパスワードを入力させることができます(ただし、パスワード変更の際はこうしてはいけません。変更画面で古いパスワードを入力する必要がある場合、おすすめの操作手順は:変更画面にアクセスして古いパスワードを自動入力する->新しいパスワードを生成してコピーする->パスワードマネージャー内の古いパスワードを上書きする。この時絶対に自動入力しないように注意する->アイテムを保存する->ウェブページの新しいパスワードフィールドに新パスワードを貼り付けて送信する、となります)。もし間違えて入力してしまった場合は、クリップボードにあるものを貼り付けて修正します。

また、TOTP(時間ベースのワンタイムパスワード)による二要素認証を有効にできるウェブサイトでは、TOTP キーを Bitwarden のアイテムに貼り付けておくこともできます。そうすれば、アイテムの詳細情報を開いたときに、現在の TOTP ワンタイムパスワードを確認することができます。

Android 側では、Bitwarden の使用感は Keepass2Android よりも優れていると筆者は感じています。主な理由は自動入力の成功率がはるかに高いことで、ほとんどのアプリやサイトで正しく自動入力ドロップダウンメニューがポップアップしてくれます。

ディスカッション

筆者がパスワードマネージャー環境を構築し、移行する過程で、「パスワード」という古くからの認証条件がその地位を失いつつあることに気づきました。一方で TOTP やメール認証などの二要素/多要素補助認証が発展し、もう一方では中国国内の SMS 認証や、海外の FIDO2/WebAuthn などのパスワードレスログインの仕組みも主流になり始めており、単にパスワードだけを使った認証方式は間違いなく廃れていくでしょう。同時に、OpenID Connect などの SSO(シングルサインオン)ソリューションの発展により、インターネット全体の認証が集中化する傾向にあります。皆が利用している認証サービス(Google、Facebook など)が信頼できるという前提の下では、これは良い傾向であると考えられます。小さな会社ほど不適切な情報セキュリティの運用/実践が行われやすく、大手企業のサービスは少なくとも技術面では基準を満たしているからです。

また、ここでユーザーの視点から各種認証方法に対する評価についても書いておこうと思います。

まず、認証要素の分類についてですが、一般的な認証は主に以下の3つの観点から行われます:

上記はユーザー側における「認証要素」の区分です。同時に、異なる検証環境に向けて、様々な認証「アーキテクチャ(体系)」も開発されてきました(これら「認証アーキテクチャ」と上記の「認証要素」は大部分において直交関係にあることに注意してください):

様々な既存の手段を列挙したところで、筆者がそれぞれについて評価してみます。様々な認証アーキテクチャがもたらす階層を一旦脇に置くと、ユーザーは最終的にどこかで認証要素を使って自身の身元を証明する必要があります。ここでは実のところ、2つのことが行われています:

異なる認証要素を使用する際、上記2つの段階の組み合わせ関係も異なります。例えば、FIDO2 を使う場合はこの2つを合わせて行えますが、パスワードを使用する場合は分ける必要があり、最初のステップはユーザーがユーザー名やメールアドレスなどを提供することで完了します。以下では、議論の対象となる手段が識別と認証を同時に行う場合を除き、主に「認証」の段階に焦点を当てて議論します。

また、各種の「認証要素」は必ずしも単独で使われるわけではなく、しばしば組み合わせて使用されます。第一要素があり、さらに補助として第二要素を用います。さらには、IP アドレス(個人的には IP アドレスを認証要素として使うことには極反対ですが……NFS、お前のことだぞ)、位置情報、デバイス情報など、より補助的な性質を持つ要素も導入し、総合的に認証の意思決定を行うこともあります。

知っていること = 知識情報

この部分で最も主要なのはパスワードですが、周知の通り、パスワードには多方面で欠点があります。複雑なパスワードは忘れやすく、簡単なパスワードはブルートフォース攻撃、覗き見、衝突攻撃を受けやすいです。また、パスワード自体が容易に不正使用されるリスクがあり、複数のサイトで同じパスワードを使い回しているとクレデンシャルスタッフィング(パスワードリスト攻撃)を受ける可能性があります。しかし、これらすべてをもってしても、パスワードの最大の利点である「シンプルさ」には敵いません。ユーザーにとっても提供側にとっても、パスワードによる認証は最もシンプルな手段です(勝負できるのは SMS/メール OTP くらいでしょう)。

もちろん、パスワード認証を誤って実装/使用してしまう開発者やユーザーも依然として存在します。ここで筆者が遭遇した2つのケースを挙げてみます:

したがって、比較的バランスの取れた解決策は、この記事で説明したようなパスワードマネージャーを使用し、各サイトで異なる十分なエントロピーを持つパスワードを設定し、マネージャーに自動記憶・入力させることです。ユーザーはマスターパスワードを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 通常のパスワード)ことと、「比較的複雑な計算を実行できる」 (秘密鍵による計算やチャレンジ&レスポンス応答が可能である)ことにより、「持っていること」による認証方式はより堅牢で柔軟なものになります。欠点としては、ユーザーがこの「物品」を厳重に保管しなければならないことです。そうしないと、アカウントの復旧が非常に困難になります。というのも、「持っていること」に基づくこれらの認証を簡単にキャンセルしたりバイパスしたりすることは許可できないからです。もしそれが可能なら、その価値自体が失われてしまいます。

具体的な手段について話すと、ここからはまさに百花繚乱です。同じ使用シーンのものをできるだけまとめて説明します:

まとめると、Web / App 環境において筆者が好む認証手段は FIDO2/WebAuthn と TOTP であり、できれば FIDO2 でパスワードレス認証を行えるのがベストです。しかし、FIDO2/WebAuthn には様々なモードがあり、すべてのサイトがすべてのモードをサポートしているわけではありません。例えば、多くのサイトでは「platform」モードの WebAuthn を使えないため、Yubikey などの専用ハードウェアでのログインしかサポートされず、スマホの指紋認証や Windows Hello の顔認証を利用できなかったりします。もう一方で TOTP は第二要素としてしか使えませんが、非常にシンプルであり、Bitwarden 内に組み込むことさえ可能です。残念ながら、中国で FIDO2/WebAuthn を実装しているアプリは多くなく、より多く使われているのは以下で述べる「変種」です。

さらにいくつかの「変種」があります(これら2つをここに分類するのが適切かどうかは分かりませんが):

これら2つの変種は概して強力な認証力を持ち、単一要素認証として使われることがよくあります(中国国内では特に電話番号ログインが多く、パスワードすら省略されます)。また、ログイン環境が異常な時にこの2つの要素を補助的な検証として用いたり、第一要素が使えない時(パスワードを忘れた時など)の救済措置として使われたりするのもよく見かけます。

メールについては一旦置いておくとして、実のところ筆者は SMS ワンタイムパスワード認証を激しく憎んでいます:

自身の特性 = 生体情報

ここは主に生体認証手段、すなわち「その人自身が持つ、他の人と区別できる、できれば生涯変わらない特徴」のことです。よく使われるものには指紋、顔、掌紋、虹彩などがあります。成熟したソリューションも多く存在しますが、ここでは詳しく述べません。Windows Hello や Apple の Touch ID、Face ID などはすべてこれに分類されます。

筆者がどうしても我慢ならないのは、多くの国内メーカーが狂ったように生体認証(特に顔)を濫用して認証を行い、さらには公共の場でそのような操作を行わせていることです(WeChat Pay や Alipay の顔認証決済など)。これはセキュリティの脆弱性であると同時に、プライバシー漏洩の危機でもあります。筆者が考える生体認証の越えてはならない一線は、一般のアプリにおいては(つまり警察/税関/出入国管理/銀行のような特殊業務を除き、そもそも警察が前科のない人の高精度な特徴データを保存すべきかどうかも議論の余地がありますが)、いかなる生体特徴データもクラウドに上げるべきではない、ということです。スマホ上で指紋や顔の認識を行う場合、通常はこの問題はありません(指紋の場合はスマホの CPU にすら入らず、独立したセキュリティチップ上で完全に処理されます)が、街中にある自動販売機は明らかにあなたの顔をクラウドに送信しています。

認証アーキテクチャ

各認証要素についての辛口評価が終わったところで、次に各認証アーキテクチャ(体系)を見てみましょう:

最後に

Vaultwarden について語る記事で、その半分のスペースを各種認証要素やソリューションの論評に費やしてしまいましたが、まあそれも理にかなっているでしょう。私の論評は主観的な色合いが強く、またゴッドビューに立っている(例えば2023年の視点から1990年代に発明されたプロトコルを見ているなど)ため、皆さんは話半分で楽しんでいただければ幸いです。しかし、一人のユーザーとして(たまには実装者として)、これらの論評がいくつかのペインポイントを反映し、より多くの実装者がより合理的な認証ソリューションをサポートし、より多くのユーザーが適切な認証要素を選択できるようになることを願っています。

モバイルバージョンを終了