天天看點

[譯]再談如何安全地在 Android 中存儲令牌

<b>本文講的是[譯]再談如何安全地在 Android 中存儲令牌,</b>

作為本文的序言,我想對讀者做一個簡短的聲明。下面的引言對本文的後續内容而言十分重要。

沒有絕對的安全。所謂的安全是指利用一系列措施的堆積群組合,來試圖延緩必然發生的事情。

用戶端應用與服務端的互動是最常見的場景之一。資料交換時的敏感度差别很大,并且登入請求、使用者資料更改請求等之間交換的資料類型也變化多樣。

當你使用 SSL 連接配接時(也就是當你看到浏覽器上有一個小鎖時),這意味着你與伺服器之間的連接配接被加密了。理論上講,沒有什麼能夠通路到你請求裡的資訊(*)

(*)我說過絕對的安全不存在吧?SSL 連接配接仍然可以被攻破。本文不打算提供所有可能的攻擊手段清單,隻想讓你了解幾種攻擊的可能性。比如,可以僞造 SSL 證書,或者進行中間人攻擊。

我們繼續。假設用戶端正在通過加密的 SSL 通道與背景連結,它們在愉快的交換有用的資料,執行業務邏輯。但是我們還想提供一個額外的安全層。

接下來要采取的措施是在通信中使用授權令牌或 API 密鑰。當背景收到一個請求時,我們如何判斷該請求是來自認證的用戶端而不是任意一個想要擷取我們 API 資料的家夥?背景會檢查該用戶端是否提供了一個有效的 API 密鑰。如果密鑰有效,則執行請求操作,否則拒絕該請求并根據業務需求采取一些措施(當出現此情況時,我一般會紀錄他們的 IP 位址和用戶端 ID,看一下他們的通路頻率。如果頻率高于我的忍受範圍,我會考慮禁止并觀察一下這個無禮的家夥想要得到什麼)。

讓我們從頭開始建構我們的城堡吧。在我們的應用中,添加一個叫做 API_KEY 的變量,該變量會自動注入到每次的請求(如果是 Android 應用,可能會是你的 Retrofit 用戶端)中。

很好,這樣可以幫助我們鑒定用戶端。但問題在于它本身并沒有提供一個十分有效的安全保證。

是的,我知道。這并不能保證是一個有效的令牌,是以我們仍然需要通過一個精确的驗證來決定如何找到那個字元串,和它是否可以用來通過驗證。但是你知道我要表達什麼:這通常隻是時間和資源的問題。

我将會更新我提出的初始模型,不斷疊代它,以提供更安全的替代方案。我們假設有兩個函數分别負責加密和解密資料:

代碼沒啥好說的。這兩個函數會使用一個密鑰值和一個被用來編/解碼的字元串作為入參。它們會傳回相應的加密或解密過的字元串。我們會用如下方式調用它們:

猜到為什麼要這麼做了嗎?是的,我們可以根據需求來加/解密令牌。這就為我們提供了一個額外的安全層:當代碼混淆後,尋找令牌不再像執行字元串搜尋和檢查字元串周圍的環境那樣簡單了。但是,你能指出還有一個需要解決的問題嗎?

找到了嗎?

如果還沒找到就多花點時間。

是的。我們仍然有一個加密密鑰以字元串的形式存儲。雖然這種隐晦的做法增加了更多的安全層,但不管這個令牌是用于加密或它本身就是一個令牌,我們仍然有一個以明文形式存在的令牌。

現在,我們将使用 NDK 來繼續疊代我們的安全機制。

NDK 允許我們在 Android 代碼中通路 C++ 代碼庫。首先我們來想一下要做什麼。我們可以在一個 C++ 函數中存放 API 密鑰或者敏感資料。該函數可以在之後的代碼中調用,避免了在 Java 檔案中存儲字元串。這就提供了一個自動的保護機制來防止反編譯技術。

C++ 函數如下:

在 Java 代碼中調用它也很簡單:

在加/解密函數中會這樣調用:

此時我們生成 APK,混淆它,然後反編譯并嘗試在原生函數 getSecretKey() 中查找該字元串,無法找到!勝利了嗎?

并沒有!NDK 代碼其實也可以被反彙編和檢查。隻是難度較高,需要更進階的工具和技術。雖然這樣可以擺脫掉 95% 的腳本小子,但一個有充足資源和動機的團隊讓然可以拿到令牌。還記得這句話嗎?

那麼,我們要使用哪種方案來避免背景與用戶端的通信被标記呢?

在裝置上實時生成密鑰。

你的裝置不需要存儲任何形式的密鑰并處理各種保護字元串字面值的麻煩!這是在服務中用到的非常古老的技術,比如遠端密鑰驗證。

用戶端知道有個函數會傳回一個密鑰。

背景知道在用戶端中實作的那個函數。

用戶端通過該函數生成一個密鑰,并發送到伺服器上。

伺服器驗證密鑰,并根據請求執行相應的操作。

抓到重點了嗎?為什麼不使用傳回三個随機素數( 1~100 之間)之和的函數來代替傳回一個字元串(很容易被識别)的原生函數呢?或者拿到當天的 UNIX 時間,然後給每一位數字加 1?通過裝置的一些上下文相關資訊(如正在使用的記憶體量)來提供一個更高程度的熵值?

上面這段包含了一些想法,希望讀者們已經得到重點了。

絕對的安全是不存在的。

多種保護手段的結合是達到高安全度的關鍵。

不要在代碼中存儲字元串明文。

使用 NDK 來建立自生成的密鑰。

還記得開頭的那段話吧?

我想再強調一次,你的目标是盡可能的保護你的代碼,同時不要忘記 100% 的安全是不可能的。但是,如果你能保證解密你代碼中任意的敏感資訊都需要耗費大量的資源,你就能安心睡覺啦。

愉快的編碼吧!

<b></b>

<b>原文釋出時間為:2017年6月08日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>

繼續閱讀