[iOS] SHA256withRSA加签验签

一般来说这种加密方式,主要在金融行业遇到的。主要流程先是对内容进行了base64加解密,然后使用SHA256加密方式进行加解密,再对其内容进行RSA加签验签。这里要注意下,RSA加签、验签与RSA加密解密,不要搞混淆了,如果能与后台联调的话,建议抽点时间一步一步和后台对数据,基本上能一遍过。

Base64与SHA256这一块的理论知识,可以百度搜到很多资料,本文就不提了。
下面直接贴上干货。

加签

需要用到的系统库

#import 
#import 
#import 

加签外面的代码不方便贴,但是我这儿还是说一下我需要处理数据的方式:根据后台要求,加签之前,表单内容须按字母顺序排序,并拼以" &***=**** "的形式接成字符串,然后再以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的转换,需要细心一点。

参考

https://www.jianshu.com/p/67aeca97f25d

你可能感兴趣的:([iOS] SHA256withRSA加签验签)