iOS RSA非对称加解密

项目结束了,维护期也是整天打酱油,坐着也是浪费美好时光,不如搞点儿什么,充实一下。

踌躇二三,最终还是发了这篇随笔,一来,分享一下; 二来,希望各位大大指出不足。

一周前在搞iOS的非对称加解密,开始的时候感觉有些难度,但看了苹果的官方文档《Certificate, Key, and Trust Services Programming Guide》后,豁然开朗。

刚开始的时候,写的解密方法,有时可以解密,但有时就是不能解密,而且,只可以对小数据段进行加密,数据超出一定长度就不能加密了,搞得我很是纳闷儿。在网上搜,也只是搜出这个问题 利用RSA加解密为什么有时候可以解密 有事不能够解密时什么原因 ,没有真正的解答。 

无奈之下,还是回归到苹果的官方文档上。文档上讲了加解密,但开始的时候没有注意看,最终在其注释中找到答案。其中曲折,难以一言概之。

下面步入正轨。

非对称加密体系,讲究公钥与私钥。先来搞定这两个东西。这里,我使用的是OpenSSL生成的。我们最终需要的是一个PKCS#12格式的文件,以及一个自签名证书。

因为自己用的是Mac系统,所以就不用再麻烦着安装OpenSSL了。另外,你也可以使用jdk中自带的keytool工具生成公私钥。这里不讲解怎么安装这些工具。

首先,生成私钥与自签名证书,这里,我们将其合并,我们需要的就是这种效果。

openssl req -x509 -days 3650 -new -newkey rsa:2048 -keyout priv_key_self_signed_cert.pem -out priv_key_self_signed_cert.pem

这里,文件有效期为10年,密钥长度为2048位。

接着,使用上面生成的文件,导出PKCS#12格式的文件(后缀通常为pfx或p12)及自签名证书(后缀通常为der、cer、crt)。

openssl pkcs12 -export -out pkcs.p12 -in priv_key_self_signed_cert.pem
openssl x509 -in priv_key_self_signed_cert.pem -inform PEM -out cert.der -outform DER

这样,我们就完成了初步的任务。

PKCS#12文件是iOS支持的,其中包含了私钥及证书,通过它,我们是可以找出公钥的。公钥一般被包含在了证书中。所以说,我们不导出自签名证书也行,只保留一个PKCS#12文件就可以了。为了方便,我们这里导出了自签名证书。

 

下面是核心代码,注意看注释,有什么不足之处,还请各位指出。

我们需要公钥加密、私钥解密,因此我们把公私钥设置为成员变量。

#import <Foundation/Foundation.h>
#import <Security/Security.h>
#import <CommonCrypto/CommonDigest.h>

@interface Security : NSObject {
    SecKeyRef _privateKey;
    SecKeyRef _publicKey;
}

@property (nonatomic, readonly) SecKeyRef privateKey;
@property (nonatomic, readonly) SecKeyRef publicKey;

另外,我们需要,获取公私钥,设置加解密方法。

// 可以从PKCS#12文件中提取身份、信任、证书、公钥、私钥,这里,我们只需要保留私钥
- (OSStatus)extractEveryThingFromPKCS12File:(NSString *)pkcsPath passphrase:(NSString *)pkcsPassword;
// 从证书文件中提取公钥
- (OSStatus)extractPublicKeyFromCertificateFile:(NSString *)certPath;
// RSA公钥加密,支持长数据加密
- (NSData *)encryptWithPublicKey:(NSData *)plainData;
// RSA私钥解密,支持长数据解密
- (NSData *)decryptWithPrivateKey:(NSData *)cipherData;

获取私钥。

