サーバーは、シークレットを読み取るためのキーを保持せずに、どのようにしてシークレットを保存し、同期できるのでしょうか? Confidex がキーを作成し、ロックで保護し、それを使用してデータを暗号化し、変更を確認する方法を見ていきます。
ロック
扉を開けられるのは選ばれし者だけ
マトリックス リローデッド
機密データを保存するために仮想 vault を構築していると想像してください。アプリケーションを閉じた後もそのデータをラップトップに保持し、別のデバイスで開くことができるようにする必要があります。ボールトには独自のキーが必要です。ファイルをコピーすることで部外者が内容を読み取ったり変更したりできないように、ファイルを保存する必要があります。
lock は鍵を保護する小さな暗号化ファイルです. 開くにはマスターパスワードと秘密のコード secret_key が必要で, どちらもファイル自体には含まれません. ロックの内容を丸ごと朝刊に印刷してもかまいません. 読者はそのコピーを手にできますが, 公開するだけでは秘密鍵は明らかになりません.
部外者がこのファイルを入手し、さらにマスターパスワードを知ったとします。パスワードのみを必要とするすべての計算を繰り返すことができるようになりましたが、それらの計算ではロックを開くキーは生成されません。secret_key も入力です。このファイルには、パスワードのみの個別のチェックは含まれていません。それを開くには依然として未知のシークレットコードが必要です。
そのコードは、暗号学的に安全な乱数から生成され、人が考えたものではありません。secret_key が不明な限り、マスターパスワードを正しく推測できてもロックは開きません。攻撃者にはランダムな秘密値が欠けているからです。そのため、パスワードが弱いだけでサーバーのコピーに対する辞書攻撃が現実的になるわけではありません。
攻撃者が secret_key も取得した場合、オフラインでマスター パスワードの推測をテストできます。これにはコストがかかりますが、最終的な難易度はパスワードの強度によって決まります。
鍵
皆さん、キーは何をするのですか?鍵…何かのロックを解除しますか?
パイレーツ・オブ・カリビアン – デッドマンズ・チェスト
アプリケーションは、オペレーティング システムの暗号ジェネレータから 32 ランダム バイトを取得し、キーとして使用できるように準備します。これらの準備されたバイトは、秘密マスター キー master_key です。
アプリケーションは常に master_key から同じキーを導出します。同じバイトで同じ計算を繰り返すと、同じ結果が得られます。したがって、ラップトップと電話は同一のキーを回復できます。新しいデバイスで master_key を取得するには、アプリケーションはまずマスター パスワードと secret_key を使用してロックを開く必要があります。
最初のペア: 暗号化
私はキーマスターです。
ゴーストバスターズ
暗号化されたデータを保存するために、アプリケーションは master_key: secret_key から公開キーを計算します。ボールトのデータを暗号化できますが、復号化には master_key が必要です。
「パブリック」とは、アルゴリズムにおけるこのキーの役割を表しており、すべての人にそれを与える義務ではありません。 Confidex では、それはユーザーに残ります。これはロックを開くために必要な 2 番目のシークレットでもあるため、ローカルに残り、ネットワーク経由で送信されません。 secret_key を単独で使用すると、Vault のデータを暗号化できますが、復号したり、代わりに変更に署名したりすることはできません。
X25519 は master_key を入力として secret_key を計算するアルゴリズムです. master_key を secret_key から復元することは現実的には不可能です. 毎秒 10^18 回の曲線演算を行う架空のスーパーコンピューターでも古典的な攻撃には平均で約2.3兆年かかります (SafeCurves による見積もり).
2 番目のペア: 署名
オリジナルのものは署名だけです
キャッチ・ミー・イフ・ユー・キャン
最初のペアは内容を隠すのに役立ちます。次に、Vault の所有者が変更を承認したかどうかを確認する方法が必要です。サーバーは署名もチェックできる必要があるため、サーバーに公開検証キーを送信する必要があります。最初のペアの公開部分は secret_key であり、サーバーはこれを受信してはなりません。したがって、署名用に 2 番目の永続的なキー ペアを作成します。
別の独立した秘密を保存する代わりに、master_key から秘密署名鍵を派生し、それを signing_key と呼びます。アプリケーションはこれを使用してデータとロックに署名します。他のデバイスやサーバーは、その公開部分を使用して、内容を読み取ることなく署名を確認します。
このペアは同じ master_key から復元できます。アプリケーションはそこから開始値 (署名キーを一意に決定する 32 バイト) を導出します。
ジョブごとに異なるキーを導出するために、計算に固定ラベル (ここでは confidex/vault/ed25519/v1) を含めます。同じ master_key と同じラベルは常に同じキーを生成します。
マスターパスワードはこれらの計算には入っていません。どちらのペアも、人が発明した文字列ではなく、master_key のランダムなバイトに依存します。この区別は後で重要になります。パスワードは既存のキーへのアクセスを保護するため、キー自体を変更せずにパスワードを変更できます。
導出には、ハッシュベースのキー導出関数 HKDF、HMAC-based Extract-and-Expand Key Derivation Function が使用されます。 Confidex は SHA-256 とともに使用するため、HKDF-SHA256 という名前が付けられます。結果の 32 バイトは、Ed25519 ペアの seed です。 signing_key の公開部分では secret_key が公開されないため、署名検証のためにサーバーに送信できます。
ロック ファイルとは何ですか?またその機能は何ですか?
ポッド ベイのドアを開けてください、HAL。
宇宙の旅
ごめんなさい、デイブ。残念ながらそれはできません。
ロックには暗号化された master_key が保存されます。これを開くにはマスター パスワードと secret_key が必要です。パブリック ヘッダーには、フォーマット バージョン、ボールト識別子、signing_key のパブリック部分、ソルト、およびパスワード処理パラメータが含まれます。内部フォーマットのバージョンは、master_key とともに暗号化されます。
ロックを作成するとき、アプリケーションはすでに master_key を持っているため、暗号化前に secret_key を計算します。これを開くとき、ユーザーは secret_key を個別に指定します。これはロックのパブリック コピーには存在しません。
復号化後、アプリケーションは X25519 を使用して master_key の公開部分を計算し、それが secret_key と一致することを要求します。次に、signing_key を復元し、その公開部分をヘッダー内のキーと比較します。このようにして、ロック自体がチェックされます。
マスターパスワードの推測を遅らせるには、試行ごとに時間とメモリを消費する必要があります。 Confidex は、64 MiB のメモリと 3 つのパスを持つ Argon2id を使用します。パスワード処理でもランダム ソルトが使用されます。ソルトは秘密ではありません。ソルトにより、同じパスワードでも異なるロックでは異なる結果が生成されます。
secret_key がすでにわかっている場合、パスワードの長さによって攻撃の規模が大きく変わります。ランダムな 5 つのラテン小文字の組み合わせは約 1,200 万通り、7 の組み合わせは約 80 億通り、12 の組み合わせは約 9.5 × 10¹⁶通りあります。7 文字から 12 文字に変更すると、検索スペースがほぼ 1,200 万倍に増加します。特定のマシンで 7 文字の検索に平均 1 日かかる場合、5 文字の検索には約 2 分、12 文字の検索には約 33,000 年かかります。
Argon2id の結果と secret_key から 1 つのキーを取得するには、前に紹介した HKDF-SHA256 関数をラベル confidex/lock/wrap/v1 とともに使用します。結果は、ロックを暗号化するキーである wrapping_key です。
master_key を非表示にし、改ざんから保護する必要があります。 XChaCha20-Poly1305 は、それを wrapping_key で暗号化し、認証タグを追加します。このタグは、暗号文をパブリック ヘッダーにバインドします。パスワードが間違っていたり、保護されたバイトが変更されていると、開くことができなくなります。タグとランダムな使い捨て値 nonce は暗号文とともに保存されます。
ロックが作成または再度暗号化されるたびに、アプリケーションは新しいソルトと nonce を取得します。暗号化されたコンテンツには、1 つのバージョン バイトと 32 バイトの master_key が含まれます。 24 バイトの nonce と 16 バイトのタグが一緒に保存されます。 アルゴリズムのドキュメントでその構造が説明されています。
回復されたキー間の不一致は、意図的に不正なロック、またはロックを作成したプログラムのバグが原因で発生する可能性があります。ランダムなバイト破損は通常、タグがチェックされるときに早期に検出されます。
サーバーはこのタグの検証に必要な秘密情報を持っていません. そのためロックを転送する際にはアプリケーションが signing_key で作成した署名を付けます. 署名の対象は公開ヘッダーと暗号化された内容を含む完成したファイル全体です. サーバーは署名を検証し, 最初のアップロード時に signing_key の公開部分全体と保管庫の内部識別子を固定します. 以後の変更もこの鍵で検証します. ロックと暗号化されたデータの署名には同じ signing_key を使います. この鍵は master_key から復元されます.
金庫室
これでロックを公開できるようになりました。 secret_key と master password を知っている人だけが master_key を読み取ることができますが、ロック ファイルやデータの内容を見なくても誰でもロック ファイルから公開署名キーを取得し、署名されたデータを確認できます。
パスワード SuperPa55worD を考えてみましょう。現時点では、それを保存するだけです。アプリケーションを閉じた後、誰にも SuperPa55worD を見られることなく、ロック ファイルと暗号化されたパスワードをどこにでも送信でき、同時に作成者を確認できるようにする必要があります。 secret_key と master password がある場合、暗号化は元に戻せる必要があります。対称暗号は、同じキーを使用して暗号化と復号化を行うことができます。そのキーは保管していないため、ロックを使用して回復できなければなりません。これを message_key と呼びます。
新しい暗号化ごとに個別の message_key を取得します。私たちはまだその復元を手配する必要があります。暗号文の横に鍵を開いたままにしておくと、コピーを持っている人なら誰でもすべてを読むことができてしまいます。
アプリケーションは、新鮮なランダム バイトから、この暗号化のためだけに一時的な X25519 ペアを作成します: record_key。次に、record_key のプライベート部分とボールトの永続公開キー secret_key を取得し、共有秘密を計算します。別の関数がそのシークレットから message_key を導出します。このキーはパスワードを暗号化します。
読み取り時には、アプリケーションは別のルートをたどります。 master_key と record_key の公開部分を使用します。これら 2 つのキーは同じ共有秘密を生成します。数学的には、どちらのルートも、曲線上の同じ初期点に 2 つのプライベート数値を異なる順序で乗算します。順序によって結果は変わりません。したがって、record_key の秘密部分を保持せずに復号キーを回復できます。
2 つの公開キーからこの秘密を取得することは事実上不可能です。どちらのルートにも対応する秘密キーが必要です。公開ポイントでは、それらを生成した非公開の数値を効率的に回復することはできません。この数学的トリックが金庫のセキュリティを支えています。しかし、誰かが私たちに鍵が取り付けられた別の保管庫を提供することを阻止するものではありません。
これで、暗号文を record_key の公開部分と一緒に保存できるようになりました。 record_key のプライベート部分と使用された message_key は、今後の読み取りには必要なくなります。その後、アプリケーションは master_key と保存されている record_key の公開部分から復号キーを回復します。したがって、1 つの永続的なボールト シークレットで、個別に暗号化された多くの値を復号化でき、それぞれの値は必要に応じて回復される独自の message_key を持ちます。
テキストを隠すだけでは十分ではありません。テキストを読むときに改ざんを検出する必要もあります。したがって、暗号化では、暗号化されたデータと一緒にチェックされる短い値である認証タグが追加されます。チェックが失敗した場合、アプリケーションは結果を拒否します。正常に復号化されたパスワードとしてランダムなテキストまたは変更されたテキストを表示してはなりません。
非対称ペアが共有秘密の確立に役立ち、message_key がコンテンツを暗号化する構造について説明しました。これは Hybrid Public Key Encryption または HPKE と呼ばれ、RFC 9180 で指定されます。 Confidex は、X25519 を使用してシークレットに同意し、HKDF-SHA256 を使用してキーを導出し、ChaCha20-Poly1305 を使用して整合性チェックによる暗号化を行います。ここで、HKDF は、record_key と master_key のペアを使用して合意された共有秘密を取得します。これは、Ed25519 seed の導出とは別のジョブです。 RFC 5869 は HKDF について説明します。
次の暗号化のために、アプリケーションは新しい record_key と新しい HPKE コンテキストを作成します。 message_key と nonce の組み合わせを再利用すると、暗号の保護が破られます。 Confidex では、キーと nonce が HPKE 内で導出され、1 つのメッセージに使用され、その後コンテキストが破棄されます。
ChaCha20 自体は 256 ビットの message_key を使用します。直接攻撃の感覚をつかむために、架空のスーパーコンピューターが 1 秒あたり 10¹⁸ 個の鍵候補を調べると想像してください。 2²⁵⁶ 個の可能なキーを検索するには、約 1.8 × 10⁵¹ 年かかります。 RFC 8439 では、アルゴリズムとその分析について説明します。
記録
幸せですか?
パルプフィクション
パスワードは保存しましたが、通常、Web サイトにログインするにはユーザー名とアドレスも必要です。場合によっては、その横にメモが必要になることがあります。これらの値を別々に保存すると、アプリケーションはそれらが一緒に属していることを何らかの方法で記憶する必要があります。これらの「書類」を封筒に入れてみましょう。フィールドを 1 つのコレクションに結合し、それを record と呼びます。
レコードにはフィールドを任意に組み合わせて含めることができます。アプリケーションはそれらを 1 つのパケットに組み立て、圧縮し、今説明した構造を使用して全体を暗号化します。フィールドの値、名前、タイプ、タグはすべて保護されます。 Web サイトのアドレスを変更すると、パスワードは同じでも、レコード全体の新しいバージョンが作成され、新しい record_key で暗号化されます。
ハッシュチェーン
あなたのプロジェクトではブロックチェーンを使用すべきですか? →いいえ
XKCD2267
secret_key を知っている人は、保管庫用の暗号化されたレコードを準備できます。それが私たちのものとして受け入れられることを避けるために、アプリケーションはすべてのレコード バージョンに signing_key で署名します。このキーがなければ、部外者はサーバーや他のデバイスが受け入れる署名付きのバージョンを作成できません。
暗号化されたバージョンには、どのボールトとレコードに属しているか、いつ変更されたか、どのバージョンよりも前のバージョンであるかなどの付随情報が必要です。これによりパブリック ヘッダーが形成されます。パスワードは含まれていませんが、それでも保護する必要があります。そうしないと、誰かが暗号文に触れることなくオブジェクトの所有権と関係を変更する可能性があります。アプリケーションは、暗号文とともに正確なヘッダー バイトに署名します。署名データを変更すると署名が無効になります。
サーバーは、密封された小包を保管する倉庫に似ています。ヘッダー、暗号化されたコンテンツ、署名は認識されますが、内部のパスワードは認識されず、受信者が市内に存在することがわかります。 signing_key のピン留めされた公開部分に対して署名をチェックし、新しいバージョンを受け入れるかどうかを決定します。マスター パスワードも secret_key も秘密キーも必要ありません。クライアントは読み取り時に同じ署名をチェックし、復号化中に HPKE 認証タグを使用してコンテンツとヘッダー間のバインディングを再度チェックします。
署名はバージョン間のリンクも保護します。正確なヘッダーと暗号文バイトの SHA-256 ハッシュがバージョンの識別子になります。次のバージョンのヘッダーには、前のバージョンの識別子が含まれます。 Web サイトのパスワードを変更すると、アプリケーションはこのリンクを使用して新しいオブジェクトを作成し、署名します。古いオブジェクトとその署名は変更されません。したがって、新しいバージョンの署名を保持しながら以前のバージョンを置き換えると失敗します。 2 つのデバイスが独立して変更を行った場合、両方のブランチが存続します。マージされたバージョンでは両方を参照できます。
ブロックチェーンを覚えていますか?それはあります:-)。
パブリックヘッダーを使用すると、復号化せずにリンクをチェックできますが、一部の情報は表示されたままになります。サーバーは、識別子、バージョン リンク、クライアントから提供された変更時刻、フィールドの数、および各フィールド値のエンコードされたサイズを知っています。暗号化により、この構造情報ではなく、レコードの内容が隠蔽されます。
同期
「データ漏洩ではありません。無料で配布されたバックアップです。」
インターネットミーム。
複数のデバイスで作業するには、デバイス間でロックを転送し、履歴を記録する必要があります。オブジェクトの形式は変更されません。ローカルに保存されているのと同じ暗号化されたバイトと署名が転送されます。
手動転送の場合、アプリケーションは、ロック、ローカルに保存されているすべての暗号化されたレコードのバージョン、および署名付きファイル リストを含む ZIP アーカイブを作成します。パスワードも secret_key も含まれません。別のデバイスでは、アプリケーションは最初にロックを開き、次に署名を確認し、レコードを復号化して、ローカルのボルトのコピーを復元します。インポートは、このボールトがまだ存在しないデバイスを対象としています。履歴を既存のコピーとマージしません。
同期サーバーは、作業コピー間で変更を交換します。最初のアップロード時に、署名付きロックを受け取り、署名をチェックして、signing_key のパブリック部分からパブリック ボールト アドレスを計算します。次に、このキーとヘッダーのボールト識別子を記憶します。これにより、ボールトがサーバーに登録されます。後続の変更はこの識別子と一致し、同じキーからの署名を運ぶ必要があります。
新しいデバイスにはボールト アドレスと secret_key が必要なので、アプリケーションはそれらをチェックサム付きの転送コードに結合します。ユーザーはこのコードをデバイスに転送し、マスター パスワードを別途入力します。パブリック アドレスのみがサーバーに送信されます。
ダウンロードされたロックを開いた後、アプリケーションは、回復されたキーの公開部分と secret_key およびヘッダーを比較し、計算されたアドレスを転送コードと照合します。次に、レコードをチェックするために、signing_key の検証済みの公開部分を保存します。
同期中に、アプリケーションは欠落しているバージョンを取得し、その変更をアップロードします。サーバーは署名と以前のバージョンへのリンクをチェックし、受け入れられたオブジェクトを保存して他のデバイスに渡します。受信したデータの確認、復号化、競合の解決はすべてユーザーのデバイス上で行われます。
登録には、名前、電子メール アドレス、または別のパスワードは必要ありません。サーバーは、パブリック アドレス、署名検証キー、パブリック ヘッダー、オブジェクト サイズ、およびバージョン リンクを確認します。 IP アドレスとリクエスト時間も表示されます。マスター パスワード、secret_key、秘密キー、および平文の記録コンテンツはユーザーのデバイスに残ります。パブリック アドレスを知っている人は誰でも暗号化されたオブジェクトをダウンロードできますが、それを読み取るにはユーザーの秘密が必要です。
これらのチェックでは、偽造オブジェクトは検出されますが、サーバーが完全な履歴と最新の状態を示したことは証明されません。クライアントが見たことのないブランチを非表示にしたり、一貫した古いデータセットを返したりすることができます。署名は、サーバーの応答の完全性ではなく、個々のオブジェクトの信頼性を確認します。ユーザーは別のデバイスで変更が欠落していることに気づくかもしれませんが、一貫した古い履歴があれば hash-chain の検証に合格します。
マスターパスワードの変更
では、法王はパスワードを変更していただけませんか?
ハッカーズ (1995)
マスターパスワードを変更すると、ロックのみが再暗号化されます。内部の master_key は変わりません。したがって、レコード、その署名、secret_key、およびボールトのアドレスは変更されません。新しいロックは同じ signing_key で署名されます。
古いロックのコピーは、古いパスワードと同じ secret_key で開きます。パスワードを変更しても、すでに公開されているキーは取り消されません。
全体像
ロックは master_key を保護し、message_key はレコードを暗号化し、signing_key は署名を作成します。 ZIP はアーカイブされ、サーバーは署名付きの暗号化されたオブジェクトを受け取ります。