前言
前文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地址