iOS打包(重签名的方式)

阅读: 评论:0

iOS打包(重签名的方式)

iOS打包(重签名的方式)

iOS打包(重签名的方式)--用Mac客户端来实现

公司最近出了个需求,要求迅速给客户打一些马甲包,就是替换里面的plist和一些资源文件(icon和launchImage),于是找了很多资料,发现这一部分很多内容都已过期或者说讲的不全面,遂收集了一个全套的ipa重签名内容,分享给大家。代码是用swift写的,版本3.0

常量定义

struct PathDefine {static let UnzipPath = NSTemporaryDirectory().appending("unzip")static let TargetPath = "/Users/apple/Desktop"static let outputPath : String = "/Users/apple/Desktop/autoPackage"}
struct OtherStringDefine {static let appid = "application-identifier"static let kTeamIdentifier = "com.am-identifier"static let kKeychainAccessGroups = "keychain-access-groups"static let codeSignature = "_CodeSignature"static let dbVersionKey = "dbVersion"
}

这些常量在以下内容中会用到

1解包

//解压之前先创建解压目录fileprivate func createWorkingPath() {let manager = FileManager.defaultif manager.fileExists(atPath: PathDefine.UnzipPath) == false {//不存在目录时创建一个do {ateDirectory(atPath: PathDefine.UnzipPath, withIntermediateDirectories: true, attributes: nil)} catch {print(error.localizedDescription)}} else {// 存在目录时清空目录do {veItem(atPath: PathDefine.UnzipPath)} catch  {print(error.localizedDescription)}//再创建一个do {ateDirectory(atPath: PathDefine.UnzipPath, withIntermediateDirectories: true, attributes: nil)} catch {print(error.localizedDescription)}}}//这两个方法也是会多次被调用的,作用是监听task运行结果fileprivate func checkComplete(task : Process, complete : @escaping (Bool) -> Void) {Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerComplete(sender:)), userInfo: ["task" : task, "complete" : complete], repeats: true)}@objc private func timerComplete(sender : Timer) {let task : Process = (sender.userInfo as! Dictionary<String>)["task"] as! Processlet complete : (Bool) -> Void = (sender.userInfo as! Dictionary<String>)["complete"] as! ((Bool) -> Void)if task.isRunning == false {sender.invalidate()inationStatus == 1 {complete(false)} else {complete(true)}}}//开始解包fileprivate func unzip(complete :@escaping (Bool) -> Void) {let task = Process.init()task.launchPath = "/usr/bin/unzip"//这个record.ipaInputPath是我模型中的变量,ipa母包的绝对路径 大家可以把ipa丢到终端里面,就可以看到了task.arguments = [ "-q", record.ipaInputPath, "-d", PathDefine.UnzipPath]let pi = Pipe.init()task.standardOutput = pilet file = pi.fileHandleForReadingtask.launch()let data = adDataToEndOfFile&#40;&#41;print(String.init(data: data, encoding: .utf8) ?? "")self.checkComplete(task: task) { [unowned self] (result) veCodeSignature() //移除签名,内容在下面complete(result)}}

2移除签名文件

为什么要移除签名文件,因为我们这个是二次签名,以前的签名文件肯定不能用了啊,而且如果不移除了会影响签名过程

fileprivate func removeCodeSignature() {var appName = d.ipaInputPathappName = appName.substring(to: appName.dIndex, offsetBy: -4))//这里要说一下,解包后的目录Payload/下.app文件的文件名默认是你xcode里面被打包那个target的名字。因为我的工程名和target名不一样,所有要替换一下。 targetName变量就是我的target名字if unt > 0 {appName = targetName}let appPath = PathDefine.UnzipPath + "/Payload/" + URL.init(fileURLWithPath: appName).lastPathComponent + ".app"let codeSignPath = appPath + "/" + deSignaturelet manager = FileManager.defaultif manager.fileExists(atPath: codeSignPath) {do {veItem(atPath: codeSignPath)} catch {print(error.localizedDescription)}}}

3 编辑plist文件

    fileprivate func editPlist(complete : (Bool) -> Void) {var appName = record.ipaInputPathappName = appName.substring(to: appName.dIndex, offsetBy: -4))if unt > 0 {appName = targetName}let plistPath = PathDefine.UnzipPath + "/Payload/" + URL.init(fileURLWithPath: appName).lastPathComponent + ".app/Info.plist"let plistDic = NSMutableDictionary.init(contentsOfFile: plistPath)if plistDic == nil {complete(false)return}//定义了一个delegate变量,因为我要打多个不同项目的包,所以他的delegate来实现不同的编辑逻辑,如果不知道的plist文件中每个key的名字,可以在这里把plistDic打印到控制台上self.delegate?.editPlist(plistDic: plistDic!)//移除之前的plist文件let manager = FileManager.defaultdo {veItem(atPath: plistPath)} catch  {print(error.localizedDescription)}//把plist文件按照XML格式重新写入到目录下do {let xmlData = try PropertyListSerialization.data(fromPropertyList: plistDic ?? "", format: .xml, options: 0) as NSDataif xmlData.write(toFile: plistPath, atomically: true) == false {complete(false)return}} catch {print(error.localizedDescription)}complete(true)}

