一败潦、加密方案
比較安全的方案應(yīng)該是AES+RSA的加密方式。具體如下圖所示准脂。
為什么要這樣做呢劫扒?
1、RSA是非對稱加密狸膏,公鑰和私鑰分開粟关,且公鑰可以公開,很適合網(wǎng)絡(luò)數(shù)據(jù)傳輸場景。但RSA加密比較慢闷板,據(jù)說比AES慢100倍澎灸,且對加密的數(shù)據(jù)長度也有限制。
2遮晚、AES是對稱加密性昭,加密速度快,安全性高县遣,但密鑰的保存是個問題糜颠,在網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)膱鼍熬秃苋菀子捎诿荑€泄露造成安全隱患
3、所以萧求,AES+RSA結(jié)合才更好其兴,AES加密數(shù)據(jù),且密鑰隨機生成夸政,RSA用對方(服務(wù)器)的公鑰加密隨機生成的AES密鑰元旬。傳輸時要把密文,加密的AES密鑰和自己的公鑰傳給對方(服務(wù)器)守问。對方(服務(wù)器)接到數(shù)據(jù)后匀归,用自己的私鑰解密AES密鑰,再拿AES密鑰解密數(shù)據(jù)得到明文耗帕。這樣就綜合了兩種加密體系的優(yōu)點穆端。
4、除上面說的外仿便,還可以加簽名体啰,即對傳輸?shù)臄?shù)據(jù)(加密前)先做個哈希,然后用自己的RSA私鑰對哈希簽名(對方拿到自己的公鑰可以驗簽)嗽仪,這樣可以驗證傳輸內(nèi)容有沒有被修改過荒勇。
二、加密相關(guān)的一些坑
1钦幔、數(shù)據(jù)類型
就java來說,加密的輸入和輸出都是字節(jié)數(shù)組類型的常柄,也就是二進制數(shù)據(jù)鲤氢,網(wǎng)絡(luò)傳輸或本地保存都需要重新編碼為字符串。推薦使用Base64西潘。Android 有自帶的Base64實現(xiàn)卷玉,flag要選Base64.NO_WRAP,不然末尾會有換行影響服務(wù)端解碼喷市。
Android中Base64加密
//字節(jié)數(shù)組轉(zhuǎn)字符串
String str = Base64.encodeToString(byte_data, Base64.NO_WRAP);
//字符串轉(zhuǎn)字節(jié)數(shù)組
byte[] bytes = Base64.decode(keyStr, Base64.NO_WRAP);
2相种、加密參數(shù)
總而言之,這些不同語言都有實現(xiàn)庫品姓,調(diào)用即可寝并,關(guān)鍵是參數(shù)要一致箫措,具體還需要和后臺聯(lián)調(diào)一下。
rsa加解密的內(nèi)容超長的問題解決
AES算法:
Android端--->"AES/CFB/NOPADDING"
密鑰長度一般128衬潦,256安全性更高
ECB模式不安全斤蔓,使用會有黃色警告。
RSA算法:
密鑰長度=1024已經(jīng)被認為不安全了(RSA 768已于2009年被破解)镀岛,推薦>=2048弦牡。加密的明文長度和密鑰長度是相關(guān)的。
Android端-->"RSA/ECB/PKCS1Padding"
RSA簽名算法:
Android端-->"SHA1withRSA"
試過MD5withRSA漂羊,但是和后臺無法兼容
三驾锰、OkHttp/Retrofit的實現(xiàn)
現(xiàn)在說到網(wǎng)絡(luò)框架,應(yīng)該毫無疑問是Retrofit了走越。上面說的加密方案說到底還是要在網(wǎng)絡(luò)請求框架內(nèi)加上椭豫,怎么做入侵最小,怎么做最方便才是重點买喧。
1捻悯、坑定不能直接在接口調(diào)用層做加密嘹黔,加參數(shù)巨缘,這樣每個接口都要修改,這是不可能的抛蚤。
2低淡、ConverterFactory處理姓言,這也是網(wǎng)上可以搜到的很多文章的寫法,但我覺得還是有入侵蔗蹋。而且有點麻煩何荚。
3、OkHttp添加攔截器猪杭,這種方法入侵最胁吞痢(可以說沒有),實現(xiàn)呢也非常優(yōu)雅皂吮。
下面的實現(xiàn)戒傻,網(wǎng)上也找不到多少可以參考的文章,但不得不說蜂筹,OkHttp的封裝和設(shè)計真的很好用需纳,所見即所得∫张玻看下源碼不翩,就知道該怎么用了,連文檔都不用查。
-----------------------------------------------------------------------------------------------------------------------------------------------
----->先定義一個攔截器的實現(xiàn):
-----------------------------------------------------------------------------------------------------------------------------------------------
public class DataEncryptInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//請求
Request request = chain.request();
RequestBody oldRequestBody = request.body();
Buffer requestBuffer = new Buffer();
oldRequestBody.writeTo(requestBuffer);
String oldBodyStr = requestBuffer.readUtf8();
requestBuffer.close();
MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
//生成隨機AES密鑰并用serverPublicKey進行RSA加密
SecretKeySpec appAESKeySpec = EncryptUtils.generateAESKey(256);
String appAESKeyStr = EncryptUtils.covertAESKey2String(appAESKeySpec);
String appEncryptedKey = RSAUtils.encryptDataString(appAESKeyStr, serverPublicKey);
//計算body 哈希 并使用app私鑰RSA簽名
String appSignature = RSAUtils.signature(oldBodyStr, appPrivateKey);
//隨機AES密鑰加密oldBodyStr
String newBodyStr = EncryptUtils.encryptAES(appAESKeySpec, oldBodyStr);
RequestBody newBody = RequestBody.create(mediaType, newBodyStr);
//構(gòu)造新的request
request = request.newBuilder()
.header("Content-Type", newBody.contentType().toString())
.header("Content-Length", String.valueOf(newBody.contentLength()))
.method(request.method(), newBody)
.header("appEncryptedKey", appEncryptedKey)
.header("appSignature", appSignature)
.header("appPublicKey", appPublicKeyStr)
.build();
//響應(yīng)
Response response = chain.proceed(request);
if (response.code() == 200) {//只有約定的返回碼才經(jīng)過加密口蝠,才需要走解密的邏輯
//獲取響應(yīng)頭
String serverEncryptedKey = response.header("serverEncryptedKey");
//用app的RSA私鑰解密AES加密密鑰
String serverDecryptedKey = RSAUtils.decryptDataString(serverEncryptedKey, appPrivateKey);
SecretKeySpec serverAESKeySpec = EncryptUtils.covertString2AESKey(serverDecryptedKey);
//用AES密鑰解密oldResponseBodyStr
ResponseBody oldResponseBody = response.body();
String oldResponseBodyStr = oldResponseBody.string();
String newResponseBodyStr = EncryptUtils.decryptAES(serverAESKeySpec, oldResponseBodyStr);
oldResponseBody.close();
//構(gòu)造新的response
ResponseBody newResponseBody = ResponseBody.create(mediaType, newResponseBodyStr);
response = response.newBuilder().body(newResponseBody).build();
}
response.close();
//返回
return response;
}
}
-----------------------------------------------------------------------------------------------------------------------------------------------
----->然后OkHttp加入該攔截器:
-----------------------------------------------------------------------------------------------------------------------------------------------
new OkHttpClient.Builder()
.addInterceptor(new DataEncryptInterceptor())
....
.build();
-----------------------------------------------------------------------------------------------------------------------------------------------
----->這樣就搞定了器钟。
-----------------------------------------------------------------------------------------------------------------------------------------------
主要注意點:
0、和接口無關(guān)的新加的數(shù)據(jù)放在請求頭里亚皂。
1俱箱、該close的要close,不然會內(nèi)存泄漏灭必。
2狞谱、新舊Request和Response要區(qū)分好,新的要替換舊的去傳遞或返回禁漓。
3跟衅、要對response.code()做處理,只有在和后臺約定好的返回碼下才走解密的邏輯播歼,具體看自己的需求伶跷,不一定都是200。