IOS 逆向開發(一)密碼學 RSA

IOS 逆向開發(一)密碼學 RSA

1. 密碼學發展簡介

密碼學是指研究信息加密,破解密碼的技術科學。密碼學的起源可追溯到2000年前。而當今的密碼學是以數學為基礎的。

發展歷史

  • 密碼學的歷史大致可以追溯到兩千年前,相傳古羅馬名將凱撒大帝為了防止敵方截獲情報,用密碼傳送情報。凱撒的做法很簡單,就是對二十幾個羅馬字母建立一張對應表。這樣,如果不知道密碼本,即使截獲一段信息也看不懂。
    羅馬字母表
  • 從凱撒大帝時代到上世紀70年代這段很長的時間里,密碼學的發展非常的緩慢,因為設計者基本上靠經驗。沒有運用數學原理
  • 在1976年以前,所有的加密方法都是同一種模式:加密、解密使用同一種算法。在交互數據的時候,彼此通信的雙方就必須將規則告訴對方,否則沒法解密。那么加密和解密的規則(簡稱密鑰),它保護就顯得尤其重要。傳遞密鑰就成為了最大的隱患。這種加密方式被成為對稱加密算法(symmetric encryption algorithm)
  • 1976年,兩位美國計算機學家 迪菲(W.Diffie)、赫爾曼(M.Hellman) 提出了一種嶄新構思,可以在不直接傳遞密鑰的情況下,完成密鑰交換。這被稱為“迪菲赫爾曼密鑰交換”算法。開創了密碼學研究的新方向
    密碼學牛人
  • 1977年三位麻省理工學院的數學家 羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起設計了一種算法,可以實現非對稱加密。這個算法用他們三個人的名字命名,叫做RSA算法

2. 非對稱加密RSA產生過程

  • 上世紀70年代產生的一種加密算法。其加密方式比較特殊,需要兩個密鑰:公開密鑰簡稱公鑰(publickey)和私有密鑰簡稱私鑰(privatekey)。公鑰加密,私鑰解密;私鑰加密,公鑰解密。這個加密算法就是偉大的RSA
  • 這種算法非常可靠,密鑰越長,它就越難破解。根據已經披露的文獻,目前被破解的最長 RSA密鑰是 768 個二進制位。也就是說,長度超過 768 位的密鑰,還無法破解(至少沒人公開宣布)。因此可以認為,1024 位的 RSA 密鑰基本安全,2048 位的密鑰極其安全。
    ( 當然 RSA 的缺陷也很容易想到 : 效率相對較低 , 字節長度限制等 . 因此實際應用中我們往往會結合對稱性加密一起使用 , 關鍵內容使用 RSA )

3. RSA 數學原理

3.1 離散對數問題

3.1.1 原根

  • 先從一個問題開始:三的多少次方模 17 等于 12?


    3模17
  • 顯然 , 對于離散對數問題 , 其正向計算得到右邊 12 很簡單. 但是反向運算時 , 就無從下手. 只能窮舉 .

  • 而且當模數使用質數 17 時 , 結果固定在 1 ~ 17 之間. 而當 17 這個模數足夠大時 , 就算知道采用的是這個算法 , 也知道 17 這個質數和答案 , 想要再計算出來上圖中這個問號值 , 可以想象到其難度和計算量有多大 .

  • 如下圖:


    17的原根
  • 從上圖我們可以看出,3的N次方取模17的結果是范圍:從1到16的任意一個數字。這樣3稱為17的原根。

  • 這樣我們得出一個規律用來加密,3 的 N次方,也就是上面圖的
    3模17

    的問號,我們可以用作N(公式中的?號)作為明文,得到密文 12 。這樣即使黑客得到我們在網絡中傳輸的密文12 ,就是他知道這個公式,他也很難反算出我們的明文 N。特別是我們把被模數17改的更大一些,如改為幾百位的數字,那么黑客基本上是不可能通過這個公式反算出我們的明文的。他只能通過不斷試錯的暴力破解方式。

  • 通過上面這個公式反算,計算出明文N的問題叫做離散對數問題

3.2 歐拉函數Φ

先了解一些概念

  • 關于互質關系:如果兩個正整數,除了1以外,沒有其他公因數,我們就稱這兩個數是互質關系(coprime)。

