標籤:技術 影響 apple 間接 官方 linu 處理 原理 協助
iOS 簽名機制挺複雜,各種認證,Provisioning Profile,entitlements,CertificateSigningRequest,p12,AppID,概念一堆,也很容易出錯,本文嘗試從原理出發,一步步推出為什麼會有這麼多概念,希望能有助於理解 iOS App 簽名的原理和流程。
目的
先來看看蘋果的簽名機制是為了做什麼。在 iOS 出來之前,在主流作業系統(Mac/Windows/Linux)上開發和運行軟體是不需要簽名的,軟體隨便從哪裡下載都能運行,導致平台對第三方軟體難以控制,盜版流行。蘋果希望解決這樣的問題,在 iOS 平台對第三方 APP 有絕對的控制權,一定要保證每一個安裝到 iOS 上的 APP 都是經過蘋果官方允許的,怎樣保證呢?就是通過簽名機制。
非對稱式加密
通常我們說的簽名就是數位簽章,它是基於非對稱式加密演算法實現的。對稱式加密是通過同一份祕密金鑰加密和解密資料,而非對稱式加密則有兩份密鑰,分別是公開金鑰和私密金鑰,用公開金鑰加密的資料,要用私密金鑰才能解密,用私密金鑰加密的資料,要用公開金鑰才能解密。
簡單說一下常用的非對稱式加密演算法 RSA 的數學原理,理解簡單的數學原理,就可以理解非對稱式加密是怎麼做到的,為什麼會是安全的:
選兩個質數 p 和 q,相乘得出一個大整數n,例如 p = 61,q = 53,n = pq = 3233
選 1-n 間的隨便一個質數e,例如 e = 17
經過一系列數學公式,算出一個數字 d,滿足: a.通過 n 和 e 這兩個資料一組資料進行數學運算後,可以通過 n 和 d 去反解運算,反過來也可以。 b.如果只知道 n 和 e,要推匯出 d,需要知道 p 和 q,也就是要需要把 n 因數分解。
上述的 (n,e) 這兩個資料在一起就是公開金鑰,(n,d) 這兩個資料就是私密金鑰,滿足用私密金鑰加密,公開金鑰解密,或反過來公開金鑰加密,私密金鑰解密,也滿足在只暴露公開金鑰 (只知道 n 和 e)的情況下,要推匯出私密金鑰 (n,d),需要把大整數 n 因數分解。目前因數分解只能靠暴力窮舉,而 n 數字越大,越難以用窮舉計算出因數 p 和 q,也就越安全,當 n 大到二進位 1024 位或 2048 位時,以目前技術要破解幾乎不可能,所以非常安全。
數位簽章
現在知道了有非對稱式加密這東西,那數位簽章是怎麼回事呢?
數位簽章的作用是我對某一份資料打個標記,表示我認可了這份資料(簽了個名),然後我發送給其他人,其他人可以知道這份資料是經過我認證的,資料沒有被篡改過。
首先用一種演算法,算出未經處理資料的摘要。需滿足 a.若未經處理資料有任何變化,計算出來的摘要值都會變化。 b.摘要要夠短。這裡最常用的演算法是MD5。
產生一份非對稱式加密的公開金鑰和私密金鑰,私密金鑰我自己拿著,公開金鑰公布出去。
對一份資料,算出摘要後,用私密金鑰加密這個摘要,得到一份加密後的資料,稱為未經處理資料的簽名。把它跟未經處理資料一起發送給使用者。
使用者收到資料和簽名後,用公開金鑰解密得到摘要。同時使用者用同樣的演算法計算未經處理資料的摘要,對比這裡計算出來的摘要和用公開金鑰解密簽名得到的摘要是否相等,若相等則表示這份資料中途沒有被篡改過,因為如果篡改過,摘要會變化。
之所以要有第一步計算摘要,是因為非對稱式加密的原理限制可加密的內容不能太大(不能大於上述 n 的位元,也就是一般不能大於 1024 位 / 2048 位),於是若要對任意大的資料簽名,就需要改成對它的特徵值簽名,效果是一樣的。
好了,有了非對稱式加密的基礎,知道了數位簽章是什麼,怎樣可以保證一份資料是經過某個地方認證的,來看看怎樣通過數位簽章的機制保證每一個安裝到 iOS 上的 APP 都是經過蘋果認證允許的。
最簡單的簽名
要實現這個需求很簡單,最直接的方式,蘋果官方產生一對公私密金鑰,在 iOS 裡內建一個公開金鑰,私密金鑰由蘋果後台儲存,我們傳 App 上 AppStore 時,蘋果後台用私密金鑰對 APP 資料進行簽名,iOS 系統下載這個 APP 後,用公開金鑰驗證這個簽名,若簽名正確,這個 APP 肯定是由蘋果後台認證的,並且沒有被修改過,也就達到了蘋果的需求:保證安裝的每一個 APP 都是經過蘋果官方允許的。
如果我們 iOS 裝置安裝 APP 只有從 AppStore 下載這一種方式的話,這件事就結束了,沒有任何複雜的東西,只有一個數位簽章,非常簡單地解決問題。
但實際上因為除了從 AppStore 下載,我們還可以有三種方式安裝一個 App:
開發 App 時可以直接把開發中的應用安裝進手機進行調試。
In-House 企業內部分發,可以直接安裝企業認證簽名後的 APP。
AD-Hoc 相當於企業分發的限制版,限制安裝裝置數量,較少用。
蘋果要對用這三種方式安裝的 App 進行控制,就有了新的需求,無法像上面這樣簡單了。
新的需求
我們先來看第一個,開發時安裝APP,它有兩個個需求:
安裝包不需要傳到蘋果伺服器,可以直接安裝到手機上。如果你編譯一個 APP 到手機前要先傳到蘋果伺服器簽名,這顯然是不能接受的。
蘋果必須對這裡的安裝有控制權,包括 a. 經過蘋果允許才可以這樣安裝。 b. 不能被濫用導致非開發app也能被安裝。
為了實現這些需求,iOS 簽名的複雜度也就開始增加了。
蘋果這裡給出的方案是使用了雙層簽名,會比較繞,流程大概是這樣的:
在你的 Mac 開發機器產生一對公私密金鑰,這裡稱為 公開金鑰L , 私密金鑰L 。L:Local
蘋果自己有固定的一對公私密金鑰,跟上面 AppStore 例子一樣,私密金鑰在蘋果後台,公開金鑰在每個 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,還可以加上無限多資料,這些資料都可以保證是經過蘋果官方認證的,不會有被篡改的可能。
可以想到把 允許安裝的裝置識別碼 列表 和 App對應的 AppID 等資料,都在第三步這裡跟公開金鑰L一起組成認證,再用蘋果私密金鑰 A 對這個認證簽名。在最後第 5 步驗證時就可以拿到裝置識別碼 列表,判斷當前裝置是否符合要求。根據數位簽章的原理,只要數位簽章通過驗證,第 5 步這裡的裝置識別碼s / AppID / 公開金鑰 L 就都是經過蘋果認證的,無法被修改,蘋果就可以限制可安裝的裝置和 APP,避免濫用。
最終流程
到這裡這個認證已經變得很複雜了,有很多額外資訊,實際上除了 裝置識別碼 / AppID,還有其他資訊也需要在這裡用蘋果簽名,像這個 APP 裡 iCloud / push / 後台運行 等許可權蘋果都想控制,蘋果把這些許可權開關統一稱為 Entitlements,它也需要通過簽名去授權。
實際上一個“認證”本來就有規定的格式規範,上面我們把各種額外資訊塞入認證裡是不合適的,於是蘋果另外搞了個東西,叫 Provisioning Profile,一個 Provisioning Profile 裡就包含了認證以及上述提到的所有額外資訊,以及所有資訊的簽名。
因為步驟有小變動,這裡我們不辭囉嗦重新再列一遍整個流程:
在你的 Mac 開發機器產生一對公私密金鑰,這裡稱為公開金鑰L,私密金鑰L。L:Local
蘋果自己有固定的一對公私密金鑰,跟上面 AppStore 例子一樣,私密金鑰在蘋果後台,公開金鑰在每個 iOS 裝置上。這裡稱為公開金鑰A,私密金鑰A。A:Apple
把公開金鑰 L 傳到蘋果後台,用蘋果後台裡的私密金鑰 A 去簽名公開金鑰 L。得到一份資料包含了公開金鑰 L 以及其簽名,把這份資料稱為認證。
在蘋果後台申請 AppID,配置好裝置識別碼 列表和 APP 可使用的許可權,再加上第③步的認證,組成的資料用私密金鑰 A 簽名,把資料和簽名一起組成一個 Provisioning Profile 檔案,下載到本地 Mac 開發機。
在開發時,編譯完一個 APP 後,用本地的私密金鑰 L 對這個 APP 進行簽名,同時把第④步得到的 Provisioning Profile 檔案打包進 APP 裡,檔案名稱為 embedded.mobileprovision,把 APP 安裝到手機上。
在安裝時,iOS 系統取得認證,通過系統內建的公開金鑰 A,去驗證 embedded.mobileprovision 的數位簽章是否正確,裡面的認證簽名也會再驗一遍。
確保了 embedded.mobileprovision 裡的資料都是蘋果授權以後,就可以取出裡面的資料,做各種驗證,包括用公開金鑰 L 驗證APP簽名,驗證裝置識別碼 是否在 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 系統設定上手動點擊信任這個企業才能通過驗證。
而 AppStore 的簽名驗證方式有些不一樣,前面我們說到最簡單的簽名方式,蘋果在後台直接用私密金鑰簽名 App 就可以了,實際上蘋果確實是這樣做的,如果去下載一個 AppStore 的安裝包,會發現它裡面是沒有 embedded.mobileprovision 檔案的,也就是它安裝和啟動的流程是不依賴這個檔案,驗證流程也就跟上述幾種類型不一樣了。
據猜測,因為上傳到 AppStore 的包蘋果會重新對內容加密,原來的本地私密金鑰簽名就沒有用了,需要重新簽名,從 AppStore 下載的包蘋果也並不打算控制它的有效期間,不需要內建一個 embedded.mobileprovision 去做校正,直接在蘋果用背景私密金鑰重新簽名,iOS 安裝時用本地公開金鑰驗證 App 簽名就可以了。
那為什麼發布 AppStore 的包還是要跟開發版一樣搞各種認證和 Provisioning Profile?猜測因為蘋果想做統一管理,Provisioning Profile 裡包含一些許可權控制,AppID 的檢驗等,蘋果不想在上傳 AppStore 包時重新用另一種協議做一遍這些驗證,就不如統一把這部分放在 Provisioning Profile 裡,上傳 AppStore 時只要用同樣的流程驗證這個 Provisioning Profile 是否合法就可以了。
所以 App 上傳到 AppStore 後,就跟你的 認證 / Provisioning Profile 都沒有關係了,無論他們是否到期或被廢除,都不會影響 AppStore 上的安裝包。
到這裡 iOS 簽名機制的原理和主流程大致說完了,希望能對理解蘋果簽名和排查日常簽名問題有所協助。
P.S.一些疑問
最後這裡再提一下我關於簽名流程的一些的疑問。
企業認證
企業認證簽名因為限制少,在國內被廣泛用於測試和盜版,fir.im / 蒲公英等測試平台都是通過企業認證分發,國內一些市場像 PP 助手,愛思助手,一部分安裝手段也是通過企業認證重簽名。通過企業認證簽名安裝的 App,啟動時都會驗證認證的有效期間,並且不定期請求蘋果伺服器看認證是否被吊銷,若已到期或被吊銷,就會無法啟動 App。對於這種助手的盜版安裝手段,蘋果想打擊只能一個個吊銷企業認證,並沒有太好的辦法。
這裡我的疑問是,蘋果做了那麼多簽名和驗證機制去限制在 iOS 安裝 App,為什麼又要出這樣一個限制很少的方式讓盜版鑽空子呢?若真的是企業用途不適合上 AppStore,也完全可以在 AppStore 開闢一個小的私密版塊,還是通過 AppStore 去安裝,就不會有這個問題了。
AppStore 加密
另一個問題是我們把 App 傳上 AppStore 後,蘋果會對 App 進行加密,導致 App 體積增大不少,這個加密實際上是沒卵用的,只是讓破解的人要多做一個步驟,運行 App 去記憶體 dump 出可執行檔而已,無論怎樣加密,都可以用這種方式拿出加密前的可執行檔。所以為什麼要做這樣的加密呢?想不到有什麼好處。
本地私密金鑰
我們看到前面說的簽名流程很繞很複雜,經常出現各種問題,像有 Provisioning Profile 檔案但認證又不對,本地有密鑰憑證沒對應私密金鑰等情況,不理解原理的情況下會被繞暈,我的疑問是,這裡為什麼不能簡化呢?還是以開發認證為例,為什麼一定要用本地 Mac 產生的私密金鑰去簽名?蘋果要的只是本地簽名,私密金鑰不一定是要本地產生的,蘋果也可以自己產生一對公私密金鑰給我們,放在 Provisioning Profile 裡,我們用裡面的私密金鑰去加密就行了,這樣就不會有 CertificateSigningRequest 和 p12 的概念,跟本地 keychain 沒有關係,不需要關心認證,只要有 Provisioning Profile 就能簽名,流程會減少,易用性會提高很多,同時蘋果想要的控制一點都不會少,也沒有什麼安全問題,為什麼不這樣設計呢?
能想到的一個原因是 Provisioning Profile 在非 AppStore 安裝時會打包進安裝包,第三方拿到這個 Provisioning Profile 檔案就能直接用起來給他自己的 App 簽名了。但這種問題也挺好解決,只需要打包時去掉檔案裡的私密金鑰就行了,所以仍不明白為什麼這樣設計。
iOS 簽名機制