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

前言

前文iOS如何保證下載資源的可靠性介紹了基于RSA的下載資源驗證方案,這次詳細介紹開發過程中的問題。

iOS接入步驟

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

1、zip解壓

iOS平臺上可以使用MiniZipArchive進行解壓。

- (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格式表示二進制編碼,.pem格式表示Base64編碼。
iOS的公鑰需要用.der格式,私鑰需要用.p12格式,這個可以用openssl的指令來轉換。(指令見末尾)
加載的時候先用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、私鑰簽名和公鑰驗證

加載完公鑰和私鑰之后,用私鑰可以對原始數據進行簽名,詳見PKCSSignBytesSHA256withRSA方法,返回的是簽名串;
在用zip解壓出來的簽名串進行驗證的時候,需要用本地的公鑰、原始數據和簽名串進行驗簽,詳見PKCSVerifyBytesSHA256withRSA方法;
注意的是,因為選擇的算法是kSecPaddingPKCS1SHA256,需要對原始數據進行一次SHA256的hash。(kSecPaddingPKCS1SHA256只能用于SecKeyRawSign/SecKeyRawVerify


BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
    if (!plainData || !signature) { // 保護
        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寫入文件的擴展屬性,保證簽名串和資源的一一對應。

-(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;
}

比較奇怪的是,比較寫入擴展屬性之后的文件大小,并沒有發生較大變化。在特意查詢文檔之后,發現下面一句話:

Space consumed for extended attributes is counted towards the disk quotasof the file owner and file group
原來擴展屬性并不是寫入文件,而是由文件系統來保存。

遇到的問題

1、驗證失敗,SecKeyRawVerify返回-9809

經常遇到的問題是,配置平臺的簽名在iOS客戶端驗證不通過,可以按照下面的流程檢測:

  • 首先是確保兩端的公鑰和私鑰是一對;
  • 配置平臺簽名完之后,用iOS客戶端的公鑰在本地驗證;
  • 確認兩邊使用的簽名算法設置參數一致
  • iOS客戶端用配置平臺的私鑰進行簽名,再用公鑰進行驗證;
  • 對比配置平臺的簽名串和iOS的簽名串;

openssl的驗證命令
openssl dgst -sign private_key.pem -sha256 -out sign source
openssl dgst -verify rsa_public_key.pem -sha256 -signature sign source
如果驗證通過會有文字提示:Verified OK

2、生成證書失敗,openssl X509: 出現 Expecting: TRUSTED CERTIFICATE的錯誤

參考這些公鑰和密鑰的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地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容