Hash

為離線應用程序生成安全的短啟動碼

  • June 2, 2018

我的申請如下

  • 使用者有一個硬體系統(具有唯一 ID),它沒有連接但有一個用於輸入的鍵盤。
  • 為了使硬體能夠執行,使用者必須去中央機構並獲得一個程式碼(7-8位)以換取金錢
  • 使用者通過鍵盤輸入此程式碼,系統執行一段時間後,使用者必須重複該過程。

要求

  • 程式碼必須是簡短的數字(7-8 位)
  • 該程式碼應僅適用於特定硬體(每個使用者唯一)
  • 一開始為了簡單起見,我們可以假設生成的每個程式碼都會在相同的時間內執行硬體(而不是給出硬體應該執行時間的程式碼)
  • 標準庫的可用性來實現這一點。

這是我到目前為止所做的

  • 我想到了使用ISO 7064 Mod 97,10公式生成程式碼(在中央權威機構中)並在硬體上使用相同的公式來查看它是否有效。基於這篇文章。然而,這意味著相同的程式碼不是硬體系統獨有的。這只是一個錯誤檢測算法。
  • 我可以使用上面的公式生成程式碼並將 RSA 與數字證書一起使用。但是,輸出會不會太長正確?此外,這意味著中央機構必須有權訪問所有硬體系統的公鑰。

讓我們檢查一下問題中的命題:

  • 我曾想過使用 ISO 7064 Mod 97,10 公式

,該公式不提供針對確定的對手的加密保護。僅ISO/IEC 7064

指定一組檢查字元系統,能夠保護字元串免受人們複製或鍵入數據時發生的錯誤

  • 我可以使用上面的公式生成程式碼並將 RSA 與數字證書一起使用。但是,輸出會不會太長正確?

是的。RSA 簽名至少大致為公共模數的寬度,即現代安全性 2048 位或 $ 2048\log2/\log10\approx617 $ 十進製字元。這樣輸入太多了。

  • 此外,這意味著中央機構必須有權訪問所有硬體系統的公鑰。

不可以。驗證 RSA 簽名需要有權生成它的機構的公鑰。忽略大小問題,將需要一個公鑰/私鑰對,私鑰由簽名機構持有,所有設備中的公鑰相同。設備可以在簽名數據中辨識它們的序列號。


雖然有比 RSA 更緊湊的簽名系統,但所有簽名系統都產生的簽名至少比目標“7-8 位”高 10 倍。因此我們不能使用公鑰密碼術。

標準選項將是對稱加密,每個設備都有一個密鑰。標準做法是,設備密鑰源自發行機構已知的主密鑰,以及設備的序列號,通過某種密鑰派生功能(智能卡行業自 1980 年代以來就以多樣化的名義這樣做)。必須保留一個主密鑰,但提取設備的密鑰不會危及其他密鑰。HMAC -SHA-256 將是一種合適的推導方法。 $ \mathsf{DeviceKey}=\operatorname{HMAC}(\mathsf{MasterKey},\mathsf{SerialNumber}) $ 會做。

解鎖碼可以是某種索引的消息驗證碼,在每次重新載入時遞增並由設備(和授權的發射系統)保存在永久儲存器中,並被截斷。這可能是十進製表達式 $ \mathsf{UnlockCode}=\operatorname{HMAC}(\mathsf{DeviceKey},\mathsf{Index})\bmod10^8 $ .

正常操作是權威收款,發現 $ \mathsf{Index} $ 為了 $ \mathsf{SerialNumber} $ 在其數據庫中,計算 $ \mathsf{DeviceKey} $ 然後 $ \mathsf{UnlockCode} $ , 增加 $ \mathsf{Index} $ 數據庫中的此設備。設備檢查輸入的內容 $ \mathsf{UnlockCode} $ ,如果有匹配啟動並增加其 $ \mathsf{Index} $ 在永久的記憶中。

