美文网首页IT编程iOS开发iOS
PHP、iOS 使用JSPatch基本与RSA,AES加密

PHP、iOS 使用JSPatch基本与RSA,AES加密

作者: 小葫芦lu | 来源:发表于2016-03-13 20:40 被阅读1804次

    在使用JSPatch时,JS脚本理论上可以调用任意OC方法,权限非常大,若经过HTTP传输时,被中间人攻击篡改js代码,则会造成很大危害。

    鉴于此种情况

    1. 服务器尽量使用https传输
    2. 对传输的代码做好加密和校验
    

    接下来,以服务器端使用php,移动端iOS,主要对第二种方式进行处理

    RSA算法

    RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。

    RSA算法是一种非对称加密算法,常被用于加密数据传输.如果配合上数字摘要算法, 也可以用于文件签名.

    RSA算法是一种非对称算法,算法需要一对密钥,使用其中一个加密,需要使用另外一个才能解密。我们在进行RSA加密通讯时,就把公钥放在客户端,私钥留在服务器。

    一般来说
    1.公钥加密,私钥解密
    2.私钥对文件进行签名,公钥对签名进行验证
    

    公钥、私钥生成

    1. 使用openSSL命令生成密钥

    openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem
    

    按照提示,填入私钥的密码(之后会使用),签名证书的组织名、邮件等信息之后,就会生成包含有公钥的证书文件public_key.der和私钥文件private_key.pem。

    public_key.der文件用于分发到ios客户端进行公钥加解密,而private_key.pem文件留在服务器端供php使用

    openssl rsa -in private_key.pem -pubout -out public_key.pem
    

    此命令会根据输入的私钥文件生成pem格式的公钥文件,这也是把private_key放在服务端的原因

    服务端php代码(ThinkPHP框架,加密解密代码与框架无直接关系)

        <?php
        /**
         * Created by PhpStorm.
         * User: and
         * Date: 16/2/1
         * Time: 10:14
         */
        
        namespace Home\Controller;
        
        class RsaController extends CommenController {
        //使用文本打开之前获取的private_key.pem可得如下
            const PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----
        MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAN2DTzqHsIiEw8bQ
        R3MT9FpwVzet+kGQphJCFfY6A5u4gK6BuiVKqJpRroJlzg5yT3zy5tzowpSqIuMZ
        8104ncih3uvKoNvvPhwjTy6mGHJHoKaGBlnK7oMXOmi50wVA8qvf++kZnxn9W7tM
        YnCe6GSkQBS5KgythpIqPaqcaY1dAgMBAAECgYEAjTfqacEZvV8OxRABjQ76qDGY
        mOm0cto51cgF4k0IAd21RAt2VdHr/T33yDAJFtKvdFQS9GD7s/VnemsP6K1wgMld
        AvV2+KAPK2ZcCNTktLLBmOikJtBYQZGBnaAlxKQD2RFr+YJRCORxSQfOgGVxzch6
        tgXC7VyQmddYBvaOEq0CQQD0dRHMxvn8iTa1x+Df4ghE3XZwyG8rDIpMehfAQdm/
        hgf5Z1DX56DOG0LD99OMH5wE+C8CHdSP9F842cFJrZjTAkEA5/jlFcQU3vW/6fmk
        XshHyaF40s9+5K84i/1EzTW/Wx08zZGql/WrTuQ8QllMAUDR6+kZvPLSPexA/8DS
        e8xjDwJAObn7fhPurIfqd3q/y56gvUJe2bs7JTtM3UpnmWrzdJq9/1M6cAGuo30k
        gwpe1lQQj8vbrfBFZckbQ12Im1F3KQJBAIBQCY+nnY/SyaxHfWc8S5E5cxbQ1bTz
        Q0kT+Cm2sDlbC9X93CogJvkFgFuG/2a2Dyf6EVWVzzuXYkDVzNfTr3sCQQCdTK7v
        I+C/aVcGFFYE0ZL5y3zxUtccBdhJNORb2fG5Oa7tqJH2bancuspeoArcpqElH7GQ
        Mm9YArj1T6E10X0E
        -----END PRIVATE KEY-----';
        
            public function test() {
    
        //此处为了验证,所以取出publick_key.pem中的public_key
            $public_key = '-----BEGIN PUBLIC KEY-----
        MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdg086h7CIhMPG0EdzE/RacFc3
        rfpBkKYSQhX2OgObuICugbolSqiaUa6CZc4Ock988ubc6MKUqiLjGfNdOJ3Iod7r
        yqDb7z4cI08uphhyR6CmhgZZyu6DFzpoudMFQPKr3/vpGZ8Z/Vu7TGJwnuhkpEAU
        uSoMrYaSKj2qnGmNXQIDAQAB
        -----END PUBLIC KEY-----';
    
        //之前使用openssl生成private_key时填写的密码
        $open_key = '123456';
            $pi_key = openssl_pkey_get_private(self::PRIVATE_KEY,$open_key);//这个函数可用来判断私钥是否是可用的,可用返回资源id Resource id
            echo $pi_key,"\n";
            $pu_key = openssl_pkey_get_public($public_key);//这个函数可用来判断公钥是否是可用的
            echo $pu_key,"\n";
    
            $data = "xiaohulu123";//原始数据
            $encrypted = "";
            $decrypted = "";
            echo "source data:",$data,"\n";
            echo "private key encrypt:\n";
    
            openssl_public_encrypt($data,$encrypted,$pu_key);//公钥加密,私钥解密
            $encrypted = base64_encode($encrypted);//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
            echo "公钥加密后:",$encrypted,"\n";
    
            openssl_private_decrypt(base64_decode($encrypted),$decrypted,$pi_key);//私钥加密的内容通过公钥可用解密出来
            echo "私钥解密:",$decrypted,"\n";
    
            echo "\n","---------------------------------------","\n";
    
            //私钥对数据进行签名,公钥解密验证
            $bicode = "";
            $cdbicode = "";
            openssl_private_encrypt($data, $bicode, $pi_key);
            $bicode = base64_encode($bicode);
            echo "私钥签名:",$bicode,"\n";
    
            openssl_public_decrypt(base64_decode($bicode), $cdbicode, $pu_key);
            echo "公钥验证签名:",$cdbicode,"\n";
    
        }
    
        }
    

    如果得不到resourceID,先查看是否开启openssl扩展,之后查看private_key是否私自添加了缩进等。

    =============

    iOS使用JSPatch

    JSPatch脚本的执行权限很高,若在传输过程中被中间人篡改,会带来很大的安全问题,为了防止这种情况出现,我们在传输过程中对JS文件进行了RSA签名加密,流程如下:

    服务端:

    1. 计算 JS 内容 MD5 值。
    2. 用 RSA 私钥对 MD5 值进行加密,与JS内容一起下发给客户端。

    客户端:

    1. 拿到加密数据,用 RSA 公钥解密出 MD5 值。
    2. 本地计算返回的 JS 内容 MD5 值。
    3. 对比上述的两个 MD5 值,若相等则校验通过,取 JS 文件保存到本地。
      由于 RSA 是非对称加密,在没有私钥的情况下第三方无法加密对应的 MD5 值,也就无法伪造 JS 文件,杜绝了 JS 文件在传输过程被篡改的可能。

    服务端php代码

    /*
         * 加密一个字符串,返回RSA加密后的内容
         * aString 需要加密的字符串
         * return encrypted rsa加密后的字符串
         */
        public function enjscode($aString) {
            $pi_key = openssl_pkey_get_private(self::PRIVATE_KEY);//这个函数可用来判断私钥是否是可用的,可用返回资源id Resource id
            openssl_private_encrypt($aString, $encrypted, $pi_key);//私钥加密
            $encrypted = base64_encode($encrypted);//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
            return $encrypted;
        }
    
        //JS
        public function jscode() {
            //$headers = $this->verifyHeaders();
            //验证header
            //$this->verifyHeadersWithHeaders($headers);
    
            $data['con'] = "defineClass('FindViewController',{viewDidLoad: function() {self.super().viewDidLoad();self.setTitle('发现哈哈');_selectedIndex = 0;self.initView();}})";
            $data['isUpdate'] = 'true';
           //首先计算js内容的md5值,再将md5值进行RSA加密
            $data["ver"] = self::enjscode(md5($data['con']));
    
            $data1['con'] = "require('UIAlertView');defineClass('CircleHotPageViewController',{tableView_didSelectRowAtIndexPath:function(tableView,indexPath){tableView.deselectRowAtIndexPath_animated(indexPath, YES);var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles('只是提示而已', '测试JSPath!', null, '知道了', null, null);alert.show();}})";
            $data1['isUpdate'] = 'true';
            $data1["ver"] = self::enjscode(md5($data1['con']));
    
            $this->json_out('200','0','',$da);
        }
    
    iOS端代码

    由于iOS端原生加解密并不是很好用,所以在此使用github上已经封装好的RSA加解密方法,下载请点击 RSA加密解密

    1. 导入Security.framework
    2. 把public_key.pem中的public_key取出
    #define rsa_public_key @"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdg086h7CIhMPG0EdzE/RacFc3rfpBkKYSQhX2OgObuICugbolSqiaUa6CZc4Ock988ubc6MKUqiLjGfNdOJ3Iod7ryqDb7z4cI08uphhyR6CmhgZZyu6DFzpoudMFQPKr3/vpGZ8Z/Vu7TGJwnuhkpEAUuSoMrYaSKj2qnGmNXQIDAQAB"
    

    3.在didFinishLaunchingWithOptions方法中进行网络请求,从网络断获取内容data

        /*
         *  获取内容为
         *  con         js内容
         *  isUpdate    是否更新(无用)
         *  ver         验证内容,有con 计算md5,再进行rsa加密得来  ->  客户端首页解密得到con的md5值,将此md5值与获取的js内容的md5值进行比较
         *      
         *  若值相等,则说明无篡改;否则,说明被篡改,放弃存储
         */
        
        - (void)upJSHandle:(NSDictionary *)response {
        NSMutableString *jsString = [NSMutableString string];
        for (NSDictionary *di in (NSArray *)response) {
            NSString *js = di[@"con"];
           
           //计算获取到的js内容的md5值
            NSString *jsMd5 = [MiscTool md5:js];
            
            //解密获取js内容的md5
            NSString *cdMd5 = [RSA decryptString:di[@"ver"] publicKey:rsa_public_key];
            NSLog(@"cd bicode = %@",cdMd5);
            
            //校验
            if (![jsMd5 isEqualToString:cdMd5]) {//经过校验,不相等,说明内容被篡改,直接返回不存储
                NSLog(@"zz 内容被篡改!!!");
                return;
            }
            
            [jsString appendString:js];
            //将代码下载到本地,保存成文件Document/up.js, 各个方法之间使用 ***** 分隔符
            [jsString appendString:@"*****"];
            }
            
            //获取保存js文件内容
            NSString *jsPath = [self jsFilePath];
            NSLog(@"js xx = 写入 %@",jsString);
            
            //将jsString 进行aes对称加密
            //jsString = [self aes:jsString].mutableCopy;
            //NSLog(@"xx 加密后 = %@",jsString);
            
            NSData *jsData = [jsString dataUsingEncoding:(NSUTF8StringEncoding)];
            //写入文件
            [jsData writeToFile:jsPath atomically:YES];
            //执行
            [self execUpJsWithJsArray:[self readJs]];
        }
    

    4.在applicationDidBecomeActive方法,读取js文件,并执行

        #pragma mark -- js 读取
        - (NSArray *)readJs {
            NSString *file = [self jsFilePath];
            NSFileManager *fileManager = [NSFileManager defaultManager];
            if(![fileManager fileExistsAtPath:file]) {//如果不存在
                return @[];
            }
            NSString *jsString = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
            
            //取出后,将jsString 解密
            //jsString = [self cdAes:jsString];
            //NSLog(@"xx 解密后 = %@",jsString);
            
            NSArray *jsArray = [jsString componentsSeparatedByString:@"*****"];
            return jsArray;
        }
        
        - (void)execUpJsWithJsArray:(NSArray *)jsArray {
            if (jsArray.count == 0) {
                return;
            }
            [JPEngine startEngine];
            for (NSString *js in jsArray) {
                if ([js isEqualToString:@""] || js == nil) {
                    continue;
                }
                NSLog(@"read js = %@",js);
                [JPEngine evaluateScript:js];
            }
        }
    
        - (void)applicationDidBecomeActive:(UIApplication *)application {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
        //每次进入时执行js
        [self execUpJsWithJsArray:[self readJs]];  
        }
    

    本地存储

    本地存储的脚本被篡改的机会小很多,只在越狱机器上有点风险,对此可以在下载完脚本保存到本地时进行对称加密,每次读取时解密。

    aes加密解密
        /*
         *  进行aes对称加密
         */
        - (NSString *)aes:(NSString *)aString {
            NSString *biCode = [SecurityUtil encryptAESData:aString app_key:aesKey];
            //NSLog(@"xx 加密: %@",st);
            return biCode;
        }
        /*
         *  对字符串进行aes解密
         */
        - (NSString *)cdAes:(NSString *)aString {
            //NSData *EncryptData1 = [GTMBase64 decodeString:[SecurityUtil encryptAESData:string app_key:aesKey]]; // 解密前进行 GTMBase64 编码
            NSData *EncryptData = [GTMBase64 decodeString:aString];
            NSString *unCode = [SecurityUtil decryptAESData:EncryptData app_key:aesKey];
            //NSLog(@"xx 解密:%@", string1);
            return unCode;
        }
    

    相关文章

      网友评论

      • fonglaaam:好东西啊 不过好像如果使用阿里百川的hotfix的sdk的话 就不用自己做这一层了吧

      本文标题:PHP、iOS 使用JSPatch基本与RSA,AES加密

      本文链接:https://www.haomeiwen.com/subject/vmqskttx.html