前言
針對(duì) macOS 的開(kāi)發(fā)泳赋,多年以前蘋果就棄用了 OpenSSL爸舒,轉(zhuǎn)而推薦自有框架 Security 和 CommonCrypto臣淤。當(dāng)然你仍然可以使用 OpenSSL,比如說(shuō)在 iOS 上使用開(kāi)源庫(kù) OpenSSL for iPhone晌姚。
蘋果有一套自己的方式來(lái)生成各種密鑰(對(duì)稱加密、非對(duì)稱加密)歇竟,你可以查看蘋果的 sample code CryptoExercise挥唠,來(lái)了解如何在蘋果自有平臺(tái)(macOS、iOS焕议、 tvOS 等等)上使用這一套機(jī)制宝磨。
OpenSSL 是被廣泛使用的生成公私鑰對(duì)以及各類證書等文件的方式,比如生成 PEM 或者 DER 后綴的文件(前者是 Base64 編碼盅安,或者則是 DER 編碼的內(nèi)容唤锉,如何包含的只是公鑰或者私鑰的話,本質(zhì)上就沒(méi)有區(qū)別)别瞭。但是在 iOS 并沒(méi)有原生支持讀取只包含公鑰或者私鑰的方法(iOS 10 之后可以使用 SecKeyCreateWithData 來(lái)生成) 窿祥。
在 iOS 上, SecKeyRef 對(duì)象是一個(gè)密碼學(xué)角度的抽象的密鑰對(duì)象(也就是說(shuō)它可以代表一個(gè)公鑰蝙寨、私鑰或者某種對(duì)稱加密的密鑰)晒衩。所以如何生成這樣一個(gè)對(duì)象就顯得格外重要,因?yàn)闊o(wú)論是加解密還是簽名籽慢,都會(huì)需要這個(gè)對(duì)象
原生生成公私鑰對(duì)象的一種通用方式 (僅限 iOS 10 及以上)
蘋果從 iOS 10 開(kāi)始支持直接從公私鑰數(shù)據(jù)來(lái)生成 SecKeyRef浸遗。步驟如下:
- 對(duì)于 PEM 編碼的數(shù)據(jù),需要先將多余的信息給剔除箱亿,主要是頭尾兩行 (begin 和 end )以及去掉換行跛锌。
- 構(gòu)造一個(gè) attribute 屬性字典,指定密鑰算法(比如 RSA),密鑰格式(公鑰還是私鑰)髓帽,還有密鑰大小
- 調(diào)用 SecKeyCreateWithData菠赚,返回一個(gè) SecKeyRef
下面是具體代碼:
SecKeyRef getPrivateKeyFromPem() {
// 下面是對(duì)于 PEM 格式的密鑰文件的密鑰多余信息的處理,通常 DER 不需要這一步
NSString *key = @"PEM 格式的密鑰文件";
NSRange spos;
NSRange epos;
spos = [key rangeOfString:@"-----BEGIN RSA PRIVATE KEY-----"];
if(spos.length > 0){
epos = [key rangeOfString:@"-----END RSA PRIVATE KEY-----"];
}else{
spos = [key rangeOfString:@"-----BEGIN PRIVATE KEY-----"];
epos = [key rangeOfString:@"-----END PRIVATE KEY-----"];
}
if(spos.location != NSNotFound && epos.location != NSNotFound){
NSUInteger s = spos.location + spos.length;
NSUInteger e = epos.location;
NSRange range = NSMakeRange(s, e-s);
key = [key substringWithRange:range];
}
key = [key stringByReplacingOccurrencesOfString:@"\r" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"\n" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"\t" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@" " withString:@""];
// This will be base64 encoded, decode it.
NSData *data = base64_decode(key);
if(!data){
return nil;
}
// 設(shè)置屬性字典
NSMutableDictionary *options = [NSMutableDictionary dictionary];
options[(__bridge id)kSecAttrKeyType] = (__bridge id) kSecAttrKeyTypeRSA;
options[(__bridge id)kSecAttrKeyClass] = (__bridge id) kSecAttrKeyClassPrivate;
NSNumber *size = @2048;
options[(__bridge id)kSecAttrKeySizeInBits] = size;
NSError *error = nil;
CFErrorRef ee = (__bridge CFErrorRef)error;
// 調(diào)用接口獲取密鑰對(duì)象
SecKeyRef ret = SecKeyCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options, &ee);
if (error) {
return nil;
}
return ret;
}
原生生成公私鑰對(duì)象的一種通用方式 (iOS 9 及以前)
針對(duì) iOS 10 以前的版本郑藏,想要獲取私鑰的正規(guī)途徑是通過(guò) P12(亦即 PKCS #12) 文件獲群獠椤(P12 是同時(shí)包含公私鑰的文件,同時(shí)需要一個(gè)對(duì)稱密碼來(lái)使用 p12 文件)必盖,步驟也很簡(jiǎn)單:
- 讀取 p12 文件拌牲,當(dāng)然我不推薦你直接將 p12 文件放在 app bundle 中。你可以硬編碼在代碼中歌粥,會(huì)安全一丟丟塌忽。
- 設(shè)置參數(shù)字典,主要是設(shè)置你在導(dǎo)出 p12 文件時(shí)候設(shè)置的密碼失驶。
- 調(diào)用 SecPKCS12Import 導(dǎo)出 p12 文件包含的 item 數(shù)組
- 獲取 item 數(shù)組第一個(gè)元素的字典土居,其中 kSecImportItemIdentity 鍵對(duì)應(yīng)的是值也就是 SecIdentityRef 對(duì)象
- 從 SecIdentityRef 中拷出私鑰對(duì)象
如果要拷出公鑰,稍微有點(diǎn)不一樣:
- 上面步驟中獲得 item 數(shù)組第一個(gè)元素的字典嬉探,其中 kSecImportItemTrust 鍵對(duì)應(yīng)的是值也就是一個(gè) Trust 對(duì)象擦耀。
- 調(diào)用 SecTrustCopyPublicKey 獲取公鑰對(duì)象
下面是代碼解釋:
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"rsaPrivate" ofType:@"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKeyRef = NULL;
id publicKey = NULL;
// 改成你設(shè)置的密碼
[options setObject:@"" forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((CFDataRef) p12Data, (CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
// 獲取一個(gè) Identity 對(duì)象
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
// 獲取私鑰
SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
// 獲取一個(gè) Trust 對(duì)象
SecTrustRef trustRef = (SecTrustRef)CFDictionaryGetValue(identityDict, kSecImportItemTrust);
// 獲取公鑰
publicKey = (__bridge_transfer id)SecTrustCopyPublicKey(trustRef);
}
CFRelease(items);
從證書文件讀取公鑰對(duì)象
從證書文件讀取公鑰對(duì)象步驟如下:
- 讀取證書文件生成一個(gè) Certificate 對(duì)象(SecCertificateRef 類型)
- 從 Certificate 對(duì)象獲取一個(gè) Trust 對(duì)象 (SecTrustRef 類型)
- 從 Trust 對(duì)象拷貝出公鑰 (這一步可以先根據(jù) Trust 對(duì)象來(lái)判斷證書是否可信)
代碼解釋如下:
id publicKey = nil;
SecCertificateRef certificate;
SecCertificateRef certificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef trust = nil;
SecTrustResultType result;
certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateDate);
if (certificate) {
certificates[0] = certificate;
tempCertificates = CFArrayCreate(NULL, (const void **)certificates, 1, NULL);
policy = SecPolicyCreateBasicX509();
SecTrustCreateWithCertificates(tempCertificates, policy, &trust);
SecTrustEvaluate(trust, &result);
// 獲得公鑰對(duì)象
publicKey = (__bridge_transfer id)SecTrustCopyPublicKey(trust);
}
if (trust) {
CFRelease(trust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (certificate) {
CFRelease(certificate);
}
如何從密鑰文件生成 P12 和證書等
你可以參考這個(gè) SO How can I get SecKeyRef from DER/PEM file
開(kāi)源庫(kù)
Objective-C-RSA 這個(gè)開(kāi)源庫(kù)源碼解釋了如何自己處理 PEM 格式密鑰文件的頭,但是由于解析力不夠強(qiáng)涩堤,經(jīng)常會(huì)返回一個(gè)空的密鑰對(duì)象眷蜓。所以必要時(shí)候可以參考一下。但是不太推薦定躏。筆者對(duì) ASN.1 等概念不太熟悉账磺,這里不過(guò)多討論了。
使用 OpenSSL
使用 OpenSSL 無(wú)法生成 SecKeyRef 密鑰對(duì)象痊远,但是 OpenSSL 提供了完整的密碼學(xué)各類操作支持(加密垮抗,加簽,解密碧聪,驗(yàn)簽等)冒版,所以你完全可以不需要蘋果的 Security 框架。你可以參考 支付寶的 Demo逞姿,了解如何使用該庫(kù)辞嗡。開(kāi)源代碼地址是 OpenSSL for iPhone
iOS 上關(guān)于加密等密碼學(xué)操作的建議
蘋果的官方文檔 蘋果 Security 框架文檔 完整的描述了如何在蘋果自有平臺(tái)使用 Security 框架。你可以參考它滞造。
主要是理解幾個(gè)對(duì)象:(文檔地址 Certificate, Key, and Trust Services)
- certificate 對(duì)象
- identity 對(duì)象
- trust 對(duì)象
- key 對(duì)象
- policy 對(duì)象