一些問題需要解決:

  • 這 $ \mathsf{MasterKey} $ 和 $ \mathsf{DeviceKey} $ s 是敏感目標,在當局和製造現場可能容易受到攻擊。HSM 或智能卡可能會有所幫助。特別是,一張適用於有限數量的智能卡 $ \mathsf{UnlockCode} $ s 可能是當局職員真正使用的。
  • 應該有顯著的冗餘 $ \mathsf{SerialNumber} $ 或任何標識設備以避免當局的文書錯誤;ISO 7064 可能就足夠了。
  • 攻擊者可能會隨機嘗試程式碼來解鎖設備,可能會自動解鎖。對策是條目之間的延遲,可能會在多次連續錯誤後增加。也有可能有兩個程式碼,一個短的,限制 3 次嘗試(6 位就可以),一個長的(比如 12 位)用作備份(類似於手機 SIM 中的 PIN,PUK作為備份)。
  • 該設備應具有針對各種威脅的適當緩解措施:未經授權使用繞過付款, $ \mathsf{DeviceKey} $ 提取、設置 $ \mathsf{DeviceKey} $ 到一個已知值,設置 $ \mathsf{Index} $ 到較早的值,允許重用較早的程式碼,通過電池移除或其他干擾,側通道重置上述條目之間的延遲.. 一個經典的錯誤是將鍵入的程式碼與 $ \mathsf{UnlockCode} $ 使用strcmp或類似的庫/語言功能,通過時間洩露第一個不匹配的位置,這可能在不打開盒子的情況下可以觀察到,例如由於 LED 或鍵盤掃描 EMI,允許在大約 40 次嘗試中找到一個 8 位程式碼而不是 5000 萬。安全工程不容易!
  • 使用者將失去或暫時放錯程式碼,而不是費心要求重複,而是支付一個新的。因此,設備應該提前接受一些程式碼。然後最好接受稍微亂序的程式碼;這使算法複雜化,特別是對於短程式碼(因為可接受視窗中的所有程式碼都需要不同)。一個簡單的選項,如果一個視窗 $ w $ 必須接受的程式碼是 $ \mathsf{UnlockCode}=w(\operatorname{HMAC}(\mathsf{DeviceKey},\mathsf{Index})\bmod\lfloor10^8/w\rfloor)+(\mathsf{Index}\bmod w) $ .
  • 更一般地,可以在程式碼中編碼輔助資訊(例如重新載入量)。雖然我們這樣做了,但程式碼的長度可以是可變的,對於高價值程式碼,可能會增加一位或兩位額外的數字,以提高安全性。

注意:我沒有看到格式保留加密在這裡真的有幫助,即使有輔助資訊要傳達,因為它的機密性似乎比有用的功能更令人討厭。

讓我們先了解一些基本的數學知識。如果您有 8 位數字,您的啟動/驗證碼最多只能編碼 $ 8 \cdot log_2(10) $ 位的資訊。(大約 26.5)所以公鑰簽名是不可能的。其次,成功的機率比較高。( $ 10^{-8} $ 或 1 億分之一)。手動輸入的數字很多,但是除非我們放寬假設和要求,否則成功的機會太高而不能認為是安全的。

所以這就是我會考慮的:

  • 我們會相信廣大使用者是誠實的。如果某些使用者“盜版”啟動碼,這不是什麼大問題。(我個人在原則上反對“DRM”,因為它具有侵入性、危及使用者安全或阻止專家對已編譯程式碼進行分析。)防止訪問秘密資訊並同時防止篡改是很困難的。
  • 相反,我們會說我們假設大多數使用者沒有能力訪問他們設備上的對稱密鑰。也許有一個硬體安全模組。也許法律和道德不鼓勵“偷竊”啟動。也許人們只是願意支付和公平競爭。
  • 此外,使用者不會或不能刪除本地記錄、更改系統時鐘或修改程式碼。如果使用者破解應用程序的二進製文件以用 No-OP 替換“如果啟動失敗轉到 X”之類的內容,那麼保持對稱密鑰的秘密將無濟於事。

因此,首先您需要對猜測進行速率限制。就像線上暴力破解密碼猜測一樣,如果一個人未能通過太多啟動碼輸入,您可以暫時鎖定他的猜測。離線暴力破解不是您可以阻止的。(如果一個人從他們設備中的記憶體中讀取一個密鑰並在個人電腦上強行啟動一個啟動碼,那麼他們可以確認是否有潛在的啟動碼,並等到他們找到一個他們知道是正確的啟動碼後再嘗試啟動。)但它沒有如果我們接受列出的假設,這在理論上是可能的。

為防止雙重支出,您需要保存使用者已使用**的啟動碼列表。**我們假設使用者不會干預此列表。如果您有一個應用程序,使用者可以解除安裝並重新安裝而不恢復使用的程式碼列表,那麼他們可以重複使用程式碼,但會失去所有其他數據。您仍然可以在所做假設的範圍內防止這種情況發生。您可以在重新安裝應用程序後生成一個新的隨機使用者 ID,而不是從硬體 ID 派生使用者 ID。由於這些已用程式碼僅與舊使用者 ID 相關聯,因此無法以這種方式重複使用。

