伴隨著移動產品廣泛應用與快速發展,移動客戶端的安全問題越來越受重視。我這里主要談談iOS移動端的安全架構設計。
我們創建了一個iOS技術交流群,iOS技術職業交流覆蓋 2800+技術牛人,
直接搜索或點擊群號:638302184,快速進群,
進群與2800+iOS開發一起學習成長,我們期待您的加入!
一、前言
移動端的安全一般可以理解為兩個方面:本地安全、通信安全。
1、本地安全:
本地安全主要指客戶端本地環境與數據的安全,以及代碼被破解獲得所導致的安全問題,如:明文存儲問題,惡意二次打包問題,越權操作問題等。2、通信安全:
通信安全主要指客戶端與服務端進行數據交互時被攔截破獲導致的安全問題。如采取明文進行密碼或者數據傳輸等問題。
關于一些移動端安全的認識還有web安全等等,請參考文章:移動端本地安全解決方案
二、安全加密基礎的快速認識
加密分為對稱加密和非對稱加密。
1、對稱加密
加密使用的密鑰和解密使用的密鑰是相同的。也就是說,加密和解密都是使用的同一個密鑰,也就是共享密鑰密碼。(ps:這個很好理解吧) 常用的對稱加密算法:
| 名稱 | 英文 |
| --- | --- | --- |
| DES | Data Encryption Standard |
| 3DES | Triple DES |
| AES | dvanced Encryption Standard |
2、非對稱加密
加密使用的密鑰和解密使用的密鑰是不相同的。 (ps:這個不好理解?不好理解請好好看看非對稱加密算法原理) 簡單來說就是有一套算法可以實現你用公鑰加密的信息,對方用保留的私鑰進行解密。
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要這是一個我的iOS交流群:638302184,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!
常說的公鑰密碼就是指非對稱密碼,公鑰加密就是一種非對稱加密。
最常用的是RSA算法。RSA的演算方法是:
1)用戶選擇2個夠大的保密質數q、p(一般為100位以上十進數) 2)令n=pq,n是公開的,從n分解除qp是極其困難的。 n的歐拉函數:Φ(n)=(p-1)(q-1) Φ(n)小于等于n,并與n互質 3)選擇一個相對大的整數e作為加密指數,使e與Φ(n)互質, 4)解同等方程: ed=1modΦ(n) 求出解密指數d 5)設M、C分別為要加密的明文和被加密的密文(M、C小于n) 則:加密運算為:C=Memod n 解密運算為:M=Cdmod n 6)每個用戶都有一組密鑰(e、d、n) (e,n)為PK'可以公開在手冊上的公鑰,e為加密指數, (d,n)為SK’(或PV)是用戶保密的私鑰 將p.q銷毀 7)要求明文X 舉例: 1) 選兩個質數: p=47 q=71 2)計算: n=pq=3337 Φ(n)=(47-1)(71-1)=3220 3) e必須與Φ(n)互質,選e=79 4) 計算:ed=1modΦ(n)=1mod(3220) d=1019 將e、n公布,d保密,p.q消毀 如有一明文 M=6882326879666683要加密,則先將M分割成多塊: m1=688,m2=232,m3=687,m4=966,m5=668,m6=3 將第1塊M1加密后得密文C1: C1=m1e(mod3337)=68879(mod3337)=1570 依次對各區塊加密后得密文C: C=15702756271422762423158 對C1解密得m1 M1=C1d(mod3337)=15701019(mod3337)=688 依次解密得原文M。
那么問題來了,我們遇到的PBE(Password Based Encryption,基于口令加密)加密啊都是什么呢。詳情可以去了解下,這里強調一點,PBE算法的應用還是在對稱加密范疇。至于哪種加密方案適合自己的場景,這也是我們今天iOS移動端安全架構設計的時候需要考慮的。
知識點延伸:
1、對稱密碼與公鑰密碼對比,消息認證碼與數字簽名的對比
對象屬性 | 對稱密碼 | 公鑰密碼(非對稱密碼) |
---|---|---|
發送者 | 用共享密鑰加密-雙方持有 | 用公鑰加密 |
接收者 | 用共享密鑰加密-雙方持有 | 用私鑰解密 |
密鑰配送問題 | 存在 | 不存在,但公鑰需要另外認證 |
機密性 | ? | ? |
對象屬性 | 消息認證碼 | 數字簽名 |
---|---|---|
發送者 | 用共享密鑰計算MAC值 | 用私鑰生成簽名 |
接收者 | 用共享密鑰計算MAC值 | 用公鑰驗證簽名 |
密鑰配送問題 | 存在 | 不存在,但公鑰需要另外認證 |
完整性 | ? | ? |
認證 | ?(僅限通信對象雙方) | ? (可適用于任何第三方) |
防止否認 | ? | ? |
2、公鑰密碼與數字簽名的密鑰適用方式
對象屬性 | 公鑰 | 私鑰 |
---|---|---|
公鑰密碼 | 發送者加密時使用 | 接收者解密時使用 |
數字簽名 | 驗證者驗證簽名時使用 | 簽名者生成簽名時使用 |
誰持有密鑰? | 只要需要,可任何人都可以持有 | 個人持有 |
數字簽名就是利用非對稱加密的原理,只是用法下不一樣,反著的。另外為公鑰加上數字簽名就是證書。
二、iOS移動端設計的安全考量
移動客戶端設計時,除了上述寬泛的本地安全和通信安全需要考量,具體到模塊與業務需要開發者作哪些思考呢?本地密碼存儲安全怎么保證,客戶端web頁面怎么防止被劫持,客戶端在設計交易時如何考慮安全性等等問題。如今蘋果開始強制接口使用HTTPS協議也是為為了增強安全性,但開發者在客戶端設計之初也需要考慮這些安全問題,建立一些基礎的安全模塊。
(ps:至于涉及到金融類App考慮的安全問題就更多,如交易安全,私鑰如何存儲等)
1、安全需求分析
加密原則:
- App代碼安全,包括代碼混淆,加密或者app加殼。
- App數據存儲安全,主要指在磁盤做數據持久化的時候所做的加密。
- App網絡傳輸安全,指對數據從客戶端傳輸到Server中間過程的加密,防 止網絡世界當中其他節點對數據的竊聽。
本地存儲安全:
1)、明文存儲,過度依賴系統安全性(iOS:Keychain/UserDefault)
2)、惡意二次打包
3)、容易被逆向,被調試
4)、敏感信息寫入代碼
網絡通信安全:
1)、HTTP傳輸被劫持(蘋果開始強制接口使用HTTPS協議)
2)、明文傳輸數據
另外由本地安全延伸出來:
1)、本地自動登錄如何存儲私鑰,如何解決又方便用戶,又保證安全。
2)、金融類、支付類App如何解決支付安全問題,二維碼支付的安全問題等等。
2、需求技術準備
1)、針對本地明文存儲的安全漏洞,自然是要本地進行加密,本地加密只是增加了一些復雜度,但也是有效的。比如利用對稱算法,通過簡單的URLENCODE + BASE64編碼防止數據明文傳輸
2)、對普通請求、返回數據,生成MD5校驗(MD5中加入動態密鑰),進行數據完整性(簡單防篡改,安全性較低,優點:快速)校驗。發送請求也有PBE加密等等
3)、對于重要數據,使用RSA進行數字簽名,起到防篡改作。
4)、 對于比較敏感的數據,如用戶信息(登陸、注冊等),客戶端發送使用RSA加密,服務器返回使用DES(AES)加密。
|類型| 簡介|
| --- | --- | --- |
|本地數據加密| 對NSUserDefaults,sqlite存儲文件數據加密,保護帳號和關鍵信息。|
|URL編碼加密 |對程序中出現的URL進行編碼加密,防止URL被靜態分析|
|網絡傳輸數據加密| 對客戶端傳輸數據提供加密方案,有效防止通過網絡接口的攔截獲取|
|方法體,方法名高級混淆| 對應用程序的方法名和方法體進行混淆,保證源碼被逆向后無法解析代碼|
|程序結構混排加密| 對應用程序邏輯結構進行打亂混排,保證源碼可讀性降到最低|
三、iOS移動端安全模塊設計
1、基礎模塊
1)、對稱加密工具類
提供iOS客戶端的基礎對稱加密算法(MD5 \ SHA \ DES \ 3DES \ RC2和RC4 \ IDEA \ DSA \ AES)
//Base64加密
+(NSString *)encodeBase64:(NSString *)input {
NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
data = [GTMBase64 encodeData:data];
NSString *base64String = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"encodeBase64 == %@",base64String);
return base64String;
}
//Base64編碼
+(NSString *)base64EncodeString:(NSString *)string {
//1.先把字符串轉換為二進制數據
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
//2.對二進制數據進行base64編碼,返回編碼后的字符串
//這是蘋果已經給我們提供的方法
NSString *str = [data base64EncodedStringWithOptions:0];
return str;
}
//Base64解密
+(NSString *)decodeBase64:(NSString *)input {
NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
data = [GTMBase64 decodeData:data];
NSString *base64String = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"decodeBase64 == %@",base64String);
return base64String;
}
//對base64編碼后的字符串進行解碼
+(NSString *)base64DecodeString:(NSString *)string {
//1.將base64編碼后的字符串『解碼』為二進制數據
//這是蘋果已經給我們提供的方法
NSData *data = [[NSData alloc]initWithBase64EncodedString:string options:0];
//2.把二進制數據轉換為字符串返回
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
return str;
}
//Base64 字符串解碼成 字符串
+(NSString *)decodeBase64ToHexString:(NSString *)input {
//1.將base64編碼后的字符串『解碼』為二進制數據
//這是蘋果已經給我們提供的方法
NSData *myD = [[NSData alloc]initWithBase64EncodedString:input options:0];
Byte *bytes = (Byte *)[myD bytes];
//下面是Byte 轉換為16進制。
NSString *hexStr=@"";
for(int i=0;i<[myD length];i++) {
NSString *newHexStr = [NSString stringWithFormat:@"%x",bytes[i]&0xff];///16進制數
if([newHexStr length]==1)
hexStr = [NSString stringWithFormat:@"%@0%@",hexStr,newHexStr];
else
hexStr = [NSString stringWithFormat:@"%@%@",hexStr,newHexStr];
}
return hexStr;
}
//DES 加密
+(NSData *)encryptUseDES:(NSData *)plainText key:(Byte *)key {
NSData *textData = plainText;
NSUInteger dataLength = [textData length];
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES,
kCCOptionECBMode,
key, kCCKeySizeDES,
nil,
[textData bytes], dataLength,
buffer, 1024,
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
return data;
}
return nil;
}
//DES 解密
+(NSData *)decrypUseDES:(NSData *)plainText key:(Byte *)key {
NSData *cipherdata = plainText;
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmDES,
kCCOptionECBMode,
key, kCCKeySizeDES,
nil,
[cipherdata bytes], [cipherdata length],
buffer, 1024,
&numBytesDecrypted);
if(cryptStatus == kCCSuccess) {
NSData *plaindata = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
return plaindata;
}
return nil;
}
/MD5加密返回Nsdata
+(NSData *)encodeMD5:(NSData *)input {
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(input.bytes, (CC_LONG)input.length, result);
NSData *data =[[NSData alloc] initWithBytes:result length:CC_MD5_DIGEST_LENGTH];
return data;
}
如下,生成是小寫的MD5的字符串,如果要生成大寫的,只需要把
[ret appendFormat:@"%02X",result[i]];中的 "%02X"的 X改成小寫的 x即可。
//MD5加密返回Nsstring
+(NSString *)MD5HexDigest:(NSData *)input {
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(input.bytes, (CC_LONG)input.length, result);
NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
for (int i = 0; i<CC_MD5_DIGEST_LENGTH; i++) {
[ret appendFormat:@"%02X",result[i]];
}
return ret;
}
2)、非對稱加密工具類
iOS中的Security.framework提供了對RSA算法的支持。這種方式需要對密匙對進行處理,根據public key生成證書,通過private key生成p12格式的密匙。
除了Secruty.framework,也可以將openssl庫編譯到iOS工程中,這可以提供更靈活的使用方式。
開源的:OpenSSLRSAWrapper
2、一些場景方案設計舉例:(登錄場景token)
1)、一般的登陸:
客戶端第一次發出登錄請求時, 用戶密碼以明文的方式傳輸, 一旦被截獲, 后果嚴重。因此密碼需要加密,例如可采用RSA非對稱加密。具體流程如下:
- 客戶端向服務器第一次發起登錄請求(不傳輸用戶名和密碼)。
- 服務器利用RSA算法產生一對公鑰和私鑰。并保留私鑰, 將公鑰發送給客戶端。
- 客戶端收到公鑰后, 加密用戶密碼, 向服務器發起第二次登錄請求(傳輸用戶名和加密后的密碼)。
- 服務器利用保留的私鑰對密文進行解密,得到真正的密碼。
2)、token加密:
再仔細核對上述登錄流程, 我們發現服務器判斷用戶是否登錄, 完全依賴于sessionId, 一旦其被截獲, 黑客就能夠模擬出用戶的請求。于是我們需要引入token的概念: 用戶登錄成功后, 服務器不但為其分配了sessionId, 還分配了token, token是維持登錄狀態的關鍵秘密數據。在服務器向客戶端發送的token數據,也需要加密。于是一次登錄的細節再次擴展:
- 客戶端向服務器第一次發起登錄請求(不傳輸用戶名和密碼)。服務器利用RSA算法產生一對公鑰和私鑰。并保留私鑰, 將公鑰發送給客戶端。
- 客戶端收到公鑰后, 加密用戶密碼,向服務器發送用戶名和加密后的用戶密碼; 同時另外產生一對公鑰和私鑰,自己保留私鑰, 向服務器發送公鑰; 于是第二次登錄請求傳輸了用戶名和加密后的密碼以及客戶端生成的公鑰。
- 服務器利用保留的私鑰對密文進行解密,得到真正的密碼。 經過判斷, 確定用戶可以登錄后,生成sessionId和token, 同時利用客戶端發送的公鑰,對token進行加密。最后將sessionId和加密后的token返還給客戶端。
- 客戶端利用自己生成的私鑰對token密文解密, 得到真正的token。
圖示如下:
3)、登錄保持(也就是http/https數據請求階段)
引入token后,http/https請求被獲取問題便可得到解決。 客戶端將token和其它的一些變量, 利用散列加密算法得到簽名后,連同sessionId一并發送給服務器; 服務器取出保存于服務器端的token,利用相同的法則生成校驗簽名, 如果客戶端簽名與服務器的校驗簽名一致, 就認為請求來自登錄的客戶端。(支付寶一樣的機制)結構圖如下:
注:token失效的兩種情況:
1、用戶登錄出系統
2、token在后臺的規定時間內失效(每個token都是有時間效應的)
失效原理:在服務器端的redis中刪除相應key為session的鍵值對。
四、總結
本文只是初步介紹了一些加密的基礎知識和iOS客戶端安全設計時的基本思路,為大家提供一種參考。當然一些金融類和支付類的App對安全性的要求更高,在支付、掃碼等環節的安全設計方案可能需要更加的完善,甚至離線支付時的私鑰分端保存等等,這些都是需要思考的,也希望大家給出好的建議和方案。