Confidex
章節

伺服器如何儲存和同步我們的秘密而不持有密鑰來讀取它們?我們將了解 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 中,它保留在使用者手中。它也是打開鎖所需的第二個秘密,因此它保留在本地並且不會透過網路發送。就其本身而言,secret_key 允許某人加密保管庫的數據,但不能解密它或代表其簽署更改。

X25519 是一種以 master_key 為輸入, 計算 secret_key 的演算法. master_key 無法在現實可行的計算量內僅根據 secret_key 還原. 即使是一台每秒執行 10^18 次曲線運算的假想超級電腦, 進行經典攻擊也平均需要約 2.3 兆年 (SafeCurves 估算).

public bytes Random bytes32 bytes master_key secret_key public bytes Random bytes32 bytes master_key secret_key
從隨機位元組到加密金鑰。

第二對:簽名

唯一原創的是簽章

如果可以的話來找我

第一對有助於隱藏內容。我們現在需要一種方法來確認保管庫所有者授權進行更改。伺服器也必須能夠檢查簽名,因此我們需要向其發送公共驗證金鑰。第一對的公共部分是 secret_key,伺服器不得接收該部分。因此,我們為簽名建立第二個永久金鑰對。

我們沒有儲存另一個獨立的秘密,而是從 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,因此我們可以將其發送到伺服器進行簽名驗證。

public bytes HKDF-SHA256keymaster_keyinfo"confidex/vault/ed25519/v1" signing_key Signature verification public bytes HKDF-SHA256keymaster_keyinfo"confidex/vault/ed25519/v1" signing_key Signature verification
從同一主金鑰派生的單獨簽署金鑰。

什麼是鎖定文件,它有什麼作用?

打開吊艙門,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 記憶體和三次的 Argon2id。密碼處理也使用隨機鹽。鹽並不秘密:它使相同的密碼在不同的鎖中產生不同的結果。

如果 secret_key 已知,則密碼長度會大幅改變攻擊的規模。 5 個隨機小寫拉丁字母的組合大約有 1200 萬種,7 個隨機小寫拉丁字母的組合大約有 80 億個,12 個字母的組合大約有 9.5 × 10¹⁶個。從 7 個字母到 12 個字母,搜尋空間增加了近 1,200 萬倍。如果在給定機器上搜尋 7 個字母平均需要一天,那麼搜尋 5 個字母大約需要兩分鐘,搜尋 12 個字母大約需要 33,000 年。

為了從 Argon2id 結果和 secret_key 中取得一個鍵,我們使用前面介紹的 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 的公共部分。這兩個密鑰產生相同的共享秘密。從數學上講,兩條路線都以不同的順序將曲線上的相同初始點乘以兩個私人數字。該順序不會改變結果。因此,我們可以在不保留 record_key 的私有部分的情況下恢復解密金鑰。

從兩個公鑰中取得這個秘密實際上是不可能的:任一路由都需要對應的私鑰。公共點不允許我們有效地恢復產生它們的私人號碼。這個數學技巧支撐著金庫的安全。但這並不能阻止有人給我們一個不同的金庫,上面掛著我們的鎖。

我們現在可以將密文與 record_key 的公共部分一起儲存。以後的讀取不再需要 record_key 的私有部分和使用的 message_key。隨後,應用程式從 master_key 恢復解密金鑰以及 record_key 儲存的公共部分。因此,一個永久保管庫秘密可以解密許多單獨加密的值,每個值都有自己的 message_key 在需要時恢復。

隱藏文字是不夠的:我們還必須在閱讀時偵測到篡改。因此,加密添加了一個身份驗證標籤,即與加密資料一起檢查的短值。如果檢查失敗,應用程式將拒絕結果。它不得將隨機或更改的文字顯示為成功解密的密碼。

public bytes Fresh random bytes record_key secret_key Shared secret Derive message key message_key Encrypted passwordpacket public bytes Fresh random bytes record_key secret_key Shared secret Derive message key message_key Encrypted passwordpacket
每次加密都有一個新的金鑰對。

我們描述了一種結構,其中非對稱對有助於建立共享秘密,而 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內部派生,用於一則訊息,然後丟棄上下文。

