寫在前面:任何應(yīng)用的開發(fā)中安全都是重中之重,在信息交互異惩妫活躍的現(xiàn)在滴劲,信息加密技術(shù)顯得尤為重要。在app應(yīng)用開發(fā)中顾复,我們需要對(duì)應(yīng)用中的多項(xiàng)數(shù)據(jù)進(jìn)行加密處理班挖,從而來保證應(yīng)用上線后的安全性,給用戶一個(gè)安全保障芯砸。這篇文章就介紹在iOS開發(fā)中最常用的數(shù)據(jù)加密方式萧芙。
文中證書鎖定內(nèi)容部分參考了博客
http://blog.csdn.net/dd864140130/article/details/52625666。
iOS中數(shù)據(jù)加密有下面幾種方式
1假丧、使用數(shù)字證書鎖定來保證不被中間人攔截双揪,將服務(wù)器返回的數(shù)據(jù)和我的當(dāng)?shù)刈C書進(jìn)行對(duì)比,確保是從服務(wù)器返回回來的包帚。證書有ca證書渔期,也可以自己給自己簽發(fā)證書。像12306購票渴邦。
2疯趟、使用https協(xié)議請(qǐng)求網(wǎng)頁,post來請(qǐng)求網(wǎng)頁數(shù)據(jù)谋梭,保證用戶的賬號(hào)密碼不被被人獲取到信峻。
3、使用蘋果自己的SSKeyChain鑰匙串瓮床,將用戶的賬號(hào)密碼保存在鑰匙串中盹舞。鑰匙串拱了錯(cuò)誤處理产镐,如果保存出錯(cuò),會(huì)在判斷后打印出出錯(cuò)的信息踢步。
4癣亚、最保險(xiǎn)的加密算法是非對(duì)稱加密。非對(duì)稱加密公鑰加密私鑰解密贾虽。缺點(diǎn)是要耗費(fèi)時(shí)間逃糟。
1、證書鎖定
當(dāng)我們上網(wǎng)瀏覽網(wǎng)頁蓬豁,從網(wǎng)上獲取數(shù)據(jù)的時(shí)候,我們知道菇肃,不管是http還是https協(xié)議地粪,都是服務(wù)端被動(dòng),客戶端主動(dòng)琐谤。所以蟆技,客戶端第一次發(fā)出請(qǐng)求之后,通常無法確定服務(wù)端是不是合法斗忌。就很可能就會(huì)出現(xiàn)以下情景质礼,正常情況下,我們想要根據(jù)文章aid查看某篇文章內(nèi)容织阳,其流程如下:
但如果遭受黑客攻擊眶蕉,流程就會(huì)這樣的:
此時(shí)惡意服務(wù)端完全可以發(fā)起雙向攻擊:對(duì)上可以欺騙服務(wù)端,對(duì)下可以欺騙客戶端唧躲,更嚴(yán)重的是客戶端段和服務(wù)端完全感知不到已經(jīng)被攻擊了造挽。這就是中間人攻擊。
關(guān)于中間人攻擊維基百科上有更深入的定義:
中間人攻擊(Man-in-the-middle attack弄痹,縮寫:MITM)是指攻擊者與通訊的兩端分別創(chuàng)建獨(dú)立的聯(lián)系饭入,并交換其所收到的數(shù)據(jù),使通訊的兩端認(rèn)為他們正在通過一個(gè)私密的連接與對(duì)方直接對(duì)話肛真,但事實(shí)上整個(gè)會(huì)話都被攻擊者完全控制谐丢。在中間人攻擊中,攻擊者可以攔截通訊雙方的通話并插入新的內(nèi)容蚓让。在許多情況下這是很簡單的(例如乾忱,在一個(gè)未加密的Wi-Fi無線接入點(diǎn)的接受范圍內(nèi)的中間人攻擊者,可以將自己作為一個(gè)中間人插入這個(gè)網(wǎng)絡(luò))凭疮。
一個(gè)中間人攻擊能成功的前提條件是攻擊者能將自己偽裝成每一個(gè)參與會(huì)話的終端饭耳,并且不被其他終端識(shí)破。中間人攻擊是一個(gè)(缺乏)相互認(rèn)證的攻擊执解。大多數(shù)的加密協(xié)議都專門加入了一些特殊的認(rèn)證方法以阻止中間人攻擊寞肖。例如纲酗,SSL協(xié)議可以驗(yàn)證參與通訊的一方或雙方使用的證書是否是由權(quán)威的受信任的數(shù)字證書認(rèn)證機(jī)構(gòu)頒發(fā),并且能執(zhí)行雙向身份認(rèn)證新蟆。
那么我們來看下證書鎖定是怎么樣提高安全性觅赊,避免中間人攻擊的,用一張簡單的流程圖來說明:
不難看出琼稻,通過證書鎖定能有有效的避免中間人攻擊吮螺。
證書鎖定的缺點(diǎn)
證書鎖定盡管帶了較高的安全性,但是這種安全性的提高卻犧牲了靈活性帕翻。一旦當(dāng)證書發(fā)生變化時(shí)鸠补,我們的客戶端也必須隨之升級(jí),除此之外嘀掸,我們的服務(wù)端不得不為了兼容以前的客戶端而做出一些妥協(xié)或者說直接停用以前的客戶端紫岩,這對(duì)開發(fā)者和用戶來說并不是那么的友好。
但實(shí)際上睬塌,極少情況下我們才會(huì)變動(dòng)證書泉蝌。因此,如果產(chǎn)品安全性要求比較高還是啟動(dòng)證書鎖定吧揩晴。
在iOS開發(fā)中勋陪,我們可以自己給自己簽發(fā)數(shù)字證書,就類似于12306購票網(wǎng)站硫兰。從而保證了數(shù)據(jù)的安全性诅愚。下面是生成證書的過程。
// 生成1024位私鑰
openssl genrsa -out private_key.pem 1024
// 根據(jù)私鑰生成CSR文件
openssl req -new -key private_key.pem -out rsaCertReq.csr
// 根據(jù)私鑰和CSR文件生成crt文件 openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey private_key.pem -out rsaCert.crt
// 為IOS端生成公鑰der文件
openssl x509 -outform der -in rsaCert.crt -out public_key.der
// 將私鑰導(dǎo)出為這p12文件
openssl pkcs12 -export -out private_key.p12 -inkey private_key.pem -in rsaCert.crt
2瞄崇、使用post
下面是兩段post和get的代碼的對(duì)比呻粹。
//GET請(qǐng)求的URL
http://localhost/php/login/login.php?username=zhangsan&password=zhang
//POST請(qǐng)求的URL
http://localhost/php/login/login.php
我們可以很清楚地看到使用GET方式發(fā)起請(qǐng)求的URL中包含了用戶的賬號(hào)和密碼信息。如果使用GET發(fā)起請(qǐng)求苏研,如果有人使用Charles攔截我們的請(qǐng)求等浊,就很容易地從我們發(fā)起請(qǐng)求的URL中獲取到我們的賬號(hào)信息。所以發(fā)起數(shù)據(jù)請(qǐng)求的時(shí)候避免使用GET摹蘑,而使用POST筹燕。
3、SSKeyChain
使用蘋果自己的SSKeyChain鑰匙串衅鹿,我們也能保證用戶的數(shù)據(jù)安全撒踪,我們將用戶的賬號(hào)信息保存到鑰匙串中能保證數(shù)據(jù)安全的原因是因?yàn)橹挥刑O果公司才知道鑰匙串保存在內(nèi)存中的哪個(gè)位置。
使用SSKeyChain我們進(jìn)行下面兩步驟操作:
1大渤、 在工程中加入Security.framework框架制妄。
2、 把SSKeychain.h和SSKeychain.m加到項(xiàng)目文件夾泵三。
加入了需要的文件夾后耕捞,SSKeyChain的作者samsoffes在實(shí)例代碼中給出了使用SSKeyChain的方法衔掸。
我們通過下面方法來使用SSKeyChain。
//獲取所有賬號(hào)
+ (NSArray *)allAccounts;
//通過賬號(hào)名字獲取服務(wù)名
+ (NSArray *)accountsForService:(NSString *)serviceName;
//通過服務(wù)名和賬號(hào)獲取密碼
+ (NSString *)passwordForService:(NSString*)serviceNameaccount:(NSString *)account;
//通過服務(wù)名和賬號(hào)刪除密碼
+ (BOOL)deletePasswordForService:(NSString*)serviceNameaccount:(NSString *)account;
//通過服務(wù)名和賬號(hào)設(shè)置密碼
+ (BOOL)setPassword:(NSString *)passwordforService:(NSString*)serviceName account:(NSString *)account;
下面是具體的使用方法俺抽,通過上面幾個(gè)方法敞映,我們可以很方便地將用戶賬號(hào)保存到鑰匙串,或者從鑰匙串中取出來磷斧。
項(xiàng)目地址https://github.com/samsoffes/sskeychain
#import <SenTestingKit/SenTestingKit.h>
#import "SSKeychain.h"
//用變量接受服務(wù)名振愿,賬號(hào)和密碼
static NSString *kSSToolkitTestsServiceName = @"SSToolkitTestService";
static NSString *kSSToolkitTestsAccountName = @"SSToolkitTestAccount";
static NSString *kSSToolkitTestsPassword = @"SSToolkitTestPassword";
@interface SSKeychainTests : SenTestCase
//判斷鑰匙串所有賬號(hào)中是否包含一個(gè)指定的賬號(hào)
- (BOOL)_accounts:(NSArray *)accounts containsAccountWithName:(NSString *)name;
@end
@implementation SSKeychainTests
- (void)testAll {
// Getting & Setings Passwords
[SSKeychain setPassword:kSSToolkitTestsPassword forService:kSSToolkitTestsServiceName account:kSSToolkitTestsAccountName];
NSString *password = [SSKeychain passwordForService:kSSToolkitTestsServiceName account:kSSToolkitTestsAccountName];
STAssertEqualObjects(password, kSSToolkitTestsPassword, @"Password reads and writes");
// Getting Accounts
NSArray *accounts = [SSKeychain allAccounts];
STAssertTrue([self _accounts:accounts containsAccountWithName:kSSToolkitTestsAccountName], @"All accounts");
accounts = [SSKeychain accountsForService:kSSToolkitTestsServiceName];
STAssertTrue([self _accounts:accounts containsAccountWithName:kSSToolkitTestsAccountName], @"Account for service");
// Deleting Passwords
[SSKeychain deletePasswordForService:kSSToolkitTestsServiceName account:kSSToolkitTestsAccountName];
password = [SSKeychain passwordForService:kSSToolkitTestsServiceName account:kSSToolkitTestsAccountName];
STAssertNil(password, @"Password deletes");
}
- (BOOL)_accounts:(NSArray *)accounts containsAccountWithName:(NSString *)name {
for (NSDictionary *dictionary in accounts) {
if ([[dictionary objectForKey:@"acct"] isEqualToString:name]) {
return YES;
}
}
return NO;
}
上面的方法是用來保存用戶的賬號(hào)信息,將賬號(hào)信息保存到鑰匙串中弛饭,因?yàn)殍€匙串的不可見性冕末,就已經(jīng)足夠地保證了用戶的賬號(hào)信息安全。如果我們還想讓用戶的賬號(hào)信息得到更安全的保證侣颂,我們可以先將用戶信息進(jìn)行MD5加密栓霜,然后加鹽。再將加密后的賬號(hào)信息保存到鑰匙串中横蜒。因?yàn)镸D5編碼的不可逆性,就更進(jìn)一步地保證了用戶信息的安全销凑。
關(guān)于MD5加密我在我之前寫的一篇MD5加密的博客中有詳細(xì)的說明丛晌。
4、非對(duì)稱加密
剛才介紹方法是用來保證用戶信息的安全斗幼。在金融類app中澎蛛,要保證財(cái)務(wù)數(shù)據(jù)的安全,就需要使用到更加安全的非對(duì)稱加密(維基百科上叫公開密鑰加密)蜕窿。維基百科對(duì)于非對(duì)稱加密的定義是:一種密碼學(xué)算法類型谋逻,在這種密碼學(xué)方法中,需要一對(duì)密鑰桐经,一個(gè)是私人密鑰毁兆,另一個(gè)則是公開密鑰。這兩個(gè)密鑰是數(shù)學(xué)相關(guān)阴挣,用某用戶密鑰加密后所得的信息气堕,只能用該用戶的解密密鑰才能解密。如果知道了其中一個(gè)畔咧,并不能計(jì)算出另外一個(gè)茎芭。因此如果公開了一對(duì)密鑰中的一個(gè),并不會(huì)危害到另外一個(gè)的秘密性質(zhì)誓沸。稱公開的密鑰為公鑰梅桩;不公開的密鑰為私鑰。
使用公開加密方式中的公鑰和私鑰可以進(jìn)行數(shù)字簽名,原理是這樣子的:用私鑰加密的信息拜隧,可以用公鑰對(duì)其解密宿百,用于客戶驗(yàn)證持有私鑰一方發(fā)布的數(shù)據(jù)或文件是完整準(zhǔn)確的趁仙,接收者由此可知這條信息確實(shí)來自于擁有私鑰的某人,這被稱作數(shù)字簽名犀呼,公鑰的形式就是數(shù)字證書幸撕。例如,從網(wǎng)上下載的安裝程序外臂,一般都帶有程序制作者的數(shù)字簽名坐儿,可以證明該程序的確是該作者(公司)發(fā)布的而不是第三方偽造的且未被篡改過(身份認(rèn)證/驗(yàn)證)。
常見的公鑰加密算法有:RSA宋光、ElGamal貌矿、背包算法、Rabin(RSA的特例)罪佳、迪菲-赫爾曼密鑰交換協(xié)議中的公鑰加密算法逛漫、橢圓曲線加密算法(英語:Elliptic Curve Cryptography, ECC)。使用最廣泛的是RSA算法(由發(fā)明者Rivest赘艳、Shmir和Adleman姓氏首字母縮寫而來)是著名的公開秘鑰加密算法酌毡。在這里我們也是使用RSA來進(jìn)行公鑰加密。
通過下面的代碼蕾管,我們能夠自己生成一個(gè)數(shù)字證書枷踏。
// 生成1024位私鑰 openssl genrsa -out private_key.pem 1024
// 根據(jù)私鑰生成CSR文件 openssl req -new -key private_key.pem -out rsaCertReq.csr
// 根據(jù)私鑰和CSR文件生成crt文件 openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey private_key.pem -out rsaCert.crt
// 為IOS端生成公鑰der文件 openssl x509 -outform der -in rsaCert.crt -out public_key.der
// 將私鑰導(dǎo)出為這p12文件 openssl pkcs12 -export -out private_key.p12 -inkey private_key.pem -in rsaCert.crt
得到公鑰和私鑰后就可以將數(shù)據(jù)進(jìn)行加密了。我們把一個(gè)字符串用RAS算法加密后查看加密后的字符串掰曾,再反編碼看到解密后的字符串旭蠕,查看加密和解密的效果。
NSString *encryptString = [self rsaEncryptText:@"123456好哇好哇哈"];
NSLog(@"加密:123456好哇好哇哈:%@", encryptString);
NSLog(@"解密結(jié)果為:%@", [self rsaDecryptWithText:encryptString]);
然后我們自己定義加密和解密的方法旷坦,等下我們就要使用加密和解密的方法來進(jìn)行數(shù)據(jù)的加密和解密掏熬。
插入自定義加密和解密的方法。
#import <Foundation/Foundation.h>
@interface HYBRSAEncrypt : NSObject
// 加密相關(guān)
- (void)loadPublicKeyWithPath:(NSString *)derFilePath;
- (void)loadPublicKeyWithData:(NSData *)derData;
- (NSString *)rsaEncryptText:(NSString *)text;
- (NSData *)rsaEncryptData:(NSData *)data;
// 解密相關(guān)
- (void)loadPrivateKeyWithPath:(NSString *)p12FilePath password:(NSString *)p12Password;
- (void)loadPrivateKeyWithData:(NSData *)p12Data password:(NSString *)p12Password;
- (NSString *)rsaDecryptText:(NSString *)text;
- (NSData *)rsaDecryptData:(NSData *)data;
@end
加密過程中的思路是:
1秒梅、定義一個(gè)方法旗芬,把證書文件需要加密的數(shù)據(jù)傳入一個(gè)方法中,生成一個(gè)公鑰番电。
2岗屏、創(chuàng)建一個(gè)能夠?qū)?shù)據(jù)進(jìn)行base64加密的方法。將需要加密的文本通過base加密漱办,加密完成后再調(diào)用公鑰加密的方法對(duì)base加密后的數(shù)據(jù)進(jìn)行二次加密这刷。加密時(shí)是講二進(jìn)制數(shù)據(jù)分段,切片后進(jìn)行加密再拼接到二進(jìn)制數(shù)據(jù)的變量中娩井。
#import "HYBRSAEncrypt.h"
@interface HYBRSAEncrypt () {
SecKeyRef _publicKey;
SecKeyRef _privateKey;
}
@end
@implementation HYBRSAEncrypt
- (void)dealloc {
if (nil != _publicKey) {
CFRelease(_publicKey);
}
if (nil != _privateKey) {
CFRelease(_privateKey);
}
}
#pragma mark - 加密相關(guān)
//用本地證書加載公鑰
- (void)loadPublicKeyWithPath:(NSString *)derFilePath {
NSData *derData = [[NSData alloc] initWithContentsOfFile:derFilePath];
if (derData.length > 0) {
[self loadPublicKeyWithData:derData];
} else {
NSLog(@"load public key fail with path: %@", derFilePath);
}
}
//加載公鑰方法
- (void)loadPublicKeyWithData:(NSData *)derData {
SecCertificateRef myCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)derData);
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecTrustRef myTrust;
OSStatus status = SecTrustCreateWithCertificates(myCertificate,myPolicy,&myTrust);
SecTrustResultType trustResult;
if (status == noErr) {
status = SecTrustEvaluate(myTrust, &trustResult);
}
SecKeyRef securityKey = SecTrustCopyPublicKey(myTrust);
CFRelease(myCertificate);
CFRelease(myPolicy);
CFRelease(myTrust);
_publicKey = securityKey;
}
//將文本內(nèi)容加密
- (NSString *)rsaEncryptText:(NSString *)text {
NSData *encryptedData = [self rsaEncryptData:[text hdf_toData]];
NSString *base64EncryptedString = [NSString hdf_base64StringFromData:encryptedData
length:encryptedData.length];
return base64EncryptedString;
}
//分段再加密數(shù)據(jù)
- (NSData *)rsaEncryptData:(NSData *)data {
SecKeyRef key = _publicKey;
size_t cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
size_t blockSize = cipherBufferSize - 11;
size_t blockCount = (size_t)ceil([data length] / (double)blockSize);
NSMutableData *encryptedData = [[NSMutableData alloc] init] ;
for (int i = 0; i < blockCount; i++) {
size_t bufferSize = MIN(blockSize,[data length] - i * blockSize);
NSData *buffer = [data subdataWithRange:NSMakeRange(i * blockSize, bufferSize)];
OSStatus status = SecKeyEncrypt(key,
kSecPaddingPKCS1,
(const uint8_t *)[buffer bytes],
[buffer length],
cipherBuffer,
&cipherBufferSize);
if (status == noErr) {
NSData *encryptedBytes = [[NSData alloc] initWithBytes:(const void *)cipherBuffer
length:cipherBufferSize];
[encryptedData appendData:encryptedBytes];
} else {
if (cipherBuffer) {
free(cipherBuffer);
}
return nil;
}
}
if (cipherBuffer){
free(cipherBuffer);
}
return encryptedData;
}
然后我們可以通過私鑰解密暇屋。解密思路和加密過程相同。
#pragma mark - 解密相關(guān)
- (void)loadPrivateKeyWithPath:(NSString *)p12FilePath password:(NSString *)p12Password {
NSData *data = [NSData dataWithContentsOfFile:p12FilePath];
if (data.length > 0) {
[self loadPrivateKeyWithData:data password:p12Password];
} else {
NSLog(@"load private key fail with path: %@", p12FilePath);
}
}
//生成私鑰
- (void)loadPrivateKeyWithData:(NSData *)p12Data password:(NSString *)p12Password {
SecKeyRef privateKeyRef = NULL;
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
[options setObject:p12Password forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)p12Data,
(__bridge CFDictionaryRef)options,
&items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
_privateKey = privateKeyRef;
// CFRelease(items);
}
//調(diào)用下面方法進(jìn)行解密洞辣,最后返回一個(gè)字符串
- (NSString *)rsaDecryptText:(NSString *)text {
NSData *data = [NSData hdf_base64DataFromString:text];
NSData *decryptData = [self rsaDecryptData:data];
NSString *result = [[NSString alloc] initWithData:decryptData encoding:NSUTF8StringEncoding];
return result;
}
//用私鑰解密的方法咐刨,被上面方法調(diào)用
- (NSData *)rsaDecryptData:(NSData *)data {
SecKeyRef key = _privateKey;
size_t cipherLen = [data length];
void *cipher = malloc(cipherLen);
[data getBytes:cipher length:cipherLen];
size_t plainLen = SecKeyGetBlockSize(key) - 12;
void *plain = malloc(plainLen);
OSStatus status = SecKeyDecrypt(key, kSecPaddingPKCS1, cipher, cipherLen, plain, &plainLen);
if (status != noErr) {
return nil;
}
NSData *decryptedData = [[NSData alloc] initWithBytes:(const void *)plain length:plainLen];
return decryptedData;
}
@end
上面就是iOS開發(fā)中常用的幾種數(shù)據(jù)加密方式昙衅。