一般来说这种加密方式,主要在金融行业遇到的。主要流程先是对内容进行了base64加解密,然后使用SHA256加密方式进行加解密,再对其内容进行RSA加签验签。这里要注意下,RSA加签、验签与RSA加密解密,不要搞混淆了,如果能与后台联调的话,建议抽点时间一步一步和后台对数据,基本上能一遍过。
Base64与SHA256这一块的理论知识,可以百度搜到很多资料,本文就不提了。
下面直接贴上干货。
加签
需要用到的系统库
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonHMAC.h>
#import <Security/Security.h>
加签外面的代码不方便贴,但是我这儿还是说一下我需要处理数据的方式:根据后台要求,加签之前,表单内容须按字母顺序排序,并拼以" &***=**** "的形式接成字符串,然后再以utf-8的编码形式处理成字符数组,而并非字符串形式。然后把加签结果放入原表单进行网络请求。
+ (NSData *)getHashBytes:(NSData *)plainText {
CC_SHA256_CTX ctx;
uint8_t * hashBytes = NULL;
NSData * hash = nil;
// Malloc a buffer to hold hash.
hashBytes = malloc( CC_SHA256_DIGEST_LENGTH * sizeof(uint8_t) );
memset((void *)hashBytes, 0x0, CC_SHA256_DIGEST_LENGTH);
// Initialize the context.
CC_SHA256_Init(&ctx);
// Perform the hash.
CC_SHA256_Update(&ctx, (void *)[plainText bytes], (CC_LONG)[plainText length]);
// Finalize the output.
CC_SHA256_Final(hashBytes, &ctx);
// Build up the SHA1 blob.
hash = [NSData dataWithBytes:(const void *)hashBytes length:CC_SHA256_DIGEST_LENGTH];
if (hashBytes) free(hashBytes);
return hash;
}
+(NSString *)signTheDataBySHA256WithRSA:(NSString *)plainText
{
uint8_t* signedBytes = NULL;
size_t signedBytesSize = 0;
OSStatus sanityCheck = noErr;
NSData* signedHash = nil;
// 按路径读取证书内容
NSString * path = [[NSBundle mainBundle]pathForResource:@"Your Certificate" ofType:@"pfx"];
NSData * data = [NSData dataWithContentsOfFile:path];
// 证书的密码
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
[options setObject:@"Certificate Passphrase" forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((CFDataRef) data, (CFDictionaryRef)options, &items);
if (securityError!=noErr) {
return nil ;
}
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);
SecKeyRef privateKeyRef=nil;
SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
signedBytesSize = SecKeyGetBlockSize(privateKeyRef);
signedBytes = malloc( signedBytesSize * sizeof(uint8_t) );
memset((void *)signedBytes, 0x0, signedBytesSize);
NSData *plainTextBytes = [plainText dataUsingEncoding:NSUTF8StringEncoding];
sanityCheck = SecKeyRawSign(privateKeyRef,
kSecPaddingPKCS1SHA256,
(const uint8_t *)[[self getHashBytes:plainTextBytes] bytes],
CC_SHA256_DIGEST_LENGTH,
(uint8_t *)signedBytes,
&signedBytesSize);
if (sanityCheck == noErr)
{
signedHash = [NSData dataWithBytes:(const void *)signedBytes length:(NSUInteger)signedBytesSize];
}
else
{
return nil;
}
if (signedBytes)
{
free(signedBytes);
}
NSString *signatureResult = [[NSString alloc]initWithData:[signedHash base64EncodedDataWithOptions:0] encoding:NSUTF8StringEncoding];
return signatureResult;
}
验签
后台返回会了一个data字符串和sign字符串,我们就要拿这个sign去验证data的正确性,并对data进行解码,就可以拿到里面的内容了。
// 验签
+(BOOL)VerifyBytesSHA256withRSA:(NSData *)plainData cer:(NSData *)signature{
if (!plainData || !signature) {
return NO;
}
SecKeyRef publicKey = [self getPublicKey];
size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
const uint8_t * 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;
}
+ (SecKeyRef)getPublicKey {
NSString * path = [[NSBundle mainBundle]pathForResource:@"Your Public Certificate" ofType:@"cer"];
NSData * data = [NSData dataWithContentsOfFile:path];
SecCertificateRef myCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)data);
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecTrustRef myTrust;
OSStatus status = SecTrustCreateWithCertificates(myCertificate,myPolicy,&myTrust);
CFErrorRef trustError;
if (status == noErr) {
status = SecTrustEvaluateWithError(myTrust, &trustError);
if(trustError){
NSLog(@"%@",trustError);
}
}
SecKeyRef securityKey = SecTrustCopyPublicKey(myTrust);
CFRelease(myCertificate);
CFRelease(myPolicy);
CFRelease(myTrust);
return securityKey;
}
总结
1、加签验签都是对于加解密之后的数据进行处理,加解密之前的数据,每个人遇到的都可能不一样,比如我接收到的data和sign,都是进过一次编码的,这属于额外处理,得跟后台沟通。我之前处理这个东西搞到凌晨两三点,就是因为跟后台沟通不畅。
2、加签验签其实就是SecKeyRawSign和SecKeyRawVerify这两个方法。建议可以把加签验签的代码读一读,基本能理解。
3、这个证书有各种后缀的,但是基本都能直接读取,但是注意后台给的证书可能采用不同的结构,这种情况读取证书的时候可能要指定证书架构,具体内容需查找其他资料。iOS默认支持的是x.509,一般情况下大部分的证书都是这种。
/*!
@typedef SecTrustRef
@abstract CFType used for performing X.509 certificate trust evaluations.
*/
typedef struct CF_BRIDGED_TYPE(id) __SecTrust *SecTrustRef;
4、加解密与加签验签中,经常有NSData的转换,需要细心一点。