ChaCha20 本身使用 256 位元的 message_key。為了了解直接攻擊的感覺,想像一下一台虛構的超級電腦每秒測試 10¹⁸ 個候選金鑰。搜尋 2²⁵⁶ 可能的鍵大約需要 1.8 × 10⁵¹ 年。 RFC 8439 描述了演算法及其分析。

一個記錄

我們幸福嗎?

低俗小說

我們已經保存了密碼,但登入網站通常還需要使用者名稱和地址。有時我們需要在它們旁邊放一張紙條。如果我們單獨儲存這些值,應用程式必須以某種方式記住它們屬於一起。讓我們把這些「文件」裝進信封裡。我們將這些欄位合併到一個集合中,並將其稱為 record。

記錄可以包含欄位的任意組合。應用程式將它們組裝成一個資料包,對其進行壓縮並使用我們剛剛描述的結構對整個資料包進行加密。欄位值、名稱、類型和標籤都受到保護。變更網站位址,即使保持相同的密碼,也會建立整個記錄的新版本,並使用新的 record_key 進行加密。

哈希鏈

您的專案應該使用區塊鏈嗎? → 不

XKCD 2267

知道 secret_key 的人可以為我們的保管庫準備加密記錄。為了避免接受它作為我們的,應用程式使用 signing_key 簽署每個記錄版本。如果沒有此金鑰,外部人員無法建立具有伺服器和其他裝置可接受的簽章的版本。

加密版本需要一些附帶資訊:它屬於哪個保管庫和記錄、何時更改以及它之前的版本。這形成了公共標頭。它不包含密碼,但仍然需要保護:否則有人可以在不接觸密文的情況下更改物件的所有權和關係。應用程式將確切的標頭位元組與密文一起簽署。更改簽名資料會使簽章無效。

伺服器就像一個密封包裹的倉庫。它看到了標頭、加密內容和簽名,但看不到裡面的密碼,並且它知道收件人存在於該城市。它根據 signing_key 的固定公共部分檢查簽名,並決定是否接受新版本。它既不需要主密碼,也不需要 secret_key 或任何私鑰。讀取時,用戶端檢查相同的簽名,然後在解密期間使用 HPKE 驗證標記再次檢查內容和標頭之間的綁定。

簽名還保護版本之間的連結。確切的標頭和密文字節的 SHA-256 雜湊成為版本的識別碼。下一個版本的標頭包含上一個版本的識別碼。當我們更改網站密碼時,應用程式會使用此連結建立並簽署新物件。舊物件及其簽名保持不變。因此,在保留新版本簽名的同時替換前一個版本將會失敗。如果兩個裝置進行獨立更改,則兩個分支都會存活;合併版本可以引用兩者。

還記得區塊鏈嗎?就在那裡:-)。

公共標頭允許我們在不解密的情況下檢查鏈接,但某些資訊仍然可見。伺服器知道客戶端提供的標識符、版本連結、更改時間、字段數量以及每個字段值的編碼大小。加密隱藏了記錄的內容,而不是結構資訊。

Header andparent IDs Encrypted recordpacket Sign revision signing_key Signed revision Header andparent IDs Encrypted recordpacket Sign revision signing_key Signed revision
標頭和密文一起簽名。

同步

「不是資料洩露,免費的分散式備份。」

網路迷因。

為了在多個設備上工作,我們需要在它們之間傳輸鎖定和記錄歷史記錄。物件格式不會改變:我們傳輸本地儲存的相同加密位元組和簽名。

對於手動傳輸,應用程式會建立一個包含鎖定、所有本機儲存的加密記錄版本以及簽署檔案清單的 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 檔案和伺服器接收帶有簽署的加密物件。

public bytes public bytes public bytes master_key Fresh randombytes secret_key record_key Master password wrapping_key signing_key message_key lock Encrypted records Signed lockand records Signatureverification ZIP archive /sync server public bytes public bytes public bytes master_key Fresh randombytes secret_key record_key Master password wrapping_key signing_key message_key lock Encrypted records Signed lockand records Signatureverification ZIP archive /sync server
金鑰、它們保護的內容以及可以分享的內容。