iOS中的安全與加密
一。HTTPS雙向認(rèn)證
Charles是大家所熟悉的抓包工具,如果網(wǎng)絡(luò)請求未經(jīng)過雙向認(rèn)證,那么我們可以通過Charles拿到請求的參數(shù)和返回,具體的操作方法請看這里。它的原理簡單的概括為Charles偽裝為服務(wù)器與客戶端通信。那么雙向認(rèn)證要做的工作就是在服務(wù)器與客戶端之間相互驗證,避免數(shù)據(jù)被Charles這類的中間人截取。
準(zhǔn)備工作
1.服務(wù)端會向我們提供.pem證書和.key密鑰
2.將.pem導(dǎo)為.cer文件
openssl x509 -inform der -in certificate.cer -out certificate.pem
3.將.pem和.key導(dǎo)為p12文件
openssl pkcs12 -export -in certificate.pem -inkey chejinjia.key -out certificate.p12
4.這里的設(shè)置是以swift語言,并且Alamofire請求為基礎(chǔ)的,注意將代碼里的.cer和p12換成真實(shí)的名字
import Alamofire
class UtimesNetWorkConfig {
static let shared = UtimesNetWorkConfig()
//https認(rèn)證
func HTTPSAuthentication() {
SessionManager.default.delegate.sessionDidReceiveChallenge = { session, challenge in
//驗證服務(wù)端
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
return self.verifyServer(challenge: challenge)
}
//驗證客戶端
else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
return self.sendClientP12()
}
return (.cancelAuthenticationChallenge, nil)
}
}
}
-
sessionDidReceiveChallenge是對urlSession(_:didReceive:completionHandler:)的重寫,也就是發(fā)生網(wǎng)絡(luò)請求時的回調(diào),我們在這里面設(shè)置雙向認(rèn)證extension UtimesNetWorkConfig { //驗證服務(wù)端發(fā)來的證書 private func verifyServer(challenge: URLAuthenticationChallenge) -> SessionChallenge{ let serverTrust:SecTrust = challenge.protectionSpace.serverTrust! let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)! let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))! let cerPath = Bundle.main.path(forResource: "你的證書文件名", ofType: "cer")! let cerUrl = URL(fileURLWithPath:cerPath) if let localCertificateData = try? Data(contentsOf: cerUrl), remoteCertificateData.isEqual(localCertificateData) { let credential = URLCredential(trust: serverTrust) challenge.sender?.use(credential, for: challenge) return (URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!)) } else { return (.cancelAuthenticationChallenge, nil) } } //將本地證書發(fā)送到服務(wù)端認(rèn)證 private func sendClientP12() -> SessionChallenge { var identityAndTrust:IdentityAndTrust! var securityError:OSStatus = errSecSuccess let path: String = Bundle.main.path(forResource: "chejinjia", ofType: "p12") ?? "" let PKCS12Data = NSData(contentsOfFile:path)! let key : NSString = kSecImportExportPassphrase as NSString let options : NSDictionary = [key : "你的P12文件的密碼"] //客戶端證書密碼 var items : CFArray? securityError = SecPKCS12Import(PKCS12Data, options, &items) if securityError == errSecSuccess { // let certItems:CFArray = items as! CFArray; let certItemsArray:Array = items! as Array let dict:AnyObject? = certItemsArray.first; if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> { // grab the identity let identityPointer:AnyObject? = certEntry["identity"]; let secIdentityRef:SecIdentity = identityPointer as! SecIdentity // grab the trust let trustPointer:AnyObject? = certEntry["trust"] let trustRef:SecTrust = trustPointer as! SecTrust // grab the cert let chainPointer:AnyObject? = certEntry["chain"] identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: chainPointer!) } } let urlCredential:URLCredential = URLCredential( identity: identityAndTrust.identityRef, certificates: identityAndTrust.certArray as? [AnyObject], persistence: URLCredential.Persistence.forSession); return (.useCredential, urlCredential); } }
以上兩個方法可以看出,雙向認(rèn)證無非是將服務(wù)端的證書和客戶端的證書對比,看是否是同一本證書。是,則網(wǎng)絡(luò)請求成功,不是的話,網(wǎng)絡(luò)請求就會被取消。
由此,Charles一類的抓包工具已經(jīng)無法再抓取到網(wǎng)絡(luò)請求了。
二。AES
雖然用了HTTPS雙向認(rèn)證,可我們的安全系數(shù)還是不夠高。因為請求的參數(shù)實(shí)際上還是明文。比較敏感的參數(shù)例如密碼,我們還應(yīng)該將它再次加密。
這里介紹一下如果使用第三方庫CryptoSwift進(jìn)行AES加密
準(zhǔn)備工作:
1.安裝CryptoSwift
2.和服務(wù)器約定好AES的加密模式及參數(shù)(這里使用CBC模式)
let key = Array("ed3f91d05bbd77a5aea5c82c07f11a7b".utf8)
let iv = Array("ed3f91d05bbd77a5".utf8)
let input = Array("wodemima".utf8)
do {
let encrypt: Array<UInt8> = try AES(key: key, blockMode: CBC(iv: iv),
padding: .pkcs5).encrypt(input)
let decrypt = try AES(key: key, blockMode: CBC(iv: iv),
padding: .pkcs5).decrypt(encrypt)
if let string = String(bytes: decrypt, encoding: .utf8) {
print(string)
}
} catch let error {
print(error)
}
- key是和服務(wù)端約定好的密鑰
- iv是偏移量,這里取Key的前16位,必須和服務(wù)器統(tǒng)一
- input是用來測試的輸入
- 代碼里encrypt和decrypt是加密和解密操作
3.CryptoSwift還包含了md5,sha等很多的加解密方法,功能很完善,可根據(jù)實(shí)際需要選取使用。
三。MD5
import CommonCrypto
extension String {
func MD5() -> String {
let messageData = self.data(using:.utf8)!
var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes {digestBytes in
messageData.withUnsafeBytes {messageBytes in
CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes)
}
}
return digestData.hexString()
}
}
extension Data {
func hexString() -> String {
return self.reduce("") { string, byte in
string + String(format: "%02X", byte)
}
}
}
系統(tǒng)庫CommonCrypto為我們實(shí)現(xiàn)了MD5加密,這里我們通過extension為string添加一個加密的調(diào)用。
值的一提的是,直接對敏感文本MD5的操作已經(jīng)不是那么安全了,因為可以通過反解來暴力解密。那么比較通用的方式是對MD5加鹽。
加鹽的意思就是對敏感文本添加一段文本,再進(jìn)行MD5。添加的這段文本越復(fù)雜越好。
例如:
let input = "password"
let inputSalty = "password" + "asdfghjklpoiuytrewqzxcvbnm"
print(inputSalty.MD5())
當(dāng)然鹽的值如果是固定的,也有一定風(fēng)險,如果鹽被泄漏,那么MD5的安全系數(shù)就會降低,因此可以使用時間戳或隨機(jī)數(shù)作為鹽?;蛘呤荕D5后截取部分再進(jìn)行MD5。
這里注意一點(diǎn)的是。由于MD5是不可逆的,所以當(dāng)傳給服務(wù)器的參數(shù)需要解密時,不應(yīng)該用MD5,而應(yīng)該使用AES這一類的可逆加密方式。