如果一個數N是質數,那么小于N的數都會與N 這個數字互為質數。如N=5,那么1,2,3,4都與5構成互質關系,那么 Φ(5) = 4,表示有4個數與5構成互質關系。

  • 任意給定正整數n,請問在小于等于n的正整數之中,有多少個與n構成互質關系?計算這個值的方式叫做歐拉函數,使用:Φ(n)表示
    • 如: 計算8的歐拉函數,和8互質的 1、2、3、4、5、6、7、8,Φ(8) = 4
    • 計算7的歐拉函數,和7互質的 1、2、3、4、5、6、7,Φ(7) = 6
    • 計算56的歐拉函數,Φ(56) = Φ(8) * Φ(7) = 4 * 6 = 24

通過上面的一些推理,我們不難發現歐拉函數的特點:

  • 歐拉函數特點
    • 一、當n是質數的時候,Φ(n)=n-1。
    • 二、如果n可以分解成兩個互質的整數之積,如n=AB則:Φ(AB)=Φ(A)*Φ(B)
    • 根據以上兩點得到:如果N是兩個質數P1 和 P2的乘積則: Φ(N)=Φ(P1)Φ(P2)=(P1-1)(P2-1)

例如 15 = 3 * 5 ,Φ(53)=Φ(5)Φ(3) , 而 Φ(5) = 4,Φ(3) = 2, 則Φ(53)=Φ(5)Φ(3) = 4 * 2 = 8, 也就是15有8個數與它構成互質關系。

3.3 歐拉定理

  • 歐拉定理:如果兩個正整數m和n互質,那么m的Φ(n)次方減去1,可以被n整除。(m^Φ(n)-1)/n = K(整數)
    歐拉定理公式
  • 費馬小定理:歐拉定理的特殊情況:如果兩個正整數m和n互質,而且n為質數!那么Φ(n)結果就是n-1。(m^(n-1)-1)/n = K(整數)
    費馬小定理

3.4 公式轉換

模反元素:如果兩個正整數e和x互質,那么一定可以找到整數d,使得 ed-1 被x整除。那么d就是e對于x的“模反元素”


模反元素

如上圖所示,轉換過程5步即可:

  1. 首先根據歐拉定理


    首先根據歐拉定理
  2. 由于 1 的 k 次方恒等于 1 , 那么


    由于 1 的 k 次方恒等于 1
  3. 由于 1*m ≡ m , 那么


    1*m ≡ m
  4. 用模反元素轉換,那么換算成公式 就是:


    用模反元素轉換
  5. 轉換一下寫法


    轉換一下寫法
  6. 比較第五步和第三步中紅框部分. 也就是說當 x 等于 Φ(n) 時 :


    比較第五步和第三步中紅框部分

d 是 e 相對于 φ(n) 的模反元素
注意 : 公式推導第一步時 我們歐拉定理的前提是 m 和 n 互質 , 但是由于模反元素的關系 , 其實只要滿足 m < n 上述結果依然成立.

如果上面的這個公式可以拆分為兩次,就可以用來加密。

  • 我們可以在終端使用python來驗證一下:

M = 4, N = 15, φ(n) = 8, e = 3,
d ? 3d -1 = 8
d = (8k+1)/3 -> ( 3, 11)
這里我們可以取d = 11


終端使用python驗證歐拉定理

上面驗證知道,m,n不一定要,只需要m < n即可。

終端使用python來驗證

從終端打印結構可以看出:n = 15 只要 m < n 也就是 m <= 14 無論是否是質數,公式:
比較第五步和第三步中紅框部分

都成立。

  • 然而科學家們一直停留在這個公式階段,直到迪菲赫爾曼密鑰交換出現,通過拆分這個公式實現。

3.5 迪菲赫爾曼密鑰交換

  • 實際場景來看下迪菲赫爾曼密鑰交換過程如下圖:
