iOS 生成 SecKeyRef 的正規(guī)方式

前言

針對(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浸遗。步驟如下:

  1. 對(duì)于 PEM 編碼的數(shù)據(jù),需要先將多余的信息給剔除箱亿,主要是頭尾兩行 (begin 和 end )以及去掉換行跛锌。
  2. 構(gòu)造一個(gè) attribute 屬性字典,指定密鑰算法(比如 RSA),密鑰格式(公鑰還是私鑰)髓帽,還有密鑰大小
  3. 調(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)單:

  1. 讀取 p12 文件拌牲,當(dāng)然我不推薦你直接將 p12 文件放在 app bundle 中。你可以硬編碼在代碼中歌粥,會(huì)安全一丟丟塌忽。
  2. 設(shè)置參數(shù)字典,主要是設(shè)置你在導(dǎo)出 p12 文件時(shí)候設(shè)置的密碼失驶。
  3. 調(diào)用 SecPKCS12Import 導(dǎo)出 p12 文件包含的 item 數(shù)組
  4. 獲取 item 數(shù)組第一個(gè)元素的字典土居,其中 kSecImportItemIdentity 鍵對(duì)應(yīng)的是值也就是 SecIdentityRef 對(duì)象
  5. 從 SecIdentityRef 中拷出私鑰對(duì)象

如果要拷出公鑰,稍微有點(diǎn)不一樣:

  1. 上面步驟中獲得 item 數(shù)組第一個(gè)元素的字典嬉探,其中 kSecImportItemTrust 鍵對(duì)應(yīng)的是值也就是一個(gè) Trust 對(duì)象擦耀。
  2. 調(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ì)象步驟如下:

  1. 讀取證書文件生成一個(gè) Certificate 對(duì)象(SecCertificateRef 類型)
  2. 從 Certificate 對(duì)象獲取一個(gè) Trust 對(duì)象 (SecTrustRef 類型)
  3. 從 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

  1. certificate 對(duì)象
  2. identity 對(duì)象
  3. trust 對(duì)象
  4. key 對(duì)象
  5. policy 對(duì)象

引用

  1. OpenSSL for iPhone
  2. 蘋果 sample code CryptoExercise
  3. Swift 對(duì)稱密碼使用法
  4. Swift 非對(duì)稱密碼使用法
  5. iOS 上的公鑰 VS OpenSSL 上的公鑰對(duì)比
  6. 密鑰文件等的區(qū)別和轉(zhuǎn)換
  7. iOS 上的 SHA256 with RSA VS JAVA 平臺(tái)
  8. P12续室、證書文件的生成
  9. 蘋果 Security 框架文檔
  10. Objective-C-RSA 開(kāi)源庫(kù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谒养,隨后出現(xiàn)的幾起案子挺狰,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丰泊,死亡現(xiàn)場(chǎng)離奇詭異薯定,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瞳购,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門话侄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人学赛,你說(shuō)我怎么就攤上這事年堆。” “怎么了罢屈?”我有些...
    開(kāi)封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵嘀韧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缠捌,道長(zhǎng),這世上最難降的妖魔是什么译蒂? 我笑而不...
    開(kāi)封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任曼月,我火速辦了婚禮,結(jié)果婚禮上柔昼,老公的妹妹穿的比我還像新娘哑芹。我一直安慰自己,他們只是感情好捕透,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布聪姿。 她就那樣靜靜地躺著,像睡著了一般乙嘀。 火紅的嫁衣襯著肌膚如雪末购。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天虎谢,我揣著相機(jī)與錄音盟榴,去河邊找鬼。 笑死婴噩,一個(gè)胖子當(dāng)著我的面吹牛擎场,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播几莽,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼迅办,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了章蚣?” 一聲冷哼從身側(cè)響起站欺,我...
    開(kāi)封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后镊绪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體匀伏,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年蝴韭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了够颠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡榄鉴,死狀恐怖履磨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庆尘,我是刑警寧澤剃诅,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站驶忌,受9級(jí)特大地震影響矛辕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜付魔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一聊品、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧几苍,春花似錦翻屈、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至刽宪,卻和暖如春厘贼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纠屋。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工涂臣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人售担。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓赁遗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親族铆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子岩四,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容