---- 轉(zhuǎn)摘自微信讀書團(tuán)隊(duì)文章
iOS 簽名機(jī)制挺復(fù)雜精盅,各種證書,Provisioning Profile,entitlements渔嚷,CertificateSigningRequest,p12稠曼,AppID形病,概念一堆,也很容易出錯霞幅,本文嘗試從原理出發(fā)漠吻,一步步推出為什么會有這么多概念,希望能有助于理解 iOS App 簽名的原理和流程司恳。
先來看看蘋果的簽名機(jī)制是為了做什么途乃。在 iOS 出來之前,在主流操作系統(tǒng)(Mac/Windows/Linux)上開發(fā)和運(yùn)行軟件是不需要簽名的扔傅,軟件隨便從哪里下載都能運(yùn)行耍共,導(dǎo)致平臺對第三方軟件難以控制,盜版流行猎塞。蘋果希望解決這樣的問題试读,在 iOS 平臺對第三方 APP 有絕對的控制權(quán),一定要保證每一個安裝到 iOS 上的 APP 都是經(jīng)過蘋果官方允許的荠耽,怎樣保證呢钩骇?就是通過簽名機(jī)制。
通常我們說的簽名就是數(shù)字簽名铝量,它是基于非對稱加密算法實(shí)現(xiàn)的倘屹。對稱加密是通過同一份密鑰加密和解密數(shù)據(jù),而非對稱加密則有兩份密鑰慢叨,分別是公鑰和私鑰纽匙,用公鑰加密的數(shù)據(jù),要用私鑰才能解密插爹,用私鑰加密的數(shù)據(jù)哄辣,要用公鑰才能解密。
簡單說一下常用的非對稱加密算法 RSA 的數(shù)學(xué)原理赠尾,理解簡單的數(shù)學(xué)原理力穗,就可以理解非對稱加密是怎么做到的,為什么會是安全的:
選兩個質(zhì)數(shù)?p?和?q气嫁,相乘得出一個大整數(shù)n当窗,例如 p=61,q=53寸宵,n=pq=3233
選 1-n 間的隨便一個質(zhì)數(shù)?e崖面,例如 e = 17
經(jīng)過一系列數(shù)學(xué)公式元咙,算出一個數(shù)字?d,滿足:
a. 通過?n?和?e?這兩個數(shù)據(jù)一組數(shù)據(jù)進(jìn)行數(shù)學(xué)運(yùn)算后巫员,可以通過 n 和 d 去反解運(yùn)算庶香,反過來也可以。
b. 如果只知道?n?和?e简识,要推導(dǎo)出?d赶掖,需要知道?p?和?q,也就是要需要把 n 因數(shù)分解七扰。
上述的?(n,e)?這兩個數(shù)據(jù)在一起就是公鑰奢赂,(n,d)?這兩個數(shù)據(jù)就是私鑰,滿足用公鑰加密颈走,私鑰解密膳灶,或反過來公鑰加密,私鑰解密立由,也滿足在只暴露公鑰(只知道?n?和 e)的情況下轧钓,要推導(dǎo)出私鑰?(n,d),需要把大整數(shù)?n?因數(shù)分解拆吆。目前因數(shù)分解只能靠暴力窮舉聋迎,而n數(shù)字越大脂矫,越難以用窮舉計(jì)算出因數(shù)?p?和?q枣耀,也就越安全,當(dāng)?n?大到二進(jìn)制 1024 位或 2048 位時庭再,以目前技術(shù)要破解幾乎不可能捞奕,所以非常安全。
若對數(shù)字?d?是怎樣計(jì)算出來的感興趣拄轻,可以詳讀這兩篇文章:RSA 算法原理(一)(二)
現(xiàn)在知道了有非對稱加密這東西颅围,那數(shù)字簽名是怎么回事呢?
數(shù)字簽名的作用是我對某一份數(shù)據(jù)打個標(biāo)記恨搓,表示我認(rèn)可了這份數(shù)據(jù)(簽了個名)院促,然后我發(fā)送給其他人,其他人可以知道這份數(shù)據(jù)是經(jīng)過我認(rèn)證的斧抱,數(shù)據(jù)沒有被篡改過常拓。
有了上述非對稱加密算法,就可以實(shí)現(xiàn)這個需求:
首先用一種算法辉浦,算出原始數(shù)據(jù)的摘要弄抬。需滿足 a.若原始數(shù)據(jù)有任何變化,計(jì)算出來的摘要值都會變化宪郊。 b.摘要要夠短掂恕。這里最常用的算法是MD5拖陆。
生成一份非對稱加密的公鑰和私鑰,私鑰我自己拿著懊亡,公鑰公布出去依啰。
對一份數(shù)據(jù),算出摘要后店枣,用私鑰加密這個摘要孔飒,得到一份加密后的數(shù)據(jù),稱為原始數(shù)據(jù)的簽名艰争。把它跟原始數(shù)據(jù)一起發(fā)送給用戶坏瞄。
用戶收到數(shù)據(jù)和簽名后,用公鑰解密得到摘要甩卓。同時用戶用同樣的算法計(jì)算原始數(shù)據(jù)的摘要鸠匀,對比這里計(jì)算出來的摘要和用公鑰解密簽名得到的摘要是否相等,若相等則表示這份數(shù)據(jù)中途沒有被篡改過逾柿,因?yàn)槿绻鄹倪^缀棍,摘要會變化。
之所以要有第一步計(jì)算摘要机错,是因?yàn)榉菍ΨQ加密的原理限制可加密的內(nèi)容不能太大(不能大于上述 n 的位數(shù)爬范,也就是一般不能大于 1024 位/ 2048 位),于是若要對任意大的數(shù)據(jù)簽名弱匪,就需要改成對它的特征值簽名青瀑,效果是一樣的。
好了萧诫,有了非對稱加密的基礎(chǔ)斥难,知道了數(shù)字簽名是什么,怎樣可以保證一份數(shù)據(jù)是經(jīng)過某個地方認(rèn)證的帘饶,來看看怎樣通過數(shù)字簽名的機(jī)制保證每一個安裝到 iOS 上的 APP 都是經(jīng)過蘋果認(rèn)證允許的哑诊。
要實(shí)現(xiàn)這個需求很簡單,最直接的方式及刻,蘋果官方生成一對公私鑰镀裤,在 iOS 里內(nèi)置一個公鑰,私鑰由蘋果后臺保存缴饭,我們傳 App 上 AppStore 時暑劝,蘋果后臺用私鑰對 APP 數(shù)據(jù)進(jìn)行簽名,iOS 系統(tǒng)下載這個 APP 后茴扁,用公鑰驗(yàn)證這個簽名铃岔,若簽名正確,這個 APP 肯定是由蘋果后臺認(rèn)證的,并且沒有被修改過毁习,也就達(dá)到了蘋果的需求:保證安裝的每一個 APP 都是經(jīng)過蘋果官方允許的智嚷。
如果我們 iOS 設(shè)備安裝 APP 只有從 AppStore 下載這一種方式的話,這件事就結(jié)束了纺且,沒有任何復(fù)雜的東西盏道,只有一個數(shù)字簽名,非常簡單地解決問題载碌。
但實(shí)際上因?yàn)槌藦?AppStore 下載猜嘱,我們還可以有三種方式安裝一個 App:
開發(fā) App 時可以直接把開發(fā)中的應(yīng)用安裝進(jìn)手機(jī)進(jìn)行調(diào)試。
In-House 企業(yè)內(nèi)部分發(fā)嫁艇,可以直接安裝企業(yè)證書簽名后的 APP朗伶。
AD-Hoc 相當(dāng)于企業(yè)分發(fā)的限制版,限制安裝設(shè)備數(shù)量步咪,較少用论皆。
蘋果要對用這三種方式安裝的 App 進(jìn)行控制,就有了新的需求猾漫,無法像上面這樣簡單了点晴。
我們先來看第一個,開發(fā)時安裝APP悯周,它有兩個個需求:
安裝包不需要傳到蘋果服務(wù)器粒督,可以直接安裝到手機(jī)上。如果你編譯一個 APP 到手機(jī)前要先傳到蘋果服務(wù)器簽名禽翼,這顯然是不能接受的屠橄。
蘋果必須對這里的安裝有控制權(quán),包括
a.經(jīng)過蘋果允許才可以這樣安裝捐康。
b.不能被濫用導(dǎo)致非開發(fā)app也能被安裝仇矾。
為了實(shí)現(xiàn)這些需求,iOS 簽名的復(fù)雜度也就開始增加了解总。
蘋果這里給出的方案是使用了雙層簽名,會比較繞姐仅,流程大概是這樣的:
在你的 Mac 開發(fā)機(jī)器生成一對公私鑰花枫,這里稱為公鑰L,私鑰L掏膏。L:Local
蘋果自己有固定的一對公私鑰劳翰,跟上面 AppStore 例子一樣,私鑰在蘋果后臺馒疹,公鑰在每個 iOS 設(shè)備上佳簸。這里稱為公鑰A,私鑰A。A:Apple
把公鑰 L 傳到蘋果后臺生均,用蘋果后臺里的私鑰 A 去簽名公鑰 L听想。得到一份數(shù)據(jù)包含了公鑰 L 以及其簽名,把這份數(shù)據(jù)稱為證書马胧。
在開發(fā)時汉买,編譯完一個 APP 后,用本地的私鑰 L 對這個 APP 進(jìn)行簽名佩脊,同時把第三步得到的證書一起打包進(jìn) APP 里蛙粘,安裝到手機(jī)上。
在安裝時威彰,iOS 系統(tǒng)取得證書出牧,通過系統(tǒng)內(nèi)置的公鑰 A,去驗(yàn)證證書的數(shù)字簽名是否正確歇盼。
驗(yàn)證證書后確保了公鑰 L 是蘋果認(rèn)證過的崔列,再用公鑰 L 去驗(yàn)證 APP 的簽名,這里就間接驗(yàn)證了這個 APP 安裝行為是否經(jīng)過蘋果官方允許旺遮。(這里只驗(yàn)證安裝行為赵讯,不驗(yàn)證APP 是否被改動,因?yàn)殚_發(fā)階段 APP 內(nèi)容總是不斷變化的耿眉,蘋果不需要管边翼。)
上述流程只解決了上面第一個需求,也就是需要經(jīng)過蘋果允許才可以安裝鸣剪,還未解決第二個避免被濫用的問題组底。怎么解決呢?蘋果再加了兩個限制筐骇,一是限制在蘋果后臺注冊過的設(shè)備才可以安裝债鸡,二是限制簽名只能針對某一個具體的 APP。
怎么加的铛纬?在上述第三步厌均,蘋果用私鑰 A 簽名我們本地公鑰 L 時,實(shí)際上除了簽名公鑰 L告唆,還可以加上無限多數(shù)據(jù)棺弊,這些數(shù)據(jù)都可以保證是經(jīng)過蘋果官方認(rèn)證的,不會有被篡改的可能擒悬。
可以想到把 允許安裝的設(shè)備 ID 列表 和 App對應(yīng)的 AppID 等數(shù)據(jù)模她,都在第三步這里跟公鑰L一起組成證書,再用蘋果私鑰 A 對這個證書簽名懂牧。在最后第 5 步驗(yàn)證時就可以拿到設(shè)備 ID 列表侈净,判斷當(dāng)前設(shè)備是否符合要求。根據(jù)數(shù)字簽名的原理,只要數(shù)字簽名通過驗(yàn)證畜侦,第 5 步這里的設(shè)備 IDs / AppID / 公鑰 L 就都是經(jīng)過蘋果認(rèn)證的元扔,無法被修改,蘋果就可以限制可安裝的設(shè)備和 APP夏伊,避免濫用摇展。
到這里這個證書已經(jīng)變得很復(fù)雜了,有很多額外信息溺忧,實(shí)際上除了 設(shè)備 ID / AppID咏连,還有其他信息也需要在這里用蘋果簽名,像這個 APP 里 iCloud / push / 后臺運(yùn)行 等權(quán)限蘋果都想控制鲁森,蘋果把這些權(quán)限開關(guān)統(tǒng)一稱為 Entitlements祟滴,它也需要通過簽名去授權(quán)。
實(shí)際上一個“證書”本來就有規(guī)定的格式規(guī)范歌溉,上面我們把各種額外信息塞入證書里是不合適的垄懂,于是蘋果另外搞了個東西,叫 Provisioning Profile痛垛,一個 Provisioning Profile 里就包含了證書以及上述提到的所有額外信息草慧,以及所有信息的簽名。
所以整個流程稍微變一下匙头,就變成這樣了:
因?yàn)椴襟E有小變動漫谷,這里我們不辭啰嗦重新再列一遍整個流程:
在你的 Mac 開發(fā)機(jī)器生成一對公私鑰,這里稱為公鑰L蹂析,私鑰L舔示。L:Local
蘋果自己有固定的一對公私鑰,跟上面 AppStore 例子一樣电抚,私鑰在蘋果后臺惕稻,公鑰在每個 iOS 設(shè)備上。這里稱為公鑰A蝙叛,私鑰A俺祠。A:Apple
把公鑰 L 傳到蘋果后臺,用蘋果后臺里的私鑰 A 去簽名公鑰 L甥温。得到一份數(shù)據(jù)包含了公鑰 L 以及其簽名锻煌,把這份數(shù)據(jù)稱為證書。
在蘋果后臺申請 AppID姻蚓,配置好設(shè)備 ID 列表和 APP 可使用的權(quán)限,再加上第③步的證書匣沼,組成的數(shù)據(jù)用私鑰 A 簽名狰挡,把數(shù)據(jù)和簽名一起組成一個 Provisioning Profile 文件,下載到本地 Mac 開發(fā)機(jī)。
在開發(fā)時加叁,編譯完一個 APP 后倦沧,用本地的私鑰 L 對這個 APP 進(jìn)行簽名,同時把第④步得到的 Provisioning Profile 文件打包進(jìn) APP 里它匕,文件名為?embedded.mobileprovision展融,把 APP 安裝到手機(jī)上。
在安裝時豫柬,iOS 系統(tǒng)取得證書告希,通過系統(tǒng)內(nèi)置的公鑰 A,去驗(yàn)證?embedded.mobileprovision?的數(shù)字簽名是否正確烧给,里面的證書簽名也會再驗(yàn)一遍燕偶。
確保了?embedded.mobileprovision?里的數(shù)據(jù)都是蘋果授權(quán)以后,就可以取出里面的數(shù)據(jù)础嫡,做各種驗(yàn)證指么,包括用公鑰 L 驗(yàn)證APP簽名,驗(yàn)證設(shè)備 ID 是否在 ID 列表上榴鼎,AppID 是否對應(yīng)得上伯诬,權(quán)限開關(guān)是否跟 APP 里的 Entitlements 對應(yīng)等。
開發(fā)者證書從簽名到認(rèn)證最終蘋果采用的流程大致是這樣巫财,還有一些細(xì)節(jié)像證書有效期/證書類型等就不細(xì)說了盗似。
上面的步驟對應(yīng)到我們平常具體的操作和概念是這樣的:
第 1 步對應(yīng)的是 keychain 里的 “從證書頒發(fā)機(jī)構(gòu)請求證書”,這里就本地生成了一堆公私鑰翁涤,保存的?CertificateSigningRequest?就是公鑰桥言,私鑰保存在本地電腦里。
第 2 步蘋果處理葵礼,不用管号阿。
第 3 步對應(yīng)把?CertificateSigningRequest?傳到蘋果后臺生成證書,并下載到本地鸳粉。這時本地有兩個證書扔涧,一個是第 1 步生成的,一個是這里下載回來的届谈,keychain 會把這兩個證書關(guān)聯(lián)起來枯夜,因?yàn)樗麄児借€是對應(yīng)的,在XCode選擇下載回來的證書時艰山,實(shí)際上會找到 keychain 里對應(yīng)的私鑰去簽名湖雹。這里私鑰只有生成它的這臺 Mac 有,如果別的 Mac 也要編譯簽名這個 App 怎么辦曙搬?答案是把私鑰導(dǎo)出給其他 Mac 用摔吏,在 keychain 里導(dǎo)出私鑰鸽嫂,就會存成?.p12?文件,其他 Mac 打開后就導(dǎo)入了這個私鑰征讲。
第 4 步都是在蘋果網(wǎng)站上操作据某,配置 AppID / 權(quán)限 / 設(shè)備等,最后下載 Provisioning Profile 文件诗箍。
第 5 步 XCode 會通過第 3 步下載回來的證書(存著公鑰)癣籽,在本地找到對應(yīng)的私鑰(第一步生成的),用本地私鑰去簽名 App滤祖,并把 Provisioning Profile 文件命名為?embedded.mobileprovision?一起打包進(jìn)去筷狼。這里對 App 的簽名數(shù)據(jù)保存分兩部分,Mach-O 可執(zhí)行文件會把簽名直接寫入這個文件里氨距,其他資源文件則會保存在?_CodeSignature?目錄下桑逝。
第 6 - 7 步的打包和驗(yàn)證都是 Xcode 和 iOS 系統(tǒng)自動做的事。
這里再總結(jié)一下這些概念:
證書:內(nèi)容是公鑰或私鑰俏让,由其他機(jī)構(gòu)對其簽名組成的數(shù)據(jù)包楞遏。
Entitlements:包含了 App 權(quán)限開關(guān)列表。
CertificateSigningRequest:本地公鑰首昔。
p12:本地私鑰寡喝,可以導(dǎo)入到其他電腦。
Provisioning Profile:包含了 證書 / Entitlements 等數(shù)據(jù)勒奇,并由蘋果后臺私鑰簽名的數(shù)據(jù)包预鬓。
前面以開發(fā)包為例子說了簽名和驗(yàn)證的流程,另外兩種方式 In-House 企業(yè)簽名和 AD-Hoc 流程也是差不多的赊颠,只是企業(yè)簽名不限制安裝的設(shè)備數(shù)格二,另外需要用戶在 iOS 系統(tǒng)設(shè)置上手動點(diǎn)擊信任這個企業(yè)才能通過驗(yàn)證。
而 AppStore 的簽名驗(yàn)證方式有些不一樣竣蹦,前面我們說到最簡單的簽名方式顶猜,蘋果在后臺直接用私鑰簽名 App 就可以了,實(shí)際上蘋果確實(shí)是這樣做的痘括,如果去下載一個 AppStore 的安裝包长窄,會發(fā)現(xiàn)它里面是沒有?embedded.mobileprovision?文件的,也就是它安裝和啟動的流程是不依賴這個文件纲菌,驗(yàn)證流程也就跟上述幾種類型不一樣了谆刨。
據(jù)猜測临燃,因?yàn)樯蟼鞯?AppStore 的包蘋果會重新對內(nèi)容加密,原來的本地私鑰簽名就沒有用了瘫絮,需要重新簽名酝碳,從 AppStore 下載的包蘋果也并不打算控制它的有效期嘶窄,不需要內(nèi)置一個?embedded.mobileprovision?去做校驗(yàn),直接在蘋果用后臺的私鑰重新簽名,iOS 安裝時用本地公鑰驗(yàn)證 App 簽名就可以了唉韭。
那為什么發(fā)布 AppStore 的包還是要跟開發(fā)版一樣搞各種證書和 Provisioning Profile夜涕?猜測因?yàn)樘O果想做統(tǒng)一管理犯犁,Provisioning Profile 里包含一些權(quán)限控制,AppID 的檢驗(yàn)等女器,蘋果不想在上傳 AppStore 包時重新用另一種協(xié)議做一遍這些驗(yàn)證酸役,就不如統(tǒng)一把這部分放在 Provisioning Profile 里,上傳 AppStore 時只要用同樣的流程驗(yàn)證這個 Provisioning Profile 是否合法就可以了驾胆。
所以 App 上傳到 AppStore 后涣澡,就跟你的 證書 / Provisioning Profile 都沒有關(guān)系了,無論他們是否過期或被廢除丧诺,都不會影響 AppStore 上的安裝包入桂。
到這里 iOS 簽名機(jī)制的原理和主流程大致說完了,希望能對理解蘋果簽名和排查日常簽名問題有所幫助驳阎。
最后這里再提一下我關(guān)于簽名流程的一些的疑問抗愁。
企業(yè)證書簽名因?yàn)橄拗粕伲趪鴥?nèi)被廣泛用于測試和盜版呵晚,fir.im / 蒲公英等測試平臺都是通過企業(yè)證書分發(fā)蜘腌,國內(nèi)一些市場像 PP 助手,愛思助手饵隙,一部分安裝手段也是通過企業(yè)證書重簽名撮珠。通過企業(yè)證書簽名安裝的 App,啟動時都會驗(yàn)證證書的有效期金矛,并且不定期請求蘋果服務(wù)器看證書是否被吊銷芯急,若已過期或被吊銷,就會無法啟動 App驶俊。對于這種助手的盜版安裝手段娶耍,蘋果想打擊只能一個個吊銷企業(yè)證書,并沒有太好的辦法废睦。
這里我的疑問是伺绽,蘋果做了那么多簽名和驗(yàn)證機(jī)制去限制在 iOS 安裝 App,為什么又要出這樣一個限制很少的方式讓盜版鉆空子呢嗜湃?若真的是企業(yè)用途不適合上 AppStore奈应,也完全可以在 AppStore 開辟一個小的私密版塊,還是通過 AppStore 去安裝购披,就不會有這個問題了杖挣。
另一個問題是我們把 App 傳上 AppStore 后,蘋果會對 App 進(jìn)行加密刚陡,導(dǎo)致 App 體積增大不少惩妇,這個加密實(shí)際上是沒卵用的株汉,只是讓破解的人要多做一個步驟,運(yùn)行 App 去內(nèi)存 dump 出可執(zhí)行文件而已歌殃,無論怎樣加密乔妈,都可以用這種方式拿出加密前的可執(zhí)行文件。所以為什么要做這樣的加密呢氓皱?想不到有什么好處路召。
我們看到前面說的簽名流程很繞很復(fù)雜,經(jīng)常出現(xiàn)各種問題波材,像有 Provisioning Profile 文件但證書又不對股淡,本地有公鑰證書沒對應(yīng)私鑰等情況,不理解原理的情況下會被繞暈廷区,我的疑問是唯灵,這里為什么不能簡化呢?還是以開發(fā)證書為例隙轻,為什么一定要用本地 Mac 生成的私鑰去簽名埠帕?蘋果要的只是本地簽名,私鑰不一定是要本地生成的大脉,蘋果也可以自己生成一對公私鑰給我們搞监,放在 Provisioning Profile 里,我們用里面的私鑰去加密就行了镰矿,這樣就不會有?CertificateSigningRequest?和?p12?的概念琐驴,跟本地 keychain 沒有關(guān)系,不需要關(guān)心證書秤标,只要有 Provisioning Profile 就能簽名绝淡,流程會減少,易用性會提高很多苍姜,同時蘋果想要的控制一點(diǎn)都不會少牢酵,也沒有什么安全問題,為什么不這樣設(shè)計(jì)呢衙猪?
能想到的一個原因是 Provisioning Profile 在非 AppStore 安裝時會打包進(jìn)安裝包馍乙,第三方拿到這個 Provisioning Profile 文件就能直接用起來給他自己的 App 簽名了。但這種問題也挺好解決垫释,只需要打包時去掉文件里的私鑰就行了丝格,所以仍不明白為什么這樣設(shè)計(jì)。