迪菲赫爾曼密鑰交換-1
  • 客戶端先選一個隨機數13 ,這個數除了客戶端知道,沒有其他任何人知道。
  • 服務器選一個隨機數15, 這個數字除了服務器端,沒有任何知道。
  • 這兩個數字13,15分別只有客戶端和服務器自己知道,不會在網絡上傳輸,所以不會被泄密。
  • 客戶端用3作為根原, 3 的13次方 然后取模 17 (3^13mod 17 = 12),得到12,發給服務器端。
  • 服務器端拿到12后,先將12保存起來,服務器端用同客戶端一樣的算法(3^15mod17 = 6),得到數字6,發給客戶端。
  • 這樣客戶端和服務器端就完成了彼此的密鑰交換。
  • 然后客戶端和服務器分別做如下一次運算:
  • 客戶端拿到服務器發過來的數字6,用同樣的算法,(6^13mod17 = 10), 服務器端用從客戶端拿到的數字12,用同樣的算法(12^ 15mod 17 = 10)同樣也是得到10,這個10 就是客戶端和服務器交換的秘鑰。
  • 這樣網絡上從來就沒有傳輸過秘鑰10,而客戶端和服務器卻通過同樣的算法,計算兩次就得到了密鑰。

3.5.1 數學原理

  • 上面講解的迪菲赫爾曼密鑰交換的數學原理如下圖:


    迪菲赫爾曼密鑰交換-2
  • 實際上客戶端和服務器都做了兩次運算,
  • 客戶端的兩次運算:
  1. 第一次是服務器端做的運算:3^15mod 17 = 6
  2. 第二次是客戶端自己拿到服務器端的6繼續做的一次運算:6^13 mod17 = 10
  3. 第二次運算的6 用第一次的315替換就實際上得到:315^13 mod17 = 10
  • 服務器端的兩次運算:
  1. 第一次是在客戶端做的運算:3^13mod17 = 12
  2. 第二次是拿到客戶端的12繼續做一次運算:12^15mod17 = 10
  3. 第二次運算的12實際上是用313代替:313^15 mod 17 = 10
  • 這樣我們可以清楚的看到:客戶端(31513 mod17 = 10)= 服務器(31315 mod 17 = 10)
  • 那我們把上面的計算過程總結出來就是如下的公式:
原理1

上面的計算套用公式:
如上面服務器端的計算: m=3, e=13, n=17, C=12 (運算公式:3^13mod17 = 12)
實際上就是:m^e mod n = C
然后由于d = 15, (運算公式:12^15 mod 17 = 10)
實際上就是: C^d mod n = m ,由于 C = m ^ e mod n,可以得到 m ^ e ^ d mod n = m, 也就是:m ^ (ed) mod n = m
實際上就是對e
d 進行了拆分,拆分成了兩次運算。

  • 結合我們剛剛第五步之后得出的


    第五步
  • 拆分公式,可以用來加密,解密還原數據:


    加密解密公式

其中 d 是 e 相對于 φ(n) 的模反元素 , 因為 x = Φ(n) , 那么同樣 , e 和 φ(n) 是互質關系

  • 舉例驗證:例如: m = 3 , n = 15 , φ(n) = 8 , e = 3 , d = 11
    通過終端python3驗證:


    加密解密驗證
  • 總結如圖:
    迪菲赫爾曼密鑰交換-2

3.7 RSA的誕生

  • 由上面的迪菲赫爾曼密鑰交換 和我們得出的公式:m ^ (e*d) mod n = m ,兩者結合換算,可以得到加密和解密的公式:
  1. 加密: m ^ e mod n = c, (c 加密的結果,m是明文, e和n就是公鑰,d和n就是私鑰)
  2. 解密:c ^ d mod n = m
  • 公式換算如下圖:


    RSA原理
  1. n 會非常大,長度一般為 1024 個二進制位。(目前人類已經分解的最大整數,232 個十進制位,768 個二進制位)
  2. 由于需要求出 φ(n),所以根據歐函數特點,最簡單的方式 n 由兩個質數相乘得到: 質數:p1、p2 . 那么
    Φ(n) = (p1 -1) * (p2 - 1)
  3. 最終由 φ(n) 得到 e 和 d 。
    總共生成 6 個數字:p1、p2、n、φ(n)、e、d
    其中 n 和 e 組成公鑰 .
    n 和 d 組成私鑰 .
    m 為明文 .
    c為密文 .
  4. 除了公鑰用到了 n 和 e 其余的 4 個數字是不公開的。

