iOS 資安 Keychain RSA加密演算法

使用Keychain與RSA加密演算法為敏感性資料加防

Morgan 2019/06/18 10:28:18
2564

ㄧ、先來說說RSA加密演算法

根據維基百科的定義與闡述,RSA加密演算法是一種非對稱加密演算法,屬於公開金鑰密碼學的一種,它需要兩個金鑰,一個是公開密鑰(用作加密),可任意向外發布;另一個是私有密鑰(用作解密),必須由用戶自行嚴格秘密保管。使用公鑰把明文加密後所得的密文,只能用相對應的私鑰才能解密得到原本的明文,因加密和解密需要兩個不同的密鑰,故被稱為非對稱加密。

簡單用圖示來解說,專案經理的電腦有一組公鑰跟私鑰,有天需要與工程師之間做加密的訊息交流,所以提供加密的公鑰給工程師,自己則保留解密的私鑰。

工程師拿到專案經理的公鑰後,透過公鑰將訊息加密,傳遞加密後的訊息回傳給專案經理。

 

 

專案經理收到加密訊息之後,再拿與公鑰同一組配對的私鑰,將訊息解密!

如果加密的訊息被有心人士從中攔截、盜取,沒有同一組配對的私鑰,要破解加密訊息是極其困難的!

 

除了私鑰的嚴密保管,傳送、分發公鑰的過程也是非常重要!過程中必須能夠抵擋中間人攻擊。假設有個情境,A取得了BC之間的訊息加密公鑰,A如果交給B自己的公鑰,並使B相信這是C的公鑰,則A可從中攔截B的密文,並用自己的私鑰解密、讀取後,再用C的公鑰重新加密明文並且回送密文給C,則B跟C會在不知情的情況下被A監聽。目前的對策是透過可靠的第三方機構簽發憑證來防止這類型的攻擊。

 

二、接下來說說Keychain

 

Keychain是iOS將私密資料安全保存在行動裝置、獨立於App沙盒(Sandbox)之外的加密資料庫,而本文的另一個重點,就是透過Keychain引入資安防護的「白箱加密」機制。

 一般開發者常將加解密演算法的金鑰處理方式以硬碼(Hard code)的方式寫在程式碼中,或者儲存於行動裝置的內部儲存空間Hard code在程式碼中,則有程式碼隱蔽性的風險;若儲存於內部儲存空間,則有密鑰被直接取得的風險,有心人士可以進而透過分析程式碼或記憶體內容取得加解密金鑰。

然而,Keychain可以幫我們一次同時處理掉兩個問題!既可以不用Hardcode加解密演算法的金鑰處理方式,直接從Keychain生成金鑰給我們,同時也將金鑰直接儲存在Keychain,兩道白箱加密的防護,致使有心人士無法藉由複製或觀察的方式,在其他應用程式或行動裝置上取得相同的密鑰,進而提高攻擊的門檻跟障礙!

 

三、實作

 

接下來要實作程式的部分,首先新建一個Single View的專案,並且新增一支Helper classswift檔:

KeychainHelper有四個屬性,「privateKey」、「publicKey」、「tag」與「algorithm」,privateKeypublicKey即為我們RSA加密演算法需要的私鑰跟公鑰,型別皆為SecKey,也就是我們儲存在Keychain中的「key」,我們可以觀察到密鑰的生成順序為:先產出私鑰,該把私鑰再透過SecKeyCopyPublicKey生成對應的配對公鑰。筆者將privateKeypublicKey宣告成延遲加載(lazy),至於生成privateKey需要的幾支method,我們待會會再談到。

另外兩個屬性,tag是我們的keyKeychain中可被辨識的唯一值,型別為Data,故我們像定義BundleId一樣,定義一個唯一值的常數字串,並用UTF-8編碼成Dataalgorithm則是實際加解密資料時採用的演算法,筆者根據Google CloudRSA金鑰演算法建議,選擇採用「OAEPSHA256」演算法,有興趣的工程師可以自行參考相關文獻與資料:

 

接下來我們再回到生成privateKey需要的幾支method,「getPrivateKey」、「createPrivateKey」跟「deletePrivateKey」,跟操作資料庫一樣,我們也是在Keychain中操作CRUD。首先,我們先確認唯一值為tag的私鑰在Keychain中存不存在,在「getPrivateKey」的method中,我們會調用SecItemCopyMatching,需要一組搜尋參數的Dictionary4個組成,kSecClass我們設置為kSecClassKey,表示是儲存在Keychain中的key,kSecAttrApplicationTag,也就是我們私鑰的唯一值,設成tag, 由於我們是採用RSA加密演算法,kSecAttrKeyType則設成kSecAttrKeyTypeRSA, 最後kSecReturnRef設成true,表示搜尋成功後要回傳私鑰給我們!

如果Keychain中找不到我們定義的tag的私鑰,則新建一把新的私鑰,我們調用SecKeyCreateRandomKey,同樣也需要一組Dictionary的參數,4個組成,kSecAttrType我們設置為kSecAttrKeyTypeRSA,表示生成的keyRSA加密演算法的私鑰,而key的長度kSecAttrKeySizeInBits我們設成2048位元,至於一直提到的kSecAttrApplicationTag也是設成我們定義的tag,最後的kSecPrivateKeyAttrs我們再提供一組Dictionary參數,其中kSecAttrIsPermanent設成true,表示成功生成私鑰後,直接把私鑰儲存在Keychain!這也是本文的重點所在,呼應前幾段提到的「白箱加密」,不會有HardCode程式碼隱蔽性的風險,也被儲存在行動裝置的特定防護空間!

最後的deletePrivateKey,是做一個清空的動作,我們可以放在新增私鑰之前調用,確認同一組tagkeyKeychain中是乾淨的!我們會調用SecItemDelete,參數Dictionary只需要2個組成,在前兩支method中也有看過,將kSecClasskSecAttrApplicationTag各自設置為kSecClassKeytag

接下來我們來完成實際負責加、解密的method,先看看加密,前面有提到,公鑰負責加密,私鑰負責解密,所以我們需要公鑰當作參數,在檢查公鑰存不存在、檢查演算法支不支援、檢查欲加密的字串能否UTF-8編碼成Data後,調用SecKeyCreateEncryptedDatamethod,需要的參數為「公鑰」、「演算法」、「欲加密的資料」以及「可選的錯誤」,如果加密成功的話,回傳加密的Data

檢查支不支援演算法,調用SecKeyIsAlgorithmSupported

解密的寫法跟加密的寫法也是大同小異,差異的地方在於調用的methodSecKeyCreateDecryptedData,需要的參數是私鑰,不過要注意的是加解密的演算法必須一致!不能加密用rsaEncryptionOAEPSHA256,解密用rsaEncryptionOAEPSHA1

最後我們來測試看看,為了方便觀察結果起見,我們同時在同一支method做加、解密的動作:

Morgan