這個用於屏蔽 DB id 的協議是否合理?
問題陳述
我不想公開我的內部數據庫 id(或其他內部私有 id),以便更難濫用它們,例如猜測序列的下一個 id 或類似的。為我的數據庫中的每個條目添加一個具有公共、不可猜測的 UUID 的新欄位對我來說是不可行的。因此,我需要一個可以(可逆地)掩蓋我的身份的協議。
所需屬性
- 序列中的 ID 在被屏蔽時應該難以猜測和辨識
- 偽裝時,ID 應該很難偽造
- 單個遮罩 id 的妥協不應使包含其他遮罩 id 變得更容易
- 屏蔽相同的 ID 應該會隨機產生不同的輸出
- 遮罩 ID 不應太長
- 掩蔽應該相當快
目前最先進的
有一個流行的庫試圖解決這個問題:https ://hashids.org/這裡有一個 Java 實現。HashIds 基本上使用特定字母對 id 進行編碼,該字母取決於呼叫者提供的“鹽”。此外,他們嘗試省略某些字母,以便將生成帶有英語詛咒詞的 id 的機會降到最低。ID是完全確定的,即。相同的數字和相同的鹽會產生相同的輸出。
新的密碼學方法
我發現所描述的問題很有趣,但對實際上掩蓋 id 的悲慘安全屬性感到失望(公平地說,HashId 從不聲稱不僅僅是混淆)。我的想法可能類似於key wrapping的專用版本。
所以我的方法是:
id .... 8 byte number entropy .... 4, 8, 12, 16 byte random value secret-key .... 16 byte high quality random key assumed to be stored in a safe manner Primitives: AES-CTR(iv, key) HMAC_SHA256(key, data) HKDF-Expand(PRK, info, L) -> OKM
- 為每個
id
創建一個entropy
4 到 16 個字節長的隨機字節數組。- 導出 64 字節密鑰
km
材料HKDF-Expand(secret-key, entropy, 64)
- 將 64 個字節拆分為:
roundSecretKey
=km[0-16]
iv
=km[16-32]
macKey
=km[32-64]
id
用AES-CTR(iv, roundSecretKey)
->加密encryptedId
HMAC_SHA256(macKey, encryptedId | iv )
使用->創建 machmac
- 將 mac 截斷為 4-16 字節 ->
truncated_hmac
- 使用以下命令創建輸出消息:
masked_id = entropy | encryptedId | truncated_hmac
討論
選擇 AES-CTR 作為加密原語是因為:
- 基本上每種程式語言都有它的標準實現
- 是一種流密碼,可以有效地加密小於 16 字節的數據
- AES 可以相當快(並且通常是硬體加速的)
然而,最大的缺點是密鑰 k1 的 AES-CTR 絕不能與 iv1 一起使用超過一次。
HMAC 用於為生成的 id 添加完整性和真實性,以防止偽造。
隨機
entropy
部分確保id1
多次屏蔽很可能會創建不同的屏蔽 id。妥協
保持被屏蔽的 id 短,
entropy
並且hmac
保持短(或非常短取決於配置)。假設最壞的情況:4 字節熵。重複靜脈注射的機會是現實的,但很少。攻擊者不應該能夠解密,因為iv
它roundSecretKey
依賴於secret-key
需要首先暴力破解的秘密。一個選項是切換到 AES-CBC,但這會使最小輸出長度為 16 字節(大多數 id 為 8 或 16 字節)。保持 hmac 小可以更容易偽造消息,但它應該更容易獲取
macKey
.這兩個問題都可以通過將
entropy
和的長度hmac
增加到 16 字節來解決。然而,這會將輸出長度增加到 40 字節。**所以社區的問題是:這個協議是否符合所需的屬性?妥協合理嗎?這基本上是一個健全的協議嗎?**我正在尋找一般回饋、安全問題、可能的改進 - 目前我不是在尋找解決公共 ID 問題的不同方法。
感興趣的讀者更新:這裡是Github 上 id 加密模式的參考實現,這裡有一篇關於它的文章。
您正在尋找的是 64 位長的短位字元串的隨機驗證密碼。您似乎願意接受 64 到 256 位的密文擴展。
- 帶著 $ r $ -bit 隨機化字元串,之後發生碰撞的機率 $ n $ 密文——至少會揭示遮罩 ID 的相等性——充其量是約 $ n^2/2^r $ .
- 和 $ t $ -bit認證擴展,偽造後的機率 $ f $ 嘗試最多以大約為界 $ f/2^t $ .
您可以使用任何您喜歡的隨機驗證密碼,例如NaCl crypto_secretbox_xsalsa20poly1305,它具有 192 位隨機數和 128 位驗證標籤;如果您使用隨機統一選擇的 128 位隨機數(用零填充到 192 位),只要您將自己限制在遠低於 $ 2^{64} $ id,例如將自己限制為十億個 id。您可以使用 AES-GCM,但限制更小,因為 nonce 是 96 位,因此您需要將自己限制在遠低於 $ 2^{48} $ 身份證。當然,這裡的 nonce 碰撞是災難性的。
如果你想用 AES 和 SHA-256 建構它,你可以使用 AES-CBC 和 encrypt-then-MAC 和 HMAC-SHA256 的 128 位截斷。由於輸入是固定長度的,因此不需要填充;實際上,它只是$$ E_{k_1,k_2}(\rho, m) := c \mathbin| \operatorname{HMAC-SHA256-128}{k_2}(c), \quad \text{where} \quad c = \rho \mathbin| \operatorname{AES}{k_1}(\rho \oplus m). $$ 在發生碰撞的情況下 $ \rho $ ,這仍然只洩漏遮罩 id 的相等性。顯然,你可以得出 $ k_1 $ 和 $ k_2 $ 從單個 32 字節主密鑰 $ k $ 使用 HKDF-SHA256。
這是一個非常簡單的方法,它使用單個密鑰對 AES 的單個呼叫來加密 64 位 id $ m $ 使用 64 位隨機化 $ \rho $ 下鍵 $ k $ :$$ E_k(\rho, m) := \rho \mathbin| \operatorname{AES}k(\rho \mathbin| m). $$ 這裡 $ r = t = 64 $ ,所以我們使用 192 位來加密 64 位 id。讓 $ \pi $ 是均勻隨機排列;對於任何偽造企圖 $ \rho_1 \mathbin| c_1, \dots, \rho_f \mathbin| c_f $ , 存在的機率 $ i $ 這樣 $ \pi^{-1}{64}(c_i) = \rho_i $ 由 $ f/2^{64} $ , 在哪裡 $ \pi^{-1}_{64}(c_i) $ 是前 64 位 $ \pi^{-1}(c_i) $ . 因此,任何算法的區分和偽造優勢 $ E_k $ 由 $ (n^2 + f)/2^{64} + \varepsilon $ 在哪裡 $ \varepsilon $ 是均勻隨機排列在 AES 中的最大優勢。如果你屏蔽一百萬個 ID,並且你的頻寬將對手限制在一萬億次偽造嘗試,那麼對手獲勝的機率不到八千分之一。即使發生碰撞 $ \rho $ ,同樣這只會洩露遮罩 ID 的相等性,儘管偽造可能是一個更大的問題。
你描述的方案呢?它比必要的更複雜:例如,您可以取消 IV,而始終使用零。如果您按照您的建議使用 32 位隨機化字元串,那麼您將在幾萬個 id 之後看到高機率的衝突,當這種情況發生時,這不僅會洩漏兩個遮罩 id 的相等性,而且兩個遮罩 ID 的異或。