3.8 RSA算法

  • 只要滿足d是e相對于Φ(n)的模反元素
  • m小于n


    RSA算法
  • 下面我們通過python來驗證一下:

m ^ e mod n = c 加密
c ^ d mod n = m 解密
我們假設 n = 15 則 φ(n) = φ(15) = 8,
假設 e = 3
假設 d= 19
假設明文 m = 7
先來計算出加密:c = 7 ^ 3 mod 15 = 13
然后解密:13 ^ 19 mod 15 = 7

python驗證RSA加密解密公式
  • RSA算法的特點:
  1. 總共生成 6 個數字:p1、p2、n、φ(n)、e、d
    其中 n 和 e 組成公鑰 .
    n 和 d 組成私鑰 .
    m 為明文 .
    c為密文 .
  2. 除了公鑰用到了 n 和 e 其余的 4 個數字是不公開的。
  3. 黑客要破解實際上就是根據n, 去求φ(n), 而當n比較大時,是很難算出φ(n),φ(n)只能通過試錯的方式去暴力破解(用因式分解方式)。
  4. 要求出φ(n) 目前最大只能計算到232個十進制位,只是運算時間的問題,如果量子計算機真的出來了,因為量子計算理論上運算量是無窮大的,所以可以破解這個φ(n),由于銀行等很多大公司都是用的RSA加密方式,所以量子計算的問世,將會對密碼學產生很大的影響。

3.9 終端演練RSA加密算法

  • Mac的終端可以直接使用OpenSSL進行RSA的命令運行。


    Mac的終端可以直接使用OpenSSL
  • OpenSSL使用RSA
  1. 生成RSA私鑰,秘鑰長度為1024bit
    終端輸入命令:openssl genrsa -out private.pem 1024
    生成RSA私鑰,秘鑰長度為1024bit
  2. 從私鑰中提取公鑰
    終端輸入命令:openssl rsa -in private.pem -pubout -out public.pem
    從私鑰中提取公鑰
  3. 通過上面兩步分別已經生成了公鑰,私鑰文件


    生成公鑰,私鑰文件
  4. 我們查看一下生成的公鑰,私鑰是什么東東


    私鑰內容
  5. 查看一下公鑰內容:


    公鑰內容
  6. 實際上公鑰,私鑰都是經過base64加密的,我們接下來將私鑰轉換成明文查看:
    終端輸入命令:openssl rsa -in private.pem -text -out private.txt
    轉換base的私鑰為明文
  7. 我們查看一下私鑰的明文:
    終端輸入:cat private.txt


    查看私鑰明文

3.9.1 openssl實現rsa加密 ,解密

  • 打開終端,新建一個message.txt文件:vi message.txt
  • 輸入hello,保存


    新建一個message.txt
  • 通過公鑰進行加密:終端輸入:
openssl rsautl -encrypt -in message.txt -inkey public.pem -pubin -out enc.txt
通過公鑰進行加密

加密后的內容hello變成了亂碼了。

  • 通過私鑰進行解密,終端輸入:
penssl rsautl -decrypt -in enc.txt -inkey private.pem -out dec.txt
通過私鑰進行解密

解密后在dec.txt輸出了原來的明文hello

  • 此外我們還可以用私鑰進行加密,公鑰進行解密。
  • 私鑰通過sign進行私鑰加密
  • 終端輸入命令:
penssl rsautl -sign -in message.txt -inkey private.pem -out enc.bin
私鑰進行加密
  • 然后我們用公鑰進行解密,終端輸入:
openssl rsautl -verify -in enc.bin -inkey public.pem -pubin -out dec.txt

解密到dec.txt ,我們可以看到解密后的明文也還原了hello


公鑰進行解密

3.9.2 openssl 提取證書p12文件

  • rsa 由于效率不高,不太適合大的數據加密,一般用來加密關鍵數據,如交換秘鑰用rsa加密,rsa也經常用于加密hash值,也就是我們所說的簽名。
  • 在代碼里面加密我們一般不會直接使用pem文件,一般要提前證書文件
  • 在終端輸入:
openssl req -new -key private.pem -out rsacert.csr

會生成一個.csr文件
其中按提示輸入一些信息,如郵箱,密碼等


