iOS 簽名機制挺複雜,各類證書,Provisioning Profile,entitlements,CertificateSigningRequest,p12,AppID,概念一堆,也很容易出錯,本文嘗試從原理出發,一步步推出爲何會有這麼多概念,但願能有助於理解 iOS App 簽名的原理和流程。 算法
先來看看蘋果的簽名機制是爲了作什麼。在 iOS 出來以前,在主流操做系統(Mac/Windows/Linux)上開發和運行軟件是不須要簽名的,軟件隨便從哪裏下載都能運行,致使平臺對第三方軟件難以控制,盜版流行。蘋果但願解決這樣的問題,在 iOS 平臺對第三方 App 有絕對的控制權,必定要保證每個安裝到 iOS 上的 App 都是通過蘋果官方容許的,怎樣保證呢?就是經過簽名機制。安全
一般咱們說的簽名就是數字簽名,它是基於非對稱加密算法實現的。對稱加密是經過同一份密鑰加密和解密數據,而非對稱加密則有兩份密鑰,分別是公鑰和私鑰,用公鑰加密的數據,要用私鑰才能解密,用私鑰加密的數據,要用公鑰才能解密。 簡單說一下經常使用的非對稱加密算法 RSA 的數學原理,理解簡單的數學原理,就能夠理解非對稱加密是怎麼作到的,爲何會是安全的: 服務器
1. 選兩個質數 p 和 q,相乘得出一個大整數 n,例如 p = 61,q = 53,n = pq = 3233; markdown
2.選 1-n 間的隨便一個質數 e,例如 e = 17;網絡
3. 通過一系列數學公式,算出一個數字 d,知足:測試
經過 n 和 e 這兩個數據一組數據進行數學運算後,能夠經過 n 和 d 去反解運算,反過來也能夠。 網站
若是隻知道 n 和 e,要推導出 d,須要知道 p 和 q,也就是要須要把 n 因數分解。加密
上述的 (n,e) 這兩個數據在一塊兒就是公鑰,(n,d) 這兩個數據就是私鑰,知足用私鑰加密,公鑰解密,或反過來公鑰加密,私鑰解密,也知足在只暴露公鑰 (只知道 n 和 e)的狀況下,要推導出私鑰 (n,d),須要把大整數 n 因數分解。目前因數分解只能靠暴力窮舉,而 n 數字越大,越難以用窮舉計算出因數 p 和 q,也就越安全,當 n 大到二進制 1024 位或 2048 位時,以目前技術要破解幾乎不可能,因此很是安全。 若對數字 d 是怎樣計算出來的感興趣,能夠詳讀這兩篇文章:RSA 算法原理(一)、(二)。spa
如今知道了有非對稱加密這東西,那數字簽名是怎麼回事呢?操作系統
數字簽名的做用是我對某一份數據打個標記,表示我承認了這份數據(簽了個名),而後我發送給其餘人,其餘人能夠知道這份數據是通過我認證的,數據沒有被篡改過。
有了上述非對稱加密算法,就能夠實現這個需求:
首先用一種算法,算出原始數據的摘要。需知足
若原始數據有任何變化,計算出來的摘要值都會變化。
摘要要夠短。這裏最經常使用的算法是 MD5。 生成一份非對稱加密的公鑰和私鑰,私鑰我本身拿着,公鑰公佈出去。
對一份數據,算出摘要後,用私鑰加密這個摘要,獲得一份加密後的數據,稱爲原始數據的簽名。把它跟原始數據一塊兒發送給用戶。
用戶收到數據和簽名後,用公鑰解密獲得摘要。同時用戶用一樣的算法計算原始數據的摘要,對比這裏計算出來的摘要和用公鑰解密簽名獲得的摘要是否相等,若相等則表示這份數據中途沒有被篡改過,由於若是篡改過,摘要會變化。
之因此要有第一步計算摘要,是由於非對稱加密的原理限制可加密的內容不能太大(不能大於上述 n 的位數,也就是通常不能大於 1024 位 / 2048 位),因而若要對任意大的數據簽名,就須要改爲對它的特徵值簽名,效果是同樣的。
好了,有了非對稱加密的基礎,知道了數字簽名是什麼,怎樣能夠保證一份數據是通過某個地方認證的,來看看怎樣經過數字簽名的機制保證每個安裝到 iOS 上的 App 都是通過蘋果認證容許的。
要實現這個需求很簡單,最直接的方式,蘋果官方生成一對公私鑰,在 iOS 裏內置一個公鑰,私鑰由蘋果後臺保存,咱們傳 App 上 App Store 時,蘋果後臺用私鑰對 App 數據進行簽名,iOS 系統下載這個 App 後,用公鑰驗證這個簽名,若簽名正確,這個 App 確定是由蘋果後臺認證的,而且沒有被修改過,也就達到了蘋果的需求:保證安裝的每個 App 都是通過蘋果官方容許的。
若是咱們 iOS 設備安裝 App 只有從 App Store 下載這一種方式的話,這件事就結束了,沒有任何複雜的東西,只有一個數字簽名,很是簡單地解決問題。
但實際上由於除了從 App Store 下載,咱們還能夠有三種方式安裝一個 App:
蘋果要對用這三種方式安裝的 App 進行控制,就有了新的需求,沒法像上面這樣簡單了。
咱們先來看第一個,開發時安裝 App,它有兩個個需求:
安裝包不須要傳到蘋果服務器,能夠直接安裝到手機上。若是你編譯一個 App 到手機前要先傳到蘋果服務器簽名,這顯然是不能接受的。
蘋果必須對這裏的安裝有控制權,包括: 通過蘋果容許才能夠這樣安裝; 不能被濫用致使非開發 App 也能被安裝。
爲了實現這些需求,iOS 簽名的複雜度也就開始增長了。 蘋果這裏給出的方案是使用了雙層簽名,會比較繞,流程大概是這樣的:
在你的 Mac 開發機器生成一對公私鑰,這裏稱爲公鑰 L,私鑰 L。L:Local
蘋果本身有固定的一對公私鑰,跟上面 App Store 例子同樣,私鑰在蘋果後臺,公鑰在每一個 iOS 設備上。這裏稱爲公鑰 A,私鑰 A。A:Apple
把公鑰 L 傳到蘋果後臺,用蘋果後臺裏的私鑰 A 去簽名公鑰 L。獲得一份數據包含了公鑰 L 以及其簽名,把這份數據稱爲證書。
在開發時,編譯完一個 App 後,用本地的私鑰 L 對這個 App 進行簽名,同時把第三步獲得的證書一塊兒打包進 App 裏,安裝到手機上。
在安裝時,iOS 系統取得證書,經過系統內置的公鑰 A,去驗證證書的數字簽名是否正確。
驗證證書後確保了公鑰 L 是蘋果認證過的,再用公鑰 L 去驗證 App 的簽名,這裏就間接驗證了這個 App 安裝行爲是否通過蘋果官方容許。(這裏只驗證安裝行爲,不驗證 App 是否被改動,由於開發階段 App 內容老是不斷變化的,蘋果不須要管。)
上述流程只解決了上面第一個需求,也就是須要通過蘋果容許才能夠安裝,還未解決第二個避免被濫用的問題。怎麼解決呢?蘋果再加了兩個限制,一是限制在蘋果後臺註冊過的設備才能夠安裝,二是限制簽名只能針對某一個具體的 App。
怎麼加的?在上述第三步,蘋果用私鑰 A 簽名咱們本地公鑰 L 時,實際上除了簽名公鑰 L,還能夠加上無限多數據,這些數據均可以保證是通過蘋果官方認證的,不會有被篡改的可能。
能夠想到把 容許安裝的設備 ID 列表 和 App 對應的 AppID 等數據,都在第三步這裏跟公鑰 L 一塊兒組成證書,再用蘋果私鑰 A 對這個證書籤名。在最後第 5 步驗證時就能夠拿到設備 ID 列表,判斷當前設備是否符合要求。根據數字簽名的原理,只要數字簽名經過驗證,第 5 步這裏的設備 IDs / AppID / 公鑰 L 就都是通過蘋果認證的,沒法被修改,蘋果就能夠限制可安裝的設備和 App,避免濫用。
到這裏這個證書已經變得很複雜了,有不少額外信息,實際上除了 設備 ID / AppID,還有其餘信息也須要在這裏用蘋果簽名,像這個 App 裏 iCloud / push / 後臺運行 等權限蘋果都想控制,蘋果把這些權限開關統一稱爲 Entitlements,它也須要經過簽名去受權。
實際上一個「證書」原本就有規定的格式規範,上面咱們把各類額外信息塞入證書裏是不合適的,因而蘋果另外搞了個東西,叫 Provisioning Profile,一個 Provisioning Profile 裏就包含了證書以及上述提到的全部額外信息,以及全部信息的簽名。
因此整個流程稍微變一下,就變成這樣了:
由於步驟有小變更,這裏咱們不辭囉嗦從新再列一遍整個流程:
在你的 Mac 開發機器生成一對公私鑰,這裏稱爲公鑰 L,私鑰 L。L:Local
蘋果本身有固定的一對公私鑰,跟上面 App Store 例子同樣,私鑰在蘋果後臺,公鑰在每一個 iOS 設備上。這裏稱爲公鑰 A,私鑰 A。A:Apple
把公鑰 L 傳到蘋果後臺,用蘋果後臺裏的私鑰 A 去簽名公鑰 L。獲得一份數據包含了公鑰 L 以及其簽名,把這份數據稱爲證書。
在蘋果後臺申請 AppID,配置好設備 ID 列表和 App 可以使用的權限,再加上第③步的證書,組成的數據用私鑰 A 簽名,把數據和簽名一塊兒組成一個 Provisioning Profile 文件,下載到本地 Mac 開發機。
在開發時,編譯完一個 App 後,用本地的私鑰 L 對這個 App 進行簽名,同時把第④步獲得的 Provisioning Profile 文件打包進 App 裏,文件名爲 embedded.mobileprovision,把 App 安裝到手機上。
在安裝時,iOS 系統取得證書,經過系統內置的公鑰 A,去驗證 embedded.mobileprovision 的數字簽名是否正確,裏面的證書籤名也會再驗一遍。
確保了 embedded.mobileprovision 裏的數據都是蘋果受權之後,就能夠取出裏面的數據,作各類驗證,包括用公鑰 L 驗證 App 簽名,驗證設備 ID 是否在 ID 列表上,AppID 是否對應得上,權限開關是否跟 App 裏的 Entitlements 對應等。
開發者證書從簽名到認證最終蘋果採用的流程大體是這樣,還有一些細節像證書有效期/證書類型等就不細說了。
上面的步驟對應到咱們日常具體的操做和概念是這樣的:
第 1 步對應的是 keychain 裏的 「從證書頒發機構請求證書」,這裏就本地生成了一堆公私鑰,保存的 CertificateSigningRequest 就是公鑰,私鑰保存在本地電腦裏。
第 2 步蘋果處理,不用管。
第 3 步對應把 CertificateSigningRequest 傳到蘋果後臺生成證書,並下載到本地。這時本地有兩個證書,一個是第 1 步生成的,一個是這裏下載回來的,keychain 會把這兩個證書關聯起來,由於他們公私鑰是對應的,在 XCode 選擇下載回來的證書時,實際上會找到 keychain 裏對應的私鑰去簽名。這裏私鑰只有生成它的這臺 Mac 有,若是別的 Mac 也要編譯簽名這個 App 怎麼辦?答案是把私鑰導出給其餘 Mac 用,在 keychain 裏導出私鑰,就會存成 .p12 文件,其餘 Mac 打開後就導入了這個私鑰。
第 4 步都是在蘋果網站上操做,配置 AppID / 權限 / 設備等,最後下載 Provisioning Profile 文件。
第 5 步 XCode 會經過第 3 步下載回來的證書(存着公鑰),在本地找到對應的私鑰(第一步生成的),用本地私鑰去簽名 App,並把 Provisioning Profile 文件命名爲 embedded.mobileprovision 一塊兒打包進去。這裏對 App 的簽名數據保存分兩部分,Mach-O 可執行文件會把簽名直接寫入這個文件裏,其餘資源文件則會保存在 _CodeSignature 目錄下。
第 6 – 7 步的打包和驗證都是 Xcode 和 iOS 系統自動作的事。
這裏再總結一下這些概念:
證書: 內容是公鑰或私鑰,由其餘機構對其簽名組成的數據包。
Entitlements: 包含了 App 權限開關列表。
CertificateSigningRequest: 本地公鑰。
p12: 本地私鑰,能夠導入到其餘電腦。
Provisioning Profile: 包含了 證書 / Entitlements 等數據,並由蘋果後臺私鑰簽名的數據包。
前面以開發包爲例子說了簽名和驗證的流程,另外兩種方式 In-House 企業簽名和 AD-Hoc 流程也是差很少的,只是企業簽名不限制安裝的設備數,另外須要用戶在 iOS 系統設置上手動點擊信任這個企業才能經過驗證。
而 App Store 的簽名驗證方式有些不同,前面咱們說到最簡單的簽名方式,蘋果在後臺直接用私鑰簽名 App 就能夠了,實際上蘋果確實是這樣作的,若是去下載一個 App Store 的安裝包,會發現它裏面是沒有 embedded.mobileprovision 文件的,也就是它安裝和啓動的流程是不依賴這個文件,驗證流程也就跟上述幾種類型不同了。
據猜想,由於上傳到 App Store 的包蘋果會從新對內容加密,原來的本地私鑰簽名就沒有用了,須要從新簽名,從 App Store 下載的包蘋果也並不打算控制它的有效期,不須要內置一個 embedded.mobileprovision 去作校驗,直接在蘋果用後臺的私鑰從新簽名,iOS 安裝時用本地公鑰驗證 App 簽名就能夠了。
那爲何發佈 App Store 的包仍是要跟開發版同樣搞各類證書和 Provisioning Profile?猜想由於蘋果想作統一管理,Provisioning Profile 裏包含一些權限控制,AppID 的檢驗等,蘋果不想在上傳 App Store 包時從新用另外一種協議作一遍這些驗證,就不如統一把這部分放在 Provisioning Profile 裏,上傳 App Store 時只要用一樣的流程驗證這個 Provisioning Profile 是否合法就能夠了
因此 App 上傳到 App Store 後,就跟你的 證書 / Provisioning Profile 都沒有關係了,不管他們是否過時或被廢除,都不會影響 App Store 上的安裝包。 到這裏 iOS 簽名機制的原理和主流程大體說完了,但願能對理解蘋果簽名和排查平常簽名問題有所幫助。
最後這裏再提一下我關於簽名流程的一些的疑問。
企業證書籤名由於限制少,在國內被普遍用於測試和盜版,fir.im / 蒲公英等測試平臺都是經過企業證書分發,國內一些市場像 PP 助手、愛思助手,一部分安裝手段也是經過企業證書重簽名。經過企業證書籤名安裝的 App,啓動時都會驗證證書的有效期,而且不按期請求蘋果服務器看證書是否被吊銷,若已過時或被吊銷,就會沒法啓動 App。對於這種助手的盜版安裝手段,蘋果想打擊只能一個個吊銷企業證書,並無太好的辦法。
這裏個人疑問是,蘋果作了那麼多簽名和驗證機制去限制在 iOS 安裝 App,爲何又要出這樣一個限制不多的方式讓盜版鑽空子呢?若真的是企業用途不適合上 App Store,也徹底能夠在 App Store 開闢一個小的私密版塊,仍是經過 App Store 去安裝,就不會有這個問題了
另外一個問題是咱們把 App 傳上 App Store 後,蘋果會對 App 進行加密,致使 App 體積增大很多,這個加密其實是沒卵用的,只是讓破解的人要多作一個步驟,運行 App 去內存 dump 出可執行文件而已,不管怎樣加密,均可以用這種方式拿出加密前的可執行文件。因此爲何要作這樣的加密呢?想不到有什麼好處。
咱們看到前面說的簽名流程很繞很複雜,常常出現各類問題,像有 Provisioning Profile 文件但證書又不對,本地有公鑰證書沒對應私鑰等狀況,不理解原理的狀況下會被繞暈,個人疑問是,這裏爲何不能簡化呢?
仍是以開發證書爲例,爲何必定要用本地 Mac 生成的私鑰去簽名?蘋果要的只是本地簽名,私鑰不必定是要本地生成的,蘋果也能夠本身生成一對公私鑰給咱們,放在 Provisioning Profile 裏,咱們用裏面的私鑰去加密就好了,這樣就不會有 CertificateSigningRequest 和 p12 的概念,跟本地 keychain 沒有關係,不須要關心證書,只要有 Provisioning Profile 就能簽名,流程會減小,易用性會提升不少,同時蘋果想要的控制一點都不會少,也沒有什麼安全問題,爲何不這樣設計呢?
能想到的一個緣由是 Provisioning Profile 在非 App Store 安裝時會打包進安裝包,第三方拿到這個 Provisioning Profile 文件就能直接用起來給他本身的 App 簽名了。但這種問題也挺好解決,只須要打包時去掉文件裏的私鑰就好了,因此仍不明白爲何這樣設計。
關於文章末尾的疑問,唐巧在微博分享了他的見解:
1. 企業發佈能夠構建出絕對的私有網絡下載和使用環境,避免一些極其機密內容在公網傳播。
2. 加殼防止的是靜態分析,不然無需越獄手機便可反彙編 IPA 源碼。
若是你有好的見解與建議,歡迎與咱們一塊兒分享,技術之路,共同進步,
複製代碼