蘋(píng)果IPA包的重簽名可以解決跟換一個(gè)包名、logo和IP地址后進(jìn)行重新打包的工作,也避免了因?yàn)槿藶樵蛐薷拇a后忘記處理而導(dǎo)致打出的包無(wú)法使用的問(wèn)題。目前市面上有的超級(jí)app或者蒲公英上宣傳的企業(yè)簽名所用到的就是重簽名技術(shù)。
iOS簽名機(jī)制
先來(lái)看一下數(shù)字簽名的圖

蘋(píng)果的簽名機(jī)制如果簡(jiǎn)化,那也就是數(shù)字簽名的流程。圖的上半部分就是簽名的過(guò)程,下半部分就是用戶安裝應(yīng)用驗(yàn)證的過(guò)程。
蘋(píng)果為了控制應(yīng)用的安裝和發(fā)布,除了公私鑰對(duì),還增加了Provisioning Profile(包含了APP ID、手機(jī)UDID列表、配置的功能權(quán)限信息表Entitlements以及證書(shū)),驗(yàn)證的流程也增加了好多層級(jí):
- 先用公鑰驗(yàn)證provisioning Profile 的簽名和證書(shū)的簽名;
- 認(rèn)證通過(guò)后,再使用證書(shū)內(nèi)的公鑰驗(yàn)證APP的簽名,APP ID和Entitlements驗(yàn)證APP,手機(jī)UDID列表驗(yàn)證手機(jī)是否包含在列表內(nèi);
-
所有的驗(yàn)證通過(guò)后用戶才能使用。
蘋(píng)果簽名
IPA包的分析
IPA包解壓后是Payload文件夾,然后顯示文件夾內(nèi)的包內(nèi)容,可以發(fā)現(xiàn)(企業(yè)證書(shū)打的包)包內(nèi)有一個(gè)_CodeSignature文件夾和.mobileprovision文件還有一些已經(jīng)加密后的文件。其實(shí)_CodeSignature就是APP的簽名文件,哪些已經(jīng)加密的文件中也保存了APP的簽名文件。如果有framework,它的文件內(nèi)容和主文件下的差不多。
重簽名
需要使用到的文件:
- 開(kāi)發(fā)者證書(shū)
- 配置文件(entitlements)
- Provisioning Profile文件(embedded.mobileprovision)
- APP
步驟
- 找到開(kāi)發(fā)者證書(shū),可以在keychain我的證書(shū)中查找,找到證書(shū)后右鍵選擇顯示簡(jiǎn)介,拉到最底下選擇SHA-1設(shè)置為常量,在重簽名的時(shí)候可以通過(guò)這個(gè)哈希值找到對(duì)應(yīng)的證書(shū)。
CERT_FILE = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
- 使用embedded.mobileprovision文件生成entitlements.plist文件:
def gen_entitlements(out_file_name):
os.system('security cms -D -i "%s" > entitlement_full.plist ' % (get_mobile_provision_file()))
os.system('/usr/libexec/PlistBuddy -x -c \'Print:Entitlements\' entitlement_full.plist > "%s" ' % (out_file_name))
- 解壓APP的IPA包
def unzip_app(original_ipa):
os.system('unzip -qo ./%s -d ./' % (original_ipa))
print('unzip_app %s done!' % (original_ipa))
- 移除XX.app文件夾下所有_CodeSignature包括frameworks里面的
def del_code_signature():
os.system("rm -rf ./Payload/iVMS-7880.app/_CodeSignature")
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('rm -rf ./Payload/iVMS-7880.app/"%s"/_CodeSignature' % (file))
print('del_code_signature done!')
- 把準(zhǔn)備好的embedded.mobileprovision復(fù)制到XX.app文件夾下
def replace_provision_file():
for file in os.listdir("./"):
if os.path.splitext(file)[1] == '.mobileprovision':
os.system('cp "%s" ./Payload/iVMS-7880.app/embedded.mobileprovision' % (file))
- 重簽名framework
def resign_framework():
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/frameworks/"%s"' % (file)))
print('resign_framework "%s" done!' % (file))
- 重簽名app執(zhí)行文件
def resign_app():
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/iVMS-7880'))
print('resign_app done!')
- 壓縮Payload文件,重命名壓縮文件為XX.ipa
def zip_app(f_ipa):
os.system('zip -r %s ./Payload' % (f_ipa))
print('zip_app done!')
到此就會(huì)有一個(gè)新的IPA包生成了,可以通過(guò)手機(jī)助手或者上傳平臺(tái)進(jìn)行手機(jī)安裝驗(yàn)證。
代碼
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import json
CERT_FILE = '6BF988FC0D4993B3D48810E4D67419A2B1E81DC1'
def get_mobile_provision_file():
file_path = ""
for file in os.listdir("./"):
if os.path.splitext(file)[1] == '.mobileprovision':
file_path = os.path.join(os.getcwd(),"%s" % (file))
return file_path
def reset_bundle_identifier(bundle_identifier):
os.system('/usr/libexec/PlistBuddy -c \'Set:CFBundleIdentifier "%s"\' Payload/iVMS-7880.app/Info.plist' % (bundle_identifier))
def reset_bundle_version(build):
os.system('/usr/libexec/PlistBuddy -c \'Set:CFBundleVersion "%s"\' Payload/iVMS-7880.app/Info.plist' % (build))
def unzip_app(original_ipa):
os.system('unzip -qo ./%s -d ./' % (original_ipa))
print('unzip_app %s done!' % (original_ipa))
def del_code_signature():
os.system("rm -rf ./Payload/iVMS-7880.app/_CodeSignature")
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('rm -rf ./Payload/iVMS-7880.app/"%s"/_CodeSignature' % (file))
print('del_code_signature done!')
def resign_app():
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/iVMS-7880'))
print('resign_app done!')
def resign_framework():
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/frameworks/"%s"' % (file)))
print('resign_framework "%s" done!' % (file))
def zip_app(f_ipa):
os.system('zip -r %s ./Payload' % (f_ipa))
print('zip_app done!')
def del_payload():
os.system('rm -r ./Payload')
def gen_entitlements(out_file_name):
os.system('security cms -D -i "%s" > entitlement_full.plist ' % (get_mobile_provision_file()))
os.system('/usr/libexec/PlistBuddy -x -c \'Print:Entitlements\' entitlement_full.plist > "%s" ' % (out_file_name))
def rep_emb_file():
os.system('cp "%s" ./Payload/iVMS-7880/embedded.mobileprovision' % (get_mobile_provision_file()))
def replace_provision_file():
for file in os.listdir("./"):
if os.path.splitext(file)[1] == '.mobileprovision':
os.system('cp "%s" ./Payload/iVMS-7880.app/embedded.mobileprovision' % (file))
if __name__ == '__main__':
gen_entitlements("entitlement.plist")
original_ipa = input("original_ipa:")
unzip_app(original_ipa)
bundle_identifier = input("bundle_identifier:")
reset_bundle_identifier(bundle_identifier)
build = input("build_version:")
reset_bundle_version(build)
del_code_signature()
replace_provision_file()
resign_framework()
resign_app()
newName = input("newName:")
zip_app(newName)
del_payload()
復(fù)制代碼存儲(chǔ)為python文件,之前準(zhǔn)備的文件和該文件需要在同一個(gè)根目錄下,然后在終端中執(zhí)行該文件,輸入對(duì)應(yīng)信息后就可以得到新的IPA包。輸入的信息都為字符串需要用引號(hào),否則將會(huì)沒(méi)有效果。
