iOS保證下載資源的可靠性(二)

前言

前文iOS如何保證下載資源的可靠性介紹了基于RSA的下載資源驗(yàn)證方案鉴未,這次詳細(xì)介紹開(kāi)發(fā)過(guò)程中的問(wèn)題。

iOS接入步驟

  • 后臺(tái)上傳資源文件,配置平臺(tái)對(duì)文件進(jìn)行hash并用私鑰進(jìn)行簽名得到簽名串signature胯究;
  • 把文件和signature打包成zip包,下發(fā)到客戶端岸蜗;
  • 客戶端解壓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ā)生較大變化践惑。在特意查詢文檔之后腹泌,發(fā)現(xiàn)下面一句話:

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客戶端驗(yàn)證不通過(guò)专甩,可以按照下面的流程檢測(cè):

  • 首先是確保兩端的公鑰和私鑰是一對(duì)钟鸵;
  • 配置平臺(tái)簽名完之后,用iOS客戶端的公鑰在本地驗(yàn)證涤躲;
  • 確認(rèn)兩邊使用的簽名算法設(shè)置參數(shù)一致棺耍;
  • iOS客戶端用配置平臺(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

附錄

Signing and Verifying on iOS using RSA
xattr manpages
demo地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末左敌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子俐镐,更是在濱河造成了極大的恐慌,老刑警劉巖哺哼,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佩抹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡取董,警方通過(guò)查閱死者的電腦和手機(jī)棍苹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)茵汰,“玉大人枢里,你說(shuō)我怎么就攤上這事□逦纾” “怎么了栏豺?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)豆胸。 經(jīng)常有香客問(wèn)我奥洼,道長(zhǎng),這世上最難降的妖魔是什么晚胡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任灵奖,我火速辦了婚禮,結(jié)果婚禮上估盘,老公的妹妹穿的比我還像新娘瓷患。我一直安慰自己,他們只是感情好遣妥,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布擅编。 她就那樣靜靜地躺著,像睡著了一般燥透。 火紅的嫁衣襯著肌膚如雪沙咏。 梳的紋絲不亂的頭發(fā)上辨图,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音肢藐,去河邊找鬼故河。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吆豹,可吹牛的內(nèi)容都是我干的鱼的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼痘煤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼凑阶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起衷快,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宙橱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蘸拔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體师郑,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年调窍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宝冕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邓萨,死狀恐怖地梨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缔恳,我是刑警寧澤宝剖,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站褐耳,受9級(jí)特大地震影響诈闺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铃芦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一雅镊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刃滓,春花似錦仁烹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春征唬,著一層夾襖步出監(jiān)牢的瞬間捌显,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工总寒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扶歪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓摄闸,卻偏偏與公主長(zhǎng)得像善镰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子年枕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容