- (OSStatus)extractEveryThingFromPKCS12File:(NSString *)pkcsPath passphrase:(NSString *)pkcsPassword {
    SecIdentityRef identity;
    SecTrustRef trust;
    OSStatus status = -1;
    if (_privateKey == nil) {
        NSData *p12Data = [NSData dataWithContentsOfFile:pkcsPath];
        if (p12Data) {
            CFStringRef password = (CFStringRef)pkcsPassword;
            const void *keys[] = {
                kSecImportExportPassphrase
            };
            const void *values[] = {
                password
            };
            CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
            CFArrayRef items = CFArrayCreate(kCFAllocatorDefault, NULL, 0, NULL);
            status = SecPKCS12Import((CFDataRef)p12Data, options, &items);
            if (status == errSecSuccess) {
                CFDictionaryRef identity_trust_dic = CFArrayGetValueAtIndex(items, 0);
                identity = (SecIdentityRef)CFDictionaryGetValue(identity_trust_dic, kSecImportItemIdentity);
                trust = (SecTrustRef)CFDictionaryGetValue(identity_trust_dic, kSecImportItemTrust);
                // certs数组中包含了所有的证书
                CFArrayRef certs = (CFArrayRef)CFDictionaryGetValue(identity_trust_dic, kSecImportItemCertChain);
                if ([(NSArray *)certs count] && trust && identity) {
                    // 如果没有下面一句,自签名证书的评估信任结果永远是kSecTrustResultRecoverableTrustFailure
                    status = SecTrustSetAnchorCertificates(trust, certs);
                    if (status == errSecSuccess) {
                        SecTrustResultType trustResultType;
                        // 通常, 返回的trust result type应为kSecTrustResultUnspecified,如果是,就可以说明签名证书是可信的
                        status = SecTrustEvaluate(trust, &trustResultType);
                        if ((trustResultType == kSecTrustResultUnspecified || trustResultType == kSecTrustResultProceed) && status == errSecSuccess) {
                            // 证书可信,可以提取私钥与公钥,然后可以使用公私钥进行加解密操作
                            status = SecIdentityCopyPrivateKey(identity, &_privateKey);
                            if (status == errSecSuccess && _privateKey) {
                                // 成功提取私钥
                                NSLog(@"Get private key successfully~ %@", _privateKey);
                            }
                            /*
                             // 这里,不提取公钥,提取公钥的任务放在extractPublicKeyFromCertificateFile方法中
                            _publicKey = SecTrustCopyPublicKey(trust);
                            if (_publicKey) {
                                // 成功提取公钥
                            }
                             */
                        }
                    }
                }
            }
            if (options) {
                CFRelease(options);
            }
        }
    }
    return status;
}

提取公钥。

- (OSStatus)extractPublicKeyFromCertificateFile:(NSString *)certPath {
    OSStatus status = -1;
    if (_publicKey == nil) {
        SecTrustRef trust;
        SecTrustResultType trustResult;
        NSData *derData = [NSData dataWithContentsOfFile:certPath];
        if (derData) {
            SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)derData);
            SecPolicyRef policy = SecPolicyCreateBasicX509();
            status = SecTrustCreateWithCertificates(cert, policy, &trust);
            if (status == errSecSuccess && trust) {
                NSArray *certs = [NSArray arrayWithObject:(id)cert];
                status = SecTrustSetAnchorCertificates(trust, (CFArrayRef)certs);
                if (status == errSecSuccess) {
                    status = SecTrustEvaluate(trust, &trustResult);
                    // 自签名证书可信
                    if (status == errSecSuccess && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
                        _publicKey = SecTrustCopyPublicKey(trust);
                        if (_publicKey) {
                            NSLog(@"Get public key successfully~ %@", _publicKey);
                        }
                        if (cert) {
                            CFRelease(cert);
                        }
                        if (policy) {
                            CFRelease(policy);
                        }
                        if (trust) {
                            CFRelease(trust);
                        }
                    }
                }
            }
        }
    }
    return status;
}

公钥加密,因为每次的加密长度有限,所以用到了分段加密,苹果官方文档中提到了分段加密思想。

