在使用JSPatch時,JS指令碼理論上可以調用任意OC方法,許可權非常大,若經過HTTP傳輸時,被中間人攻擊篡改js代碼,則會造成很大危害。
鑒於此種情況
1. 伺服器盡量使用https傳輸2. 對傳輸的代碼做好加密和校正
接下來,以伺服器端使用php,移動端iOS,主要對第二種方式進行處理
RSA演算法
RSA是目前最有影響力的公開金鑰加密演算法,它能夠抵抗到目前為止已知的絕大多數密碼攻擊,已被ISO推薦為公開金鑰資料加密標準。
RSA演算法是一種非對稱式加密演算法,常被用於加密資料傳輸.如果配合上數字摘要演算法, 也可以用於檔案簽名.
RSA演算法是一種非對稱演算法,演算法需要一對密鑰,使用其中一個加密,需要使用另外一個才能解密。我們在進行RSA加密通訊時,就把公開金鑰放在用戶端,私密金鑰留在伺服器。
一般來說
1.公開金鑰加密,私密金鑰解密2.私密金鑰對檔案進行簽名,公開金鑰對簽名進行驗證
公開金鑰、私密金鑰產生
-
使用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架構,加密解密代碼與架構無直接關係)
如果得不到resourceID,先查看是否開啟openssl擴充,之後查看private_key是否私自添加了縮排等。
=============
iOS使用JSPatch
JSPatch指令碼的執行許可權很高,若在傳輸過程中被中間人篡改,會帶來很大的安全問題,為了防止這種情況出現,我們在傳輸過程中對JS檔案進行了RSA簽名加密,流程如下:
服務端:
- 計算 JS 內容 MD5 值。
- 用 RSA 私密金鑰對 MD5 值進行加密,與JS內容一起下發給用戶端。
用戶端:
- 拿到加密資料,用 RSA 公開金鑰解密出 MD5 值。
- 本地計算返回的 JS 內容 MD5 值。
- 對比上述的兩個 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加密解密
- 匯入Security.framework
- 把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]]; // 解密前進行 GTMBase 64 編碼 NSData *EncryptData = [GTMBase64 decodeString:aString]; NSString *unCode = [SecurityUtil decryptAESData:EncryptData app_key:aesKey]; //NSLog(@"xx 解密:%@", string1); return unCode; }