总结:
1、在传输过程中对JS文件进行了RSA签名加密;
2、下载完脚本保存到本地时,应进行简单的对称加密,每次读取时解密;
3、建议js脚本的增、删、改、查的内容在同一个js文件处理,只下载一个文件就OK。
继上一篇了解工作原理后,这篇谈谈一下自行搭建管理后台实现补丁更新的基本思路。
先假设遇到以下问题:App发行了两个版本,V1.0和V2.0,上线投产后发现V1.0存在bug1,V2.0存在bug2;
前提准备:
a、本地生成RSA密钥对,公钥可以公开,打包作为配置常量放进ipa文件,私钥自己保管好,存放在如PSAM卡、带加密功能的磁盘载体等。
b、制作好的脚本文件(即补丁)。
基本原理:安装本地所有补丁 –> 联网更新补丁信息,并安装有更新或新增加的补丁。
具体流程:
1、在管理后台新建需要更新补丁的版本号,然后上传bug文件(*.js)。上传脚本时,选择本地的 rsa_private_key.pem (RSA私钥)文件,与脚本一同上传,管理平台会使用这个上传的 Private Key 对脚本 MD5 值进行加密,得到加密结果并与该脚本文件一一对应保存起来。
2、客户端判断本地是否有脚本,有则在程序开始的时候加载执行(若文件对称加密了需先解密),无则跳过;
3、客户端联网请求补丁更新,上传时参数带版本信息(如版本号),查询当前版本是否有bug。
4、管理平台会只针对这个版本号下发对应的 JS 脚本(实现对V1.0和V2.0分别处理),若版本号对应不上或无bug,客户端也就请求不到相应的 JS 脚本;若有bug,由服务器返回step1的脚本和加密MD5值。
5、客户端计算脚本的MD5值(MD5_1),通过内置的RSA公钥解密 step4 的MD5得到值MD5_2,比较MD5_1和MD5_2是否相同,能够解密得出来并结果相同,说明加密者一定是使用了step1中RSA私钥,服务器返回数据可信,先保存到App本地缓存(保存到本地时可进行简单的对称加密,每次读取时解密)。
6、以后App每次启动,都需要加载并执行本地已保存好的这个脚本(即上面的step2),这样就可以实现补丁更新了。
注意:这里上传的 rsa_private_key.pem 只是一次性使用,不会保存在服务端,所以只有通过用户自己保存的 rsa_private_key.pem 文件才可以针对 APP 下发脚本,即使管理平台被黑,黑客也无法对你的 APP 下发恶意脚本(可以下发,但验证不过,不会执行),保证安全性。rsa_private_key.pem 请妥善保管,避免泄露。
待讨论:上面步骤中,补丁文件没有强制更新,它只会加载上一次已下载好的脚本。假设在AppDelegate中加载脚步,且客户端一直休眠在系统后台没有重启,那么这种方案无法对每一个用户实现即时修复。有人说,那好办,我在程序入口处设置必须拉取服务器脚本成功后才能进入程序界面,这样可以强制用户实现即时修复,我觉得也是有问题的:如果网络不稳定,拉取请求失败,客户端应该如何呈现?是退出(用户体验不好)还是跳转到程序界面(这样就无法在本地执行脚本)呢?
App需要具备 “增、删、改、查” 功能
其他问题:如果V1.0新发现了bug3;或者发现上一次脚步内容有纰漏需要修改;或者取消这个版本的补丁,客户端和服务器应该如何应对?这就要求app需要具备 “增、删、改、查” 功能。
增:服务器返回的补丁,本地不存在时,会默认下载存储,并执行.
删: 服务器返回的补丁集中,不包含本地的某个补丁,则此补丁下次不会再被执行.
改: 服务器返回的补丁,本地包含,但md5值变化,此时会重新下载此补丁.
查: 会默认在应用启动时,执行所有存在,且md5值匹配的补丁.补丁集的信息,会在每次联网更新时更新.此处使用的是一个缓存库
补丁状态的管理详见点击打开链接
优化:鉴于上述步骤可能会存在操作复杂性,采用一次性下载所有可能会更好:把增、删、改、查的内容全部写到一个js文件,
1、客户端每次请求时先搜索本地是否有js文件并计算MD5值,若无值则不传,若有则新增传MD5值;
2、服务器判断MD5 不为空,进行两个MD5值比对,不一致下发脚本;
3、客户端缓存脚本(首次下载)或替换脚本(本地已缓存)
这里要注意删除脚本问题,如果后台把所有脚本删除了,那么客户端也要根据返回的空白内容删除本地缓存。
Mac电脑获取某个文件的md5值,直接在终端输入命令:
md5 文件完整路径
Objective-C获取某个文件的md5值:
#include <CommonCrypto/CommonDigest.h>:
/**
* 获取文件的md5值.
* @param path 文件路径.
* @return 文件的md5值.
*/
-(NSString *)mcMd5HashOfPath:(NSString *)path
{
NSFileManager *fileManager = [NSFileManager defaultManager];
// 确保文件存在.
if( [fileManager fileExistsAtPath:path isDirectory:nil] )
{
NSData *data = [NSData dataWithContentsOfFile:path];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5( data.bytes, (CC_LONG)data.length, digest );
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for( int i = 0; i < CC_MD5_DIGEST_LENGTH; i++ ) {
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
else
{
return @"";
}
}
在工程中放一个demo.js供Debug模式下调试
/**
* 测试模式下,会执行此方法,以验证某个JS文件的作用.默认使用本地demo.js.
*/
- (void)mcDebug
{
#ifdef DEBUG
NSString * path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
[self mcEvaluateScriptFile: path];
#endif
}
jspatch解决AppStore审查机制解决方案是:点击打开链接
参考:点击打开链接