接下來,您必須擔心猜測任何未使用程式碼的可能性。不只是其中之一。如果您不允許舊的購買但未使用的啟動碼過期,那麼您只能向使用者發放有限數量的啟動碼。這個數字越大,人們就越有可能猜出正確的啟動碼。

選項 1:根據我讀到的任天堂對一些免費增值遊戲所做的事情,一種解決方案是設置您可以購買的最大數量。在使用者為正常的完整遊戲花費等量的金錢後,所有免費增值的付費遊戲功能都將被永久無限資源所取代。這樣做的原因大概是為了維護消費者的尊重,並防止父母抱怨他們的孩子在遊戲購買上花費了無數美元。這具有向客戶表示善意的雙重用途(有人可能會預先花費最大的錢來支持您的產品)並保持猜對正確啟動碼的可能性較低

選項 2:讓購買的啟動碼過期。所以現在您驗證啟動碼對於使用者是正確的並且對於目前日期是正確的。

加密實現:

對於選項 1:我會提前生成有限數量的啟動碼,並創建一個將使用者 ID 與啟動碼相關聯的數據庫。當我向客戶出售他們的啟動碼時,我將該程式碼標記為已使用,因此我不會再向他們出售相同的啟動碼。例如,這需要在銷售點對設備進行物理訪問以編寫啟動碼,或線上註冊(以後無需訪問網際網路即可使用啟動碼),或者您在銷售硬體之前生成隨機碼,並且將它們記錄在伺服器數據庫中並同時記錄到設備上。如果您想進一步隱藏啟動碼,您可以儲存啟動碼的散列(拉伸或不拉伸)。這並不會降低程式碼的可猜測性,因為 8 位數字不能包含足夠的熵來使猜測變得不切實際。

如果您不想儲存任何數據,或者不想使用啟動碼程式或註冊。您可以改為從客戶提供給您的使用者 ID 中派生加密密鑰,並使用格式保留加密生成啟動碼。使用者說“我的 ID 是 x,我想購買啟動碼 n”。伺服器使用從 x 派生的密鑰加密 n,將結果轉換為十進制,並將其提供給使用者。使用者將十進制程式碼輸入他們的設備,他們的設備從它的使用者 ID 派生相同的密鑰,並檢查它是否解密為儲存在其啟動計數器中的數字,如果明文和啟動計數器匹配,則解鎖某些功能並增加啟動計數器。由於格式保留加密在用於輸入/輸出的任何集合上都是雙射的,因此只有一個啟動碼將映射到 {0, 1, 2, 3, …} 中的數字。

對於選項 2:我會使用**基於時間的一次性密碼**算法。您的案例在安全性方面與一次性密碼的情況基本相同。伺服器和使用者設備需要持有相同的密鑰,每個使用者都需要自己的密鑰。這樣一來,Alice 的一次性密碼與 Bob 的一次性密碼不同,因此他們不能共享相同的啟動碼。使用基於 Web 的身份驗證,您可以將計算相同一次性密碼的周期長度(從密鑰和目前時間)設置為幾秒鐘。然後在幾個週期的視窗內檢查時鐘差異、使用者輸入延遲和通信時間。您需要將周期設置得更長,因為使用者 ID -> 儲存 -> 啟動碼 -> 設備上的程式碼輸入的傳輸實際上是一個非常慢的通信通道。

請記住,這兩種方法都需要在客戶端儲存已用過的密鑰(或至少是選項 1 的計數器)。您還需要相信使用者也沒有破解/黑客/盜版,但即使您可以使用其他一些使用者訪問控制系統來防止這種情況,也有人能夠破解您的應用程序可以通過修補結帳來禁用啟動碼的需要的程式碼二進制。

PS我已經寫了好幾次這篇文章了。如果某些部分似乎不清楚,請告訴我。對於選項 1,還有 TOTP 的兄弟HOTP。我已經分心了很多次,以至於我不記得為什麼我沒有包括它。

還有一件事。對於 TOTP 和 HOTP,您需要保留已用過的啟動碼列表,但還需要注意重複碼。您可以改為儲存程式碼加上計數器值或程式碼加上時間。如果您不考慮這一點,那麼在您出售訪問程式碼後,就會有一個 $ 10^8 $ 儘管支付了兩次相同的啟動碼,但他們的程式碼可能會失敗。

引用自:https://crypto.stackexchange.com/questions/59720