在這里插入圖片描述
  • csr文件實際上會去請求一個證書文件,向證書頒發機構頒發一個證書。
  • 頒發證書終端命令:
openssl x509 -req -days 3650 -in rsacert.csr -signkey private.pem -out rsacert.crt
請求頒發證書文件
  • 這樣我們就得到了頒發的證書rsacert.crt文件:


    得到頒發的證書文件
  • 這個頒發(官方認證,證書結構蓋章的)的證書是要收費的,機構一般要收5千元一年,上面我們寫的有效期是10年,意味著要交5萬元,o my gad.
  • 這個證書我們不會直接使用,還需要提前
  • 終端輸入命令:
openssl x509 -outform der -in rsacert.crt -out rsacert.der
提前證書文件der
  • 提取到文件rsacert.der


    提取到文件rsacert.der
  • 這個文件主要包含公鑰和一些必要信息,后面我們就通過這個der生成一個p12文件,
  • p12文件實際上就包含公鑰和私鑰。
  • 接下來,我們到處p12文件。
  • 終端輸入命令
openssl pkcs12 -export -out p.p12 -inkey private.pem -in rsacert.crt
  • 這個時候會提示我們輸入密碼,如下圖:


    提前p12文件
  • 輸入密碼后(需要確認兩次密碼)


    提前p12文件2
  • 這樣我們就提前到了p12文件


    這樣我們就提前到了p12文件
  • 實際上我們就可以用p.p12 和 rsacert.der進行加密和解密


    最終需要的兩個文件

3.9.3 終端base64編碼

  • 我們在rsa文件夾下面有一張kyl.jpg圖片,現在通過終端進行base64編碼


    圖片base64編碼
  • 先終端cd 到rsa這個目錄


    目錄結構
  • 終端輸入編碼命令:
base64 kyl.jpg -o pic.txt
base64編碼圖片
  • 現在我們可以用終端進行base64解碼:
base64 pic.txt -o 123.png -D
解碼base64圖片

解碼后我們得到123.png圖片

解碼后我們得到123.png圖片

4. RSA加密代碼實現

4.1 RSA加密代碼下載

4.2加密代碼講解

  • 新建一個KRSACryptor單例類

KRSACryptor.h文件如下:

//
//  KRSACryptor.h
//  001-KylAppEncrypt
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright ? 2019 Apple. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface KRSACryptor : NSObject

+ (instancetype)shared;
    
    /**
     *  生成密鑰對
     *
     *  @param keySize 密鑰尺寸,可選數值(512/1024/2048)
     */
- (void)generateKeyPair:(NSUInteger)keySize;
    
    /**
     *  加載公鑰
     *
     *  @param publicKeyPath 公鑰路徑
     *
     @code
     # 生成證書
     $ openssl genrsa -out ca.key 1024
     # 創建證書請求
     $ openssl req -new -key ca.key -out rsacert.csr
     # 生成證書并簽名
     $ openssl x509 -req -days 3650 -in rsacert.csr -signkey ca.key -out rsacert.crt
     # 轉換格式
     $ openssl x509 -outform der -in rsacert.crt -out rsacert.der
     @endcode
     */
- (void)loadPublicKey:(NSString *)publicKeyPath;
    
    /**
     *  加載私鑰
     *
     *  @param privateKeyPath p12文件路徑
     *  @param password       p12文件密碼
     *
     @code
     openssl pkcs12 -export -out p.p12 -inkey ca.key -in rsacert.crt
     @endcode
     */
- (void)loadPrivateKey:(NSString *)privateKeyPath password:(NSString *)password;
    
    /**
     *  加密數據
     *
     *  @param plainData 明文數據
     *
     *  @return 密文數據
     */
- (NSData *)encryptData:(NSData *)plainData;
    
    /**
     *  解密數據
     *
     *  @param cipherData 密文數據
     *
     *  @return 明文數據
     */
- (NSData *)decryptData:(NSData *)cipherData;
@end

NS_ASSUME_NONNULL_END

KRSACryptor.m文件如下:

//
//  KRSACryptor.m
//  001-KylAppEncrypt
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright ? 2019 Apple. All rights reserved.
//

#import "KRSACryptor.h"

// 填充模式
#define kTypeOfWrapPadding        kSecPaddingPKCS1