4替换icon和launchImage

fileprivate func replaceIconAndLaunchImage(complete :@escaping (Bool) -> Void) {//这里每个被替换的图片的名字都是你原来工程里面配置的名字,比如AppIcon29x29@2x.png,AppIcon就是我在Xcode-General-App icons And Launch Images-App Icons Source中配置的名字,launchImage同理let destinationNameArray = ["AppIcon29x29@2x.png", "AppIcon29x29@3x.png","AppIcon40x40@2x.png", "AppIcon40x40@3x.png","AppIcon60x60@2x.png", "AppIcon60x60@3x.png","LaunchImage-700@2x.png", "LaunchImage-700-568h@2x.png","LaunchImage-800-667h@2x.png", "LaunchImage-800-Portrait-736h@3x.png"]let sourcePathArray = [record.icon58Path,record.icon87Path,record.icon80Path,record.icon120Path,record.icon120_2Path,record.icon180Path,record.launch4Path,record.launch5Path,record.launch6Path,record.launch6pPath]for index in 0..<sourcePathArray xss=removed xss=removed xss=removed> 0 {appName = targetName}let payloadPath = PathDefine.UnzipPath + "/Payload/" + URL.init(fileURLWithPath: appName).lastPathComponent + ".app"let destinationPath = payloadPath + "/" + destinationNameArray[index]if manager.fileExists(atPath: destinationPath) {do {veItem(atPath: destinationPath)} catch {complete(false)print(error.localizedDescription)}do {pyItem(atPath: sourcePathArray[index], toPath: destinationPath)} catch {complete(false)print(error.localizedDescription)}}}complete(true)}

5编辑entitlement文件

entitlement文件文件里面存放了和签名有关的内容,是通过当前这个provisionFile文件和plist中的相关内容生成的

private func createEntitlements(_ complete: @escaping (Bool) -> Void) {//删除entitlement文件let entitlmentPath = PathDefine.UnzipPath + "/Entitlements.plist"let manager = FileManager.defaultif manager.fileExists(atPath: entitlmentPath) {do {veItem(atPath: entitlmentPath)} catch {print(error.localizedDescription)}}let task = Process.init()task.launchPath = "/usr/bin/security"//record.provisionPath是我模型中ProvisonFile的路径,你可以替换你的ProvisionFile的绝对路径。但是,但是,但是,你plist中Bundle Identifier的值必须和你ProvisionFile指定的id相同,比如你ProvisionFile指定的id是st,那么你的plist中Bundle Identifier也必须是这个值,否则通不过security过程。另外ProvisionFile类型最好是发布类型,开发类型的我没测试过task.arguments = ["cms", "-D", "-i", record.provisionPath]task.currentDirectoryPath = PathDefine.UnzipPathlet pi = Pipe.init()task.standardOutput = pilet file = pi.fileHandleForReadingtask.launch()self.checkComplete(task: task) { [unowned self] (result) inif result == false {let errorString = String.init(data: adDataToEndOfFile&#40;&#41;, encoding: .utf8) ?? ""complete(false)} else {//这个地方会得到一个security过的字符串,但是在Mac OS 10.10以上会报出“SecPolicySetValue”打头的一句话,这个必须去掉。此时调试台也会打印“security: SecPolicySetValue: One or more parameters passed to a function were not valid.”那是正常的,不管它let data = adDataToEndOfFile&#40;&#41;var entitlementsResult = String.init(data: data, encoding: .ascii) ?? ""ains("SecPolicySetValue") {var inOutput : Array<String> = entitlementsResultponents(separatedBy: "n")ve(at: 0)entitlementsResult = inOutput.joined(separator: "n")}let entitlementData = entitlementsResult.data(using: .utf8)!do {var entitlementDic = try PropertyListSerialization.propertyList(from: entitlementData, options: PropertyListSerialization.ReadOptions.mutableContainers, format: nil) as! Dictionary<String>entitlementDic = entitlementDic["Entitlements"] as! Dictionary<String>
//entitlementDic中有一个key为OtherStringDefine.appid的字段必须改为"teamid.bundlId"这种格式,teamID就是你证书后面括号中那一串,没有括号的去你的开发者帐号里面查,每个证书都有一个teamIDentitlementDic.updateValue(String.init(format: "%@.%@", amId, d.bid), forKey: OtherStringDefine.appid)//移除无用的字段veValue(forKey: OtherStringDefine.veValue(forKey: OtherStringDefine.kKeychainAccessGroups)//和plist文件一样,转换为XML文件重新写入原来的目录中do {let xmlData = try PropertyListSerialization.data(fromPropertyList: entitlementDic, format: .xml, options: 0) as NSDataif xmlData.write(toFile: entitlmentPath, atomically: true) == false {complete(false)return}} catch {print(error.localizedDescription)}} catch {print(error.localizedDescription)}complete(true)}}}

6替换Provision文件

这里要注意一下,放入打包目录里面的provision文件的名字必须是bileprovision

    private func editProvisionFile&#40;_ complete: @escaping (Bool&#41; -> Void) {//替换provisioninglet manager = FileManager.defaultvar appName = record.ipaInputPathappName = appName.substring(to: appName.dIndex, offsetBy: -4))if unt > 0 {appName = targetName}let payloadPath = PathDefine.UnzipPath + "/Payload/" + URL.init(fileURLWithPath: appName).lastPathComponent + ".app"let appProfilePath = payloadPath + "/bileprovision"if manager.fileExists(atPath: appProfilePath) {do {veItem(atPath: appProfilePath)} catch  {print(error.localizedDescription)}}//这里用了Process来做拷贝,其实用FileManager也是一样的,我之前遇到一些问题,总以为是因为使用FileManager造成的权限问题,但最后通过对比实验,发现不是这里造成的let task = Process.init()task.launchPath = "/bin/cp"task.arguments = [record.provisionPath, appProfilePath]let pi = Pipe.init()task.standardOutput = pilet file = pi.fileHandleForReadingtask.launch()let data = adDataToEndOfFile&#40;&#41;print(String.init(data: data, encoding: .utf8) ?? "")self.checkComplete(task: task) { (result) incomplete(result)}}

7重签名


// 在.app目录会有一些图片缓存,必须清理一下,否则不能通过签名private func removeUnrealizePath(_ complete: @escaping (Bool) -> Void) {let task = Process.init()task.launchPath = "/usr/bin/xattr"var appName = record.ipaInputPathappName = appName.substring(to: appName.dIndex, offsetBy: -4))if unt > 0 {appName = targetName}let appPath = PathDefine.UnzipPath + "/Payload/" + URL.init(fileURLWithPath: appName).lastPathComponent + ".app"task.arguments = [ "-cr", appPath]//        task.currentDirectoryPath = appPathlet pi = Pipe.init()task.standardOutput = pilet file = pi.fileHandleForReadingtask.launch()let data = adDataToEndOfFile&#40;&#41;//        print(String.init(data: data, encoding: .utf8))self.checkComplete(task: task) { (result) incomplete(result)}}private func doCodeSign(_ complete: @escaping (Bool) -> Void) {veUnrealizePath { [unowned self] (result) invar appName = d.ipaInputPathappName = appName.substring(to: appName.dIndex, offsetBy: -4))if self.unt > 0 {appName = self.targetName}let appPath = PathDefine.UnzipPath + "/Payload/" + URL.init(fileURLWithPath: appName).lastPathComponent + ".app"var task = Process.init()task.launchPath = "/bin/sh"task.currentDirectoryPath = PathDefine.UnzipPath//这里是在解压目录下寻找需要加签的文件,并放入中。这里用;n来分隔连续执行的命令,用n来分隔一条命令中需要换行的部分var cmdString = "find -d " + PathDefine.UnzipPath + " \( -name "*.app" -o -name "*.appex" -o -name "*.framework" -o -name "*.dylib" \) > ;n"//把刚才那个中得到的文件名字取出来,依次签名。ificate是你证书的名字,不是证书路径,在钥匙串里面选中证书后点显示简介就能看到cmdString = cmdString.appendingFormat("while IFS='' read -r line || [[ -n "$line" ]]; do n /usr/bin/codesign --continue -f -s "%@" --entitlements "Entitlements.plist"  "$line" n done < directories xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed> Void) {let task = Process.init()let ipaOutputPath = PathDefine.outputPath + "/" + record.appName + ".ipa"task.launchPath = "/usr/bin/zip"task.arguments = ["-qry", ipaOutputPath, "Payload/"]task.currentDirectoryPath = PathDefine.UnzipPathlet pi = Pipe.init()task.standardOutput = pilet file = pi.fileHandleForReadingtask.launch()let data = adDataToEndOfFile&#40;&#41;print(String.init(data: data, encoding: .utf8) ?? "")self.checkComplete(task: task) { [unowned self] (result) inif result == false {complete(false)} else {//这里就是把解包的目录删了self.clear({ (result) inif result == false {print("清理缓存失败")}complete(true)})}}}

如果所有的命令都执行完了,并且都成功了,那么可把find调出来并打开输出的目录

NSWorkspace.shared().openFile&#40;PathDefine.outputPath, withApplication: "Finder"&#41;

写在最后,整理这个重新打包的教程花了我一个周的时间,其中有的时间花在了制作Mac客户端的界面和学习shell命令上,而且在使用的Process的时候也遇到很多问题。此外,重签名的步骤不能乱,目前这个顺序就是经过我反复验证的了,文中出现了一些外面传进来的变量,比如record变量,它的属性名的字面意思已经再清楚不过了,这里就不再一一说明

本文发布于:2024-02-01 01:07:22,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170672084432728.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:方式   iOS
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23