- (NSData *)encryptWithPublicKey:(NSData *)plainData {
    // 分配内存块,用于存放加密后的数据段
    size_t cipherBufferSize = SecKeyGetBlockSize(_publicKey);
    uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
    /*
     为什么这里要减12而不是减11?
     苹果官方文档给出的说明是,加密时,如果sec padding使用的是kSecPaddingPKCS1,
     那么支持的最长加密长度为SecKeyGetBlockSize()-11,
     这里说的最长加密长度,我估计是包含了字符串最后的空字符'\0',
     因为在实际应用中我们是不考虑'\0'的,所以,支持的真正最长加密长度应为SecKeyGetBlockSize()-12
    */
    double totalLength = [plainData length];
    size_t blockSize = cipherBufferSize - 12;// 使用cipherBufferSize - 11是错误的!
    size_t blockCount = (size_t)ceil(totalLength / blockSize);
    NSMutableData *encryptedData = [NSMutableData data];
    // 分段加密
    for (int i = 0; i < blockCount; i++) {
        NSUInteger loc = i * blockSize;
        // 数据段的实际大小。最后一段可能比blockSize小。
        int dataSegmentRealSize = MIN(blockSize, [plainData length] - loc);
        // 截取需要加密的数据段
        NSData *dataSegment = [plainData subdataWithRange:NSMakeRange(loc, dataSegmentRealSize)];
        OSStatus status = SecKeyEncrypt(_publicKey, kSecPaddingPKCS1, (const uint8_t *)[dataSegment bytes], dataSegmentRealSize, cipherBuffer, &cipherBufferSize);
        if (status == errSecSuccess) {
            NSData *encryptedDataSegment = [[NSData alloc] initWithBytes:(const void *)cipherBuffer length:cipherBufferSize];
            // 追加加密后的数据段
            [encryptedData appendData:encryptedDataSegment];
            [encryptedDataSegment release];
        } else {
            if (cipherBuffer) {
                free(cipherBuffer);
            }
            return nil;
        }
    }
    if (cipherBuffer) {
        free(cipherBuffer);
    }
    return encryptedData;
}

私钥解密,用到分段解密。

- (NSData *)decryptWithPrivateKey:(NSData *)cipherData {
    // 分配内存块,用于存放解密后的数据段
    size_t plainBufferSize = SecKeyGetBlockSize(_privateKey);
    NSLog(@"plainBufferSize = %zd", plainBufferSize);
    uint8_t *plainBuffer = malloc(plainBufferSize * sizeof(uint8_t));
    // 计算数据段最大长度及数据段的个数
    double totalLength = [cipherData length];
    size_t blockSize = plainBufferSize;
    size_t blockCount = (size_t)ceil(totalLength / blockSize);
    NSMutableData *decryptedData = [NSMutableData data];
    // 分段解密
    for (int i = 0; i < blockCount; i++) {
        NSUInteger loc = i * blockSize;
        // 数据段的实际大小。最后一段可能比blockSize小。
        int dataSegmentRealSize = MIN(blockSize, totalLength - loc);
        // 截取需要解密的数据段
        NSData *dataSegment = [cipherData subdataWithRange:NSMakeRange(loc, dataSegmentRealSize)];
        OSStatus status = SecKeyDecrypt(_privateKey, kSecPaddingPKCS1, (const uint8_t *)[dataSegment bytes], dataSegmentRealSize, plainBuffer, &plainBufferSize);
        if (status == errSecSuccess) {
            NSData *decryptedDataSegment = [[NSData alloc] initWithBytes:(const void *)plainBuffer length:plainBufferSize];
            [decryptedData appendData:decryptedDataSegment];
            [decryptedDataSegment release];
        } else {
            if (plainBuffer) {
                free(plainBuffer);
            }
            return nil;
        }
    }
    if (plainBuffer) {
        free(plainBuffer);
    }
    return decryptedData;
}

这些代码是支持长数据加密的,但因为非对称加密的效率不如对称加密那么高,所以通常用于密码加密,也就是短数据加密,这也就不需要什么分段加解密了。

 

如果我写得有误,请大大们指正。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值