// 公鑰/私鑰標簽
#define kPublicKeyTag            "com.logic.EncryptDemo.publickey"
#define kPrivateKeyTag            "com.logic.EncryptDemo.privatekey"

static const uint8_t publicKeyIdentifier[]        = kPublicKeyTag;
static const uint8_t privateKeyIdentifier[]        = kPrivateKeyTag;

@interface KRSACryptor() {
    SecKeyRef publicKeyRef;                             // 公鑰引用
    SecKeyRef privateKeyRef;                            // 私鑰引用
}
    
    @property (nonatomic, retain) NSData *publicTag;        // 公鑰標簽
    @property (nonatomic, retain) NSData *privateTag;       // 私鑰標簽
    
    @end

@implementation KRSACryptor

    
+ (instancetype)shared {
    static id instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
    
- (instancetype)init {
    self = [super init];
    if (self) {
        // 查詢密鑰的標簽
        _privateTag = [[NSData alloc] initWithBytes:privateKeyIdentifier length:sizeof(privateKeyIdentifier)];
        _publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
    }
    return self;
}
    
#pragma mark - 加密 & 解密數據
- (NSData *)encryptData:(NSData *)plainData {
    OSStatus sanityCheck = noErr;
    size_t cipherBufferSize = 0;
    size_t keyBufferSize = 0;
    
    NSAssert(plainData != nil, @"明文數據為空");
    NSAssert(publicKeyRef != nil, @"公鑰為空");
    
    NSData *cipher = nil;
    uint8_t *cipherBuffer = NULL;
    
    // 計算緩沖區大小
    cipherBufferSize = SecKeyGetBlockSize(publicKeyRef);
    keyBufferSize = [plainData length];
    
    if (kTypeOfWrapPadding == kSecPaddingNone) {
        NSAssert(keyBufferSize <= cipherBufferSize, @"加密內容太大");
    } else {
        NSAssert(keyBufferSize <= (cipherBufferSize - 11), @"加密內容太大");
    }
    
    // 分配緩沖區
    cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
    memset((void *)cipherBuffer, 0x0, cipherBufferSize);
    
    // 使用公鑰加密
    sanityCheck = SecKeyEncrypt(publicKeyRef,
                                kTypeOfWrapPadding,
                                (const uint8_t *)[plainData bytes],
                                keyBufferSize,
                                cipherBuffer,
                                &cipherBufferSize
                                );
    
    NSAssert(sanityCheck == noErr, @"加密錯誤,OSStatus == %d", sanityCheck);
    
    // 生成密文數據
    cipher = [NSData dataWithBytes:(const void *)cipherBuffer length:(NSUInteger)cipherBufferSize];
    
    if (cipherBuffer) free(cipherBuffer);
    
    return cipher;
}
    
- (NSData *)decryptData:(NSData *)cipherData {
    OSStatus sanityCheck = noErr;
    size_t cipherBufferSize = 0;
    size_t keyBufferSize = 0;
    
    NSData *key = nil;
    uint8_t *keyBuffer = NULL;
    
    SecKeyRef privateKey = NULL;
    
    privateKey = [self getPrivateKeyRef];
    NSAssert(privateKey != NULL, @"私鑰不存在");
    
    // 計算緩沖區大小
    cipherBufferSize = SecKeyGetBlockSize(privateKey);
    keyBufferSize = [cipherData length];
    
    NSAssert(keyBufferSize <= cipherBufferSize, @"解密內容太大");
    
    // 分配緩沖區
    keyBuffer = malloc(keyBufferSize * sizeof(uint8_t));
    memset((void *)keyBuffer, 0x0, keyBufferSize);
    
    // 使用私鑰解密
    sanityCheck = SecKeyDecrypt(privateKey,
                                kTypeOfWrapPadding,
                                (const uint8_t *)[cipherData bytes],
                                cipherBufferSize,
                                keyBuffer,
                                &keyBufferSize
                                );
    
    NSAssert1(sanityCheck == noErr, @"解密錯誤,OSStatus == %d", sanityCheck);
    
    // 生成明文數據
    key = [NSData dataWithBytes:(const void *)keyBuffer length:(NSUInteger)keyBufferSize];
    
    if (keyBuffer) free(keyBuffer);
    
    return key;
}
    
#pragma mark - 密鑰處理
    /**
     *  生成密鑰對
     */
- (void)generateKeyPair:(NSUInteger)keySize {
    OSStatus sanityCheck = noErr;
    publicKeyRef = NULL;
    privateKeyRef = NULL;
    
    NSAssert1((keySize == 512 || keySize == 1024 || keySize == 2048), @"密鑰尺寸無效 %tu", keySize);
    
    // 刪除當前密鑰對
    [self deleteAsymmetricKeys];
    
    // 容器字典
    NSMutableDictionary *privateKeyAttr = [[NSMutableDictionary alloc] init];
    NSMutableDictionary *publicKeyAttr = [[NSMutableDictionary alloc] init];
    NSMutableDictionary *keyPairAttr = [[NSMutableDictionary alloc] init];
    
    // 設置密鑰對的頂級字典
    [keyPairAttr setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    [keyPairAttr setObject:[NSNumber numberWithUnsignedInteger:keySize] forKey:(__bridge id)kSecAttrKeySizeInBits];
    
    // 設置私鑰字典
    [privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
    [privateKeyAttr setObject:_privateTag forKey:(__bridge id)kSecAttrApplicationTag];
    
    // 設置公鑰字典
    [publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
    [publicKeyAttr setObject:_publicTag forKey:(__bridge id)kSecAttrApplicationTag];
    
    // 設置頂級字典屬性
    [keyPairAttr setObject:privateKeyAttr forKey:(__bridge id)kSecPrivateKeyAttrs];
    [keyPairAttr setObject:publicKeyAttr forKey:(__bridge id)kSecPublicKeyAttrs];
    
    // SecKeyGeneratePair 返回密鑰對引用
    sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef);
    NSAssert((sanityCheck == noErr && publicKeyRef != NULL && privateKeyRef != NULL), @"生成密鑰對失敗");
}
    
    /**
     *  加載公鑰
     */
- (void)loadPublicKey:(NSString *)publicKeyPath {
    
    NSAssert(publicKeyPath.length != 0, @"公鑰路徑為空");
    
    // 刪除當前公鑰
    if (publicKeyRef) CFRelease(publicKeyRef);
    
    // 從一個 DER 表示的證書創建一個證書對象
    NSData *certificateData = [NSData dataWithContentsOfFile:publicKeyPath];
    SecCertificateRef certificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)certificateData);
    NSAssert(certificateRef != NULL, @"公鑰文件錯誤");
    
    // 返回一個默認 X509 策略的公鑰對象,使用之后需要調用 CFRelease 釋放
    SecPolicyRef policyRef = SecPolicyCreateBasicX509();
    // 包含信任管理信息的結構體
    SecTrustRef trustRef;
    
    // 基于證書和策略創建一個信任管理對象
    OSStatus status = SecTrustCreateWithCertificates(certificateRef, policyRef, &trustRef);
    NSAssert(status == errSecSuccess, @"創建信任管理對象失敗");
    
    // 信任結果
    SecTrustResultType trustResult;
    // 評估指定證書和策略的信任管理是否有效
    status = SecTrustEvaluate(trustRef, &trustResult);
    NSAssert(status == errSecSuccess, @"信任評估失敗");
    
    // 評估之后返回公鑰子證書
    publicKeyRef = SecTrustCopyPublicKey(trustRef);
    NSAssert(publicKeyRef != NULL, @"公鑰創建失敗");
    
    if (certificateRef) CFRelease(certificateRef);
    if (policyRef) CFRelease(policyRef);
    if (trustRef) CFRelease(trustRef);
}
    
    /**
     *  加載私鑰
     */
- (void)loadPrivateKey:(NSString *)privateKeyPath password:(NSString *)password {
    
    NSAssert(privateKeyPath.length != 0, @"私鑰路徑為空");
    
    // 刪除當前私鑰
    if (privateKeyRef) CFRelease(privateKeyRef);
    
    NSData *PKCS12Data = [NSData dataWithContentsOfFile:privateKeyPath];
    CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
    CFStringRef passwordRef = (__bridge CFStringRef)password;
    
    // 從 PKCS #12 證書中提取標示和證書
    SecIdentityRef myIdentity;
    SecTrustRef myTrust;
    const void *keys[] =   {kSecImportExportPassphrase};
    const void *values[] = {passwordRef};
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    
    // 返回 PKCS #12 格式數據中的標示和證書
    OSStatus status = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
    
    if (status == noErr) {
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
        myIdentity = (SecIdentityRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
        myTrust = (SecTrustRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
    }
    
    if (optionsDictionary) CFRelease(optionsDictionary);
    
    NSAssert(status == noErr, @"提取身份和信任失敗");
    
    SecTrustResultType trustResult;
    // 評估指定證書和策略的信任管理是否有效
    status = SecTrustEvaluate(myTrust, &trustResult);
    NSAssert(status == errSecSuccess, @"信任評估失敗");
    
    // 提取私鑰
    status = SecIdentityCopyPrivateKey(myIdentity, &privateKeyRef);
    NSAssert(status == errSecSuccess, @"私鑰創建失敗");
}
    
    /**
     *  刪除非對稱密鑰
     */
- (void)deleteAsymmetricKeys {
    OSStatus sanityCheck = noErr;
    NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
    NSMutableDictionary *queryPrivateKey = [[NSMutableDictionary alloc] init];
    
    // 設置公鑰查詢字典
    [queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
    [queryPublicKey setObject:_publicTag forKey:(__bridge id)kSecAttrApplicationTag];
    [queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    
    // 設置私鑰查詢字典
    [queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
    [queryPrivateKey setObject:_privateTag forKey:(__bridge id)kSecAttrApplicationTag];
    [queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    
    // 刪除私鑰
    sanityCheck = SecItemDelete((__bridge CFDictionaryRef)queryPrivateKey);
    NSAssert1((sanityCheck == noErr || sanityCheck == errSecItemNotFound), @"刪除私鑰錯誤,OSStatus == %d", sanityCheck);
    
    // 刪除公鑰
    sanityCheck = SecItemDelete((__bridge CFDictionaryRef)queryPublicKey);
    NSAssert1((sanityCheck == noErr || sanityCheck == errSecItemNotFound), @"刪除公鑰錯誤,OSStatus == %d", sanityCheck);
    
    if (publicKeyRef) CFRelease(publicKeyRef);
    if (privateKeyRef) CFRelease(privateKeyRef);
}
    
    /**
     *  獲得私鑰引用
     */
- (SecKeyRef)getPrivateKeyRef {
    OSStatus sanityCheck = noErr;
    SecKeyRef privateKeyReference = NULL;
    
    if (privateKeyRef == NULL) {
        NSMutableDictionary * queryPrivateKey = [[NSMutableDictionary alloc] init];
        
        // 設置私鑰查詢字典
        [queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
        [queryPrivateKey setObject:_privateTag forKey:(__bridge id)kSecAttrApplicationTag];
        [queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
        [queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
        
        // 獲得密鑰
        sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKeyReference);
        
        if (sanityCheck != noErr) {
            privateKeyReference = NULL;
        }
    } else {
        privateKeyReference = privateKeyRef;
    }
    
    return privateKeyReference;
}
@end

4.2.2 測試驗證

  • 工程目錄如下:
工程目錄
  • 測試代碼如下:
- (void) testRSAEncrpt {
    //1.加載公鑰
    [[KRSACryptor shared] loadPublicKey:[[NSBundle mainBundle] pathForResource:@"rsacert.der" ofType:nil]];
    //2.加載私鑰
    [[KRSACryptor shared] loadPrivateKey: [[NSBundle mainBundle] pathForResource:@"p.p12" ofType:nil] password:@"123456"];
}

static void my_encrypt(){
    NSData * result = [[KRSACryptor shared] encryptData:[@"hello" dataUsingEncoding:NSUTF8StringEncoding]];
    //base64編碼
    NSString * base64 = [result base64EncodedStringWithOptions:0];
    NSLog(@"加密之后:%@\n",base64);
    
    //解密
    NSData * dcStr = [[KRSACryptor shared] decryptData:result];
    NSLog(@"%@",[[NSString alloc] initWithData:dcStr encoding:NSUTF8StringEncoding]);
}

  • 打印結果如下:
打印結果
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容