<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          iOS 用 RSA 保證下載資源可靠性

          共 18490字,需瀏覽 37分鐘

           ·

          2021-06-22 04:42

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

          前言

          有時(shí)需要在本地存儲(chǔ)資源,并且從服務(wù)器下載資源,因?yàn)樯婕暗竭\(yùn)行期間的安全性,有必要添加校驗(yàn)的邏輯,因此有了本文的一些思考。

          ipa包被篡改的情況

          首先思考的是ipa包的安全性問(wèn)題。通過(guò)iTunes,我們可以下載ipa并且解壓,修改包中的文件,再壓縮成ipa包。

          • 1、如果開(kāi)發(fā)者A拿到應(yīng)用P的ipa包,修改其中的任何文件,都會(huì)導(dǎo)致簽名失效,ipa包無(wú)法安裝。(簽名存放在.app文件的_CodeSignature文件夾)
          • 2、如果開(kāi)發(fā)者B拿到應(yīng)用P的ipa包,安裝到自己手機(jī)中,再直接修改Bundle/Application 下的配置文件,此時(shí)應(yīng)用P仍舊可以運(yùn)行。
          • 3、如果開(kāi)發(fā)者C拿到應(yīng)用P的ipa包,修改其中的某些配置文件,用自己的證書(shū)重簽名并通過(guò)其他渠道發(fā)布出去,ipa包可以正常安裝。

          應(yīng)用在正常使用過(guò)程中,app包的文件是無(wú)法修改的,只有越獄的機(jī)子才會(huì)出現(xiàn)情況2;情況3中重簽名的ipa包無(wú)法上傳AppStore。真機(jī)app安裝目錄是 var/mobile/Containers/Bundle/Application 沙盒目錄是 var/mobile/Containers/Data/Application 類(lèi)似的,模擬的安裝目錄同樣在/Bundle下,沙盒在/Data下;

          下載資源的驗(yàn)證

          下載的資源存在沙盒目錄,在未越獄的情況下,開(kāi)發(fā)者并不能修改其中的文件。但是,下載資源通常使用http/https進(jìn)行資源下載,通過(guò)使用代理的方式,可以修改下載的資源(除非開(kāi)啟了https證書(shū)校驗(yàn))。為了保證下載資源的可靠性,采用了一套基于RSA算法的驗(yàn)證方案,具體的要點(diǎn)有:
          1、開(kāi)發(fā)者產(chǎn)生一對(duì)密鑰:公鑰和私鑰,私鑰保存在配置平臺(tái)(后臺(tái)),公鑰放到客戶(hù)端。
          2、當(dāng)文件上傳到配置平臺(tái)后,配置平臺(tái)對(duì)文件進(jìn)摘要(hash)得到md5str,并私鑰對(duì)md5str進(jìn)行簽名得到signStr,然后把 文件和signStr下發(fā)給客戶(hù)端。
          3、客戶(hù)端下載文件和signStr,計(jì)算文件的摘要(md5)得到md5str,用md5str和公鑰驗(yàn)證signStr的有效性。

          解釋?zhuān)?/strong> 非對(duì)稱(chēng)加密算法的計(jì)算比較復(fù)雜 ,所以只對(duì)摘要(md5值)進(jìn)行加密;
          具體的流程圖如下:

          iOS的RSA算法

          RSA算法的兩種加密方式:

          • 公鑰加密,私鑰解密。(一般用于公鑰持有方(客戶(hù)端)向私鑰持有者(后臺(tái))發(fā)送消息)
          • 私鑰加密,公鑰解密。(一般用于簽名和驗(yàn)證,私鑰加密相當(dāng)于簽名,公鑰解密相當(dāng)于驗(yàn)證)

          蘋(píng)果提供的Security.framework,有以下四個(gè)方法:

          • SecKeyEncrypt—encrypts a block of data using the specified key.(使用公鑰對(duì)數(shù)據(jù)進(jìn)行加密)
          • SecKeyDecrypt—decrypts a block of data using the specified key. (使用私鑰對(duì)數(shù)據(jù)進(jìn)行解密)
          • SecKeyRawSign—signs a block of data using the specified key.(使用私鑰對(duì)數(shù)據(jù)簽名)
          • SecKeyRawVerify—verifies a signature against a block of data and a specified key. (使用公鑰對(duì)數(shù)字簽名進(jìn)行驗(yàn)證)

          類(lèi)比到OpenSSL,其提供了以下四個(gè)接口:

          int     RSA_public_encrypt(int flen, const unsigned char *from,
                          unsigned char *to, RSA *rsa,int padding);
          int     RSA_private_encrypt(int flen, const unsigned char *from,
                          unsigned char *to, RSA *rsa,int padding);
          int     RSA_public_decrypt(int flen, const unsigned char *from,
                          unsigned char *to, RSA *rsa,int padding);
          int     RSA_private_decrypt(int flen, const unsigned char *from,
                          unsigned char *to, RSA *rsa,int padding);

          因?yàn)镽SA算法的計(jì)算量較大,一般不會(huì)直接使用RSA對(duì)數(shù)據(jù)進(jìn)行加密,而是對(duì)AES的密匙進(jìn)行加密,再用AES對(duì)數(shù)據(jù)加密。
          RSA算法原理 ,這里有一篇詳細(xì)介紹RSA算法原理的文章。

          數(shù)字簽名的保存

          拿到后臺(tái)下發(fā)的簽名后,就需要保存簽名,可以選擇:保存在文件中、保存到NSUserDefault、保存到數(shù)據(jù)庫(kù)等。除此之外,是否可以保存在文件屬性?
          寫(xiě)了一段代碼進(jìn)行測(cè)試:

              NSMutableDictionary *changedAttrDict = [[NSMutableDictionary alloc] init];
              [changedAttrDict setObject:@"loying" forKey:NSFileOwnerAccountName];
              [changedAttrDict setObject:@"NSFileGroupOwnerAccountName" forKey:NSFileGroupOwnerAccountName];
              [changedAttrDict setObject:[NSDate dateWithTimeIntervalSinceNow:3600] forKey:NSFileCreationDate];
              
              NSError *error;
              
              BOOL ret = [[NSFileManager defaultManager] setAttributes:changedAttrDict ofItemAtPath:encodedDataPath error:&error];

          經(jīng)過(guò)測(cè)試,NSFileCreationDate這個(gè)屬性是可以修改的;
          NSFileGroupOwnerAccountNameNSFileOwnerAccountName不能修改(真機(jī)為@"mobile");模擬器不可以修改兩個(gè)屬性,最大的可能性是因?yàn)槟M器運(yùn)行產(chǎn)生的文件,權(quán)限不夠修改文件屬性;
          createDirectoryAtPath:withIntermediateDirectories:attributes:這個(gè)方法同樣有這個(gè)限制。
          寫(xiě)入文件屬性還有其他的限制,當(dāng)文件在不同硬盤(pán)格式(HFS+ and FAT32)拷貝的時(shí)候,文件附帶的屬性可能會(huì)消失。

          NSFileProtectionKey 是后臺(tái)模式下的文件讀寫(xiě)

          為了開(kāi)發(fā)方便,可以選擇保存到NSUserDefault的方式。
          新建NSMutableDictionary,用文件作為key,用FileConfig作為value。FileConfig是驗(yàn)證相關(guān)的屬性封裝,便于后續(xù)開(kāi)發(fā)。

          1、NSUserDefault所有的屬性最終會(huì)寫(xiě)入Libary/Preference/下的plist文件中,所以NSUserDefault中不能存儲(chǔ)敏感信息。
          2、如果遇到錯(cuò)誤:Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object

          那是因?yàn)镹SUserDefault只能存基本類(lèi)型,如果dict里面存有自定義類(lèi)型,需要先轉(zhuǎn)成NSData再存儲(chǔ);(plist里面沒(méi)有結(jié)構(gòu)信息,只有key-value)

          iOS接入步驟

          上面介紹了基于RSA的下載資源驗(yàn)證方案,iOS具體的流程如下:

          • 后臺(tái)上傳資源文件,配置平臺(tái)對(duì)文件進(jìn)行hash并用私鑰進(jìn)行簽名得到簽名串signature;
          • 把文件和signature打包成zip包,下發(fā)到客戶(hù)端;
          • 客戶(hù)端解壓zip,得到文件和簽名串signature,對(duì)文件進(jìn)行hash,加載本地公鑰,把hash值、signature、公鑰傳給Security.framework;
          • 用Security.framework提供的SecKeyRawVerify方法對(duì)hash值、signature、公鑰進(jìn)行驗(yàn)證,如果通過(guò)則表示文件未修改。

          1、zip解壓

          iOS平臺(tái)上可以使用MiniZipArchive進(jìn)行解壓。

          - (BOOL)unzipFile:(NSString *)file toFilePath:(NSString *)unZipFilePath overWrite:(BOOL)overWrite
          {
              MiniZipArchive *za = [[MiniZipArchive alloc] init];
              BOOL success = NO;
              if ([za UnzipOpenFile:file]) {
                  success = [za UnzipFileTo:unZipFilePath overWrite:overWrite];
                  [za UnzipCloseFile];

              }
              return success;
          }

          2、公鑰和私鑰的加載

          .der格式和.pem格式:.der格式表示二進(jìn)制編碼,.pem格式表示Base64編碼。
          iOS的公鑰需要用.der格式,私鑰需要用.p12格式,這個(gè)可以用openssl的指令來(lái)轉(zhuǎn)換。(指令見(jiàn)末尾)
          加載的時(shí)候先用NSData加載密鑰,再用下面的:
          getPrivateKeyRefWithContentsOfFile: password:方法加載密鑰;
          getPublicKeyRefrenceFromeData:方法加載公鑰;

          //獲取私鑰
          - (SecKeyRef)getPrivateKeyRefWithContentsOfFile:(NSData *)p12Data password:(NSString*)password {
              if (!p12Data) {
                  return nil;
              }
              SecKeyRef privateKeyRef = NULL;
              NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
              [options setObject: password forKey:(__bridge id)kSecImportExportPassphrase];
              CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
              OSStatus securityError = SecPKCS12Import((__bridge CFDataRef) p12Data, (__bridge CFDictionaryRef)options, &items);
              if (securityError == noErr && CFArrayGetCount(items) > 0) {
                  CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
                  SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
                  securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
                  if (securityError != noErr) {
                      privateKeyRef = NULL;
                  }
              }
              CFRelease(items);
              
              return privateKeyRef;
          }

          - (SecKeyRef)getPublicKeyRefrenceFromeData:(NSData *)certData {
              SecKeyRef publicKeyRef = NULL;
              CFDataRef myCertData = (__bridge CFDataRef)certData;
              SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef)myCertData);
              if (cert == nil) {
                  NSLog(@"Can not read certificate ");
                  return nil;
              }
              SecPolicyRef policy = SecPolicyCreateBasicX509();
              SecCertificateRef certArray[1] = {cert};
              CFArrayRef myCerts = CFArrayCreate(NULL, (void *)(void *)certArray, 1, NULL);
              SecTrustRef trust;
              OSStatus status = SecTrustCreateWithCertificates(myCerts, policy, &trust);
              if (status != noErr) {
                  NSLog(@"SecTrustCreateWithCertificates fail. Error Code: %d", (int)status);
                  CFRelease(cert);
                  CFRelease(policy);
                  CFRelease(myCerts);
                  return nil;
              }
              SecTrustResultType trustResult;
              status = SecTrustEvaluate(trust, &trustResult);
              if (status != noErr) {
                  NSLog(@"SecTrustEvaluate fail. Error Code: %d", (int)status);
                  CFRelease(cert);
                  CFRelease(policy);
                  CFRelease(trust);
                  CFRelease(myCerts);
                  return nil;
              }
              publicKeyRef = SecTrustCopyPublicKey(trust);
              
              CFRelease(cert);
              CFRelease(policy);
              CFRelease(trust);
              CFRelease(myCerts);
              
              return publicKeyRef;
          }

          3、私鑰簽名和公鑰驗(yàn)證

          加載完公鑰和私鑰之后,用私鑰可以對(duì)原始數(shù)據(jù)進(jìn)行簽名,詳見(jiàn)PKCSSignBytesSHA256withRSA方法,返回的是簽名串;
          在用zip解壓出來(lái)的簽名串進(jìn)行驗(yàn)證的時(shí)候,需要用本地的公鑰、原始數(shù)據(jù)和簽名串進(jìn)行驗(yàn)簽,詳見(jiàn)PKCSVerifyBytesSHA256withRSA方法;
          注意的是,因?yàn)檫x擇的算法是kSecPaddingPKCS1SHA256,需要對(duì)原始數(shù)據(jù)進(jìn)行一次SHA256的hash。(kSecPaddingPKCS1SHA256只能用于SecKeyRawSign/SecKeyRawVerify

          BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
          {
              if (!plainData || !signature) { // 保護(hù)
                  return NO;
              }
              size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
              const void* signedHashBytes = [signature bytes];
              
              size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH;
              uint8_t* hashBytes = malloc(hashBytesSize);
              if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], hashBytes)) {
                  return NO;
              }
              
              OSStatus status = SecKeyRawVerify(publicKey,
                                                kSecPaddingPKCS1SHA256,
                                                hashBytes,
                                                hashBytesSize,
                                                signedHashBytes,
                                                signedHashBytesSize);
              
              return status == errSecSuccess;
          }

          NSData* PKCSSignBytesSHA256withRSA(NSData* plainData, SecKeyRef privateKey)
          {
              size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey);
              uint8_t* signedHashBytes = malloc(signedHashBytesSize);
              memset(signedHashBytes, 0x0, signedHashBytesSize);
              
              size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH;
              uint8_t* hashBytes = malloc(hashBytesSize);
              if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], hashBytes)) {
                  return nil;
              }
              
              SecKeyRawSign(privateKey,
                            kSecPaddingPKCS1SHA256,
                            hashBytes,
                            hashBytesSize,
                            signedHashBytes,
                            &signedHashBytesSize);
              
              NSData* signedHash = [NSData dataWithBytes:signedHashBytes
                                                  length:(NSUInteger)signedHashBytesSize];
              
              if (hashBytes)
                  free(hashBytes);
              if (signedHashBytes)
                  free(signedHashBytes);
              
              return signedHash;
          }

          4、簽名串的保存

          簽名串可以使用setxattrf寫(xiě)入文件的擴(kuò)展屬性,保證簽名串和資源的一一對(duì)應(yīng)。

          -(BOOL)setExtendValueWithPath:(NSString *)path key:(NSString *)key value:(NSData *)value {
              ssize_t writelen = setxattr([path fileSystemRepresentation],
                                          [key UTF8String],
                                          [value bytes],
                                          [value length],
                                          0,
                                          0);
              return writelen == 0;
          }

          比較奇怪的是,比較寫(xiě)入擴(kuò)展屬性之后的文件大小,并沒(méi)有發(fā)生較大變化。在特意查詢(xún)文檔之后,發(fā)現(xiàn)下面一句話(huà):Space consumed for extended attributes is counted towards the disk quotasof the file owner and file group 原來(lái)擴(kuò)展屬性并不是寫(xiě)入文件,而是由文件系統(tǒng)來(lái)保存。

          遇到的問(wèn)題

          1、驗(yàn)證失敗,SecKeyRawVerify返回-9809

          經(jīng)常遇到的問(wèn)題是,配置平臺(tái)的簽名在iOS客戶(hù)端驗(yàn)證不通過(guò),可以按照下面的流程檢測(cè):

          • 首先是確保兩端的公鑰和私鑰是一對(duì);
          • 配置平臺(tái)簽名完之后,用iOS客戶(hù)端的公鑰在本地驗(yàn)證;
          • 確認(rèn)兩邊使用的簽名算法設(shè)置參數(shù)一致;
          • iOS客戶(hù)端用配置平臺(tái)的私鑰進(jìn)行簽名,再用公鑰進(jìn)行驗(yàn)證;
          • 對(duì)比配置平臺(tái)的簽名串和iOS的簽名串;

          openssl的驗(yàn)證命令
          openssl dgst \-sign private_key.pem \-sha256 \-out sign source
          openssl dgst \-verify rsa_public_key.pem \-sha256 \-signature sign source
          如果驗(yàn)證通過(guò)會(huì)有文字提示:Verified OK

          2、生成證書(shū)失敗,openssl X509: 出現(xiàn) Expecting: TRUSTED CERTIFICATE的錯(cuò)誤

          參考這些公鑰和密鑰的openssl生成命令 openssl genrsa \-out private_key.pem 1024
          openssl req \-new \-key private_key.pem \-out rsaCertReq.csr
          openssl x509 \-req \-days 3650 \-in rsaCertReq.csr \-signkey private_key.pem \-out rsaCert.crt
          openssl x509 \-outform der \-in rsaCert.crt \-out public_key.der
          openssl pkcs12 \-export \-out private_key.p12 \-inkey private_key.pem \-in rsaCert.crt

          參考自GithubGist

          總結(jié)

          任何手段都無(wú)法完全防止惡意的攻擊,只能提高門(mén)檻。RSA不僅是可以保證資源不被篡改,也可以作為一種驗(yàn)證,檢查資源是否因?yàn)楦鞣N原因出現(xiàn)的文件缺失。

          附錄

          iOS使用Security.framework進(jìn)行RSA 加密解密簽名和驗(yàn)證簽名
          http://www.cnblogs.com/cocoajin/p/6183443.html

          http://blog.methodname.com/da-zao-yin-xing-ji-jia-mi/

          Signing and Verifying on iOS using RSA
          https://stackoverflow.com/questions/21724337/signing-and-verifying-on-ios-using-rsa

          xattr manpages
          http://manpages.ubuntu.com/manpages/xenial/man7/xattr.7.html

          demo地址
          https://github.com/loyinglin/LearnRSA


          轉(zhuǎn)自:掘金  落影

          https://juejin.cn/post/6969379271248707621

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來(lái),可以說(shuō)是程序員面試必備!所有資料都整理到網(wǎng)盤(pán)了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 75
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  丰满人妻一区二区三区色按摩 | 一级a一级a爱片免费视频 | 成人无码免费毛片 | 国产无码中文字幕在线 | 婷婷影音先锋 |