在開發項目的網絡模塊時,我們為了保證客戶端(Client)和服務端(Server)之間的通信安全,我們會對數據進行加密。
談到網絡通信加密,我們可以說出:對稱加密,非對稱加密,md5單向加密,也能提到RSA,AES128,以及MD5等專業名詞。
下面我們就來說明客戶端(Client)與服務端(Server)的加密通信邏輯。
一、加密相關名詞解析
- RSA:非對稱加密,會產生公鑰和私鑰,公鑰在客戶端,私鑰在服務端。公鑰用於加密,私鑰用於解密。
- AES:對稱加密,直接使用給定的秘鑰加密,使用給定的秘鑰解密。(加密解密使用相同的秘鑰)。
- MD5:一種單向的加密方式,只能加密,不能解密。
- Base64編碼:對字節數組轉換成字符串的一種編碼方式。
二、通信加密的邏輯
2.1 明文傳輸通信邏輯(不安全)
1). 客戶端將要上傳的數據以字典(Map)的方式打包,Post提交給服務器。
2). 服務器接收提交的數據包,通過Key-Value的形式獲取客戶端提交的值,進行處理。
3). 處理結束,將數據以字典(Map)的形式打包,返回給客戶端處理。
2.2 加密傳輸通信邏輯
整體流程:客戶端上傳數據加密 ==> 服務器獲取數據解密 ==> 服務器返回數據加密 ==> 客戶端獲取數據解密。
三、通信請求安全性實現方案
請求安全性主要體現為: 服務器端在接收到請求的時候,要主動鑒別該請求是否有效,是否可接受。
常用方案如下:
3.1 使用 token 已登陸用戶的識別碼
解決問題:
用戶調用接口時,不用每次都帶上用戶名和密碼,避免了頻繁在網絡中傳輸密碼被截獲的風險。
使用場景:
用戶登錄系統時傳入用戶名和密碼,服務器校驗成功之后,根據uuid等參數生成token返回給客戶端,同時把該token和該用戶的對應關系緩存在服務器端。客戶端在后續的請求接口中不用每次都傳入用戶名和密碼,只需要傳入token即可。服務器會根據token確定客戶端的身份。
注意事項:
token可設置生效時間,token失效之后,客戶端重新請求token。
3.2 sign:請求參數的簽名
解決問題:
避免請求參數被惡意修改。保證了請求數據的一致性。
使用場景:
客戶端和服務端約定一個簽名生成算法。客戶端在請求接口之前調用簽名算法,根據參數生成sign值。然后把sign和請求參數一並傳給服務器。服務器收到到參數和簽名之后,根據請求參數,調用簽名算法計算出簽名,然后比較該簽名和客戶端傳過來的簽名是否一致,如果一致,則說明請求參數未被修改過,如果不一致,則說明請求參數被修改過。
3.3 nonce:請求中附帶的隨機數
解決問題:
防止惡意程序重復向服務器重復發送相同的請求。
使用場景:
客服端在向服務器發出請求之前,隨機生成nonce參數。服務器在接收到請求之后,取出nonce參數,然后去緩存中查找是否已存在nonce的值。如果存在,則說明該請求已經收到過,則 拒絕本次請求,如果不存在,則說明首次接收到該請求,正常進行處理。
3.4 timestamp:客服端發送請求的時間戳
timestamp機制一般和nonce組合使用
解決的問題:
防止服務器端緩存nonce數據量過大的問題。當服務器緩存的nonce較多時,每次查找nonce就會耗費大量時間。通過添加請求時間戳,判斷請求時間到服務器接收到請求的時間差是否在有效處理時間內(例如5分鍾),如果在5分鍾之內則進行處理,如果超出五分鍾則拒絕該請求。這樣,服務器端在緩存nonce的時候,可以設置nonce的緩存時間為5分鍾,超出5分鍾之后,自動清除掉緩存中的nonce,這樣就避免了緩存大量nonce的問題。
使用場景:
客服端在發出請求時,附帶timestamp,記錄下當前的請求時間。服務器接收到請求時,取出timestamp,判斷和當前的時間差,如果超出一定的時間(例如5分鍾),則放棄該請求。如果在5分鍾之內,則取出nonce,去緩存中查找nonce,如果已存在則拒絕掉,如果不存在則正常處理。
四、通訊的數據保密性
數據保密性: http請求的數據無論是GET還是POST都可能會被抓包獲取到數據。為了避免用戶的敏感數據被竊取,則需要對數據進行加密處理。
1. AES:對稱加密算法
使用方式:
客服端和服務器端共同確定一個用來加密和解密的秘鑰。然后客服端在請求服務器是通過該秘鑰對數據進行加密,服務器端在接收到請求之后使用該秘鑰對數據進行解密。
優勢:
加密效率高
缺點:
秘鑰需要共享給客戶端,具有泄露的風險
2.RSA:非對稱加密算法
使用方式:
服務器端生成公鑰和私鑰,把私鑰發送給客戶端。客服端在請求服務器是,通過公鑰對數據進行加密。服務器端接收到請求之后,使用私鑰對加密的數據進行解密。
優勢:
不需要共享私鑰,避免了私鑰泄露的風險。
劣勢:
加密效率低,數據量大時較為耗時
五、Android端加密需要注意的問題
平時在開發的時候,盡量避免出現密鑰硬編碼。
開發者一般在做密鑰存儲的時候,主要形式有:
1、密鑰直接明文存在sharedprefs文件中,這是最不安全的。
2、密鑰直接硬編碼在Java代碼中,這很不安全,dex文件很容易被逆向成java代碼。
3、將密鑰分成不同的幾段,有的存儲在文件中、有的存儲在代碼中,最后將他們拼接起來,可以將整個操作寫的很復雜,這因為還是在java層,逆向者只要花點時間,也很容易被逆向。
4、用ndk開發,將密鑰放在so文件,加密解密操作都在so文件里,這從一定程度上提高了的安全性,擋住了一些逆向者,但是有經驗的逆向者還是會使用IDA破解的。
5、在so文件中不存儲密鑰,so文件中對密鑰進行加解密操作,將密鑰加密后的密鑰命名為其他普通文件,存放在assets目錄下或者其他目錄下,接着在so文件里面添加無關代碼(花指令),雖然可以增加靜態分析難度,但是可以使用動態調式的方法,追蹤加密解密函數,也可以查找到密鑰內容。
保證密鑰的安全確是件難事,涉及到密鑰分發,存儲,失效回收,APP防反編譯和防調試,還有風險評估。可以說在設備上安全存儲密鑰這個基本無解,只能選擇增大攻擊者的逆向成本,讓攻擊者知難而退。而要是普通開發者的話,做妥善保護密鑰這些事情這需要耗費很大的心血。
產品設計者或者開發者要明白自己的密鑰是做什么用的,重要程度怎么樣,密鑰被逆向出來會造成什么風險,通過評估APP應用的重要程度來選擇相應的技術方案。一般情況下使用4、5就夠了,盡量避免使用1、2、3的方式保存密鑰。如果需要更高的安全性,建議使用一些知名的安全加固組件服務。