美文网首页556a640e8643
iOS使用OpenSSL进行RSA加密、验签的心得

iOS使用OpenSSL进行RSA加密、验签的心得

作者: 木_穆 | 来源:发表于2019-01-06 01:44 被阅读0次

    最近做跨境支付类项目,安全要求等级比较高。数据加密验签流程比较复杂。先做一个复盘。

    工作流程:
    1. App创建RSA密钥对,将公钥(cPubKey)和IMEI码发送给服务器,私钥(cPriKey)保存本地。
    2. 服务器根据IMEI也创建RSA密钥对和一个32位随机码(RandKey)将私钥(serverPriKey)和RandKey根据IMEI码保存在服务端。返回给客户端服务器公钥(serverPubKey)和用cPubKey加密的RandKey, 客户端用cPriKey解密RandKey保存。
      完成1、2两步后:服务器和客户端双方都保存对方的公钥和自己私钥及RandKey。通过IMEI号做关联。
    3. 客户端发送请求时,将特定参数用cPriKey签名,将”真正请求参数“用RandKey进行AES256进行加密。
    4. 服务端接受请求时,用cPubKey对请求中签名进行验签。验签成功,用RandKey解密”真正请求参数“。
    5. 服务端返回请求时,将特定参数用serverPriKey签名,将”真正返回数据“用RandKey进行AES256进行加密。
    6. 客户端接受回执时,用serverPubKey对回执中签名进行验签。验签成功,用RandKey解密”真正返回数据“。

    注:iOS不能用真正的IMEI详情参考seventhboy的文章

    总结:
    1. 服务端和客户端分别生成密钥对,通过IMEI码进行绑定。保证每个用户和服务器之间的秘钥都是单独对应的。
    2. 双方都保存对方的公钥和自己私钥及RandKey。(私钥签名、公钥验签,公钥加密、私钥解密)
    3. 用自己私钥签名,将数据发送对方;收到对方签名过的数据后,用对方共钥验签。
    4. 用RandKey进行AES256进行加密核心数据。非对称加密效率低,加密内容短。所以要用aes这样的对称加密来加密data部数据。
    核心代码

    参考了XPorter的文章和其他几篇类似文章。

    #pragma mark ---生成密钥对
    + (BOOL)generateRSAKeyPairWithKeySize:(int)keySize publicKey:(RSA **)publicKey privateKey:(RSA **)privateKey{
        if (keySize == 512 || keySize == 1024 || keySize == 2048) {
            /* 产生RSA密钥 */
            RSA *rsa = RSA_new();
            BIGNUM* e = BN_new();
            /* 设置随机数长度 */
            BN_set_word(e, 65537);
            /* 生成RSA密钥对 RSA_generate_key_ex()新版本方法 */
            RSA_generate_key_ex(rsa, keySize, e, NULL);
            if (rsa) {
                *publicKey = RSAPublicKey_dup(rsa);
                *privateKey = RSAPrivateKey_dup(rsa);
                return YES;
            }
        }
        return NO;
    }
    

    此方法有一定的失败概率,用下面方法保证成功

    //生成本地密钥对
            while (1) {
                if ([LPXRSATool generateRSAKeyPairWithKeySize:2048 publicKey:&_publicKey privateKey:&_privateKey]) {
                    
                    self.publicKeyBase64 = [LPXRSATool base64EncodedStringKey:_publicKey isPubkey:YES];
                    self.privateKeyBase64 = [LPXRSATool base64EncodedStringKey:_privateKey isPubkey:NO];
                    NSLog(@"\n私钥:\n%@",_privateKeyBase64);
                    NSLog(@"\n公钥:\n%@",_publicKeyBase64);
                    if (_privateKeyBase64 && _publicKeyBase64) {
                        [ud setObject:_publicKeyBase64 forKey:cBase64_PubKey];
                        [ud setObject:_privateKeyBase64 forKey:cBase64_PriKey];
                        
                        break;
                    }
                }
            }
    
    //
    //  LPXRSATool.h
    //  OTTPAY
    //
    //  Created by Lipengxuan on 2019/1/5.
    //  Copyright © 2019 Lipengxuan. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    #import <openssl/rsa.h>
    
    typedef enum {
        Rsa_PKCS1_PADDING       =   RSA_PKCS1_PADDING,
        Rsa_SSLV23_PADDING      =   RSA_SSLV23_PADDING,
        Rsa_NO_PADDING          =   RSA_NO_PADDING,
        Rsa_PKCS1_OAEP_PADDING  =   RSA_PKCS1_OAEP_PADDING,
        Rsa_X931_PADDING        =   RSA_X931_PADDING,
        /* EVP_PKEY_ only */
        Rsa_PKCS1_PSS_PADDING   =   RSA_PKCS1_PSS_PADDING,
        Rsa_PKCS1_PADDING_SIZE  =   RSA_PKCS1_PADDING_SIZE,
    }RsaPaddingType;
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LPXRSATool : NSObject
        
    + (BOOL)generateRSAKeyPairWithKeySize:(int)keySize publicKey:(RSA **)publicKey privateKey:(RSA **)privateKey;
        
    + (RSA *)rsaFromBase64:(NSString *)base64Key isPubkey:(BOOL)isPubkey;
        
    #pragma mark ---密钥格式转换
    + (RSA *)rsaFromPEM:(NSString *)KeyPEM isPubkey:(BOOL)isPubkey;
    + (NSString *)base64EncodedStringKey:(RSA *)rsaKey isPubkey:(BOOL)isPubkey;
    +(NSString *)PEMKeyFromBase64:(NSString *)base64Key isPubkey:(BOOL)isPubkey;
    
    #pragma mark ---加解密
    + (NSData *)encryptWithPublicKey:(RSA *)publicKey plainData:(NSData *)plainData padding:(RsaPaddingType)padding;
    + (NSData *)decryptWithPrivateKey:(RSA *)privateKey cipherData:(NSData *)cipherData padding:(RsaPaddingType)padding;
    + (NSData *)encryptWithPrivateRSA:(RSA *)privateKey plainData:(NSData *)plainData padding:(RsaPaddingType)padding;
    + (NSData *)decryptWithPublicKey:(RSA *)publicKey cipherData:(NSData *)cipherData padding:(RsaPaddingType)padding;
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    //
    //  LPXRSATool.m
    //  OTTPAY
    //
    //  Created by Lipengxuan on 2019/1/5.
    //  Copyright © 2019 Lipengxuan. All rights reserved.
    //
    
    #import "LPXRSATool.h"
    #import <openssl/pem.h>
    
    @implementation LPXRSATool
        
    #pragma mark ---生成密钥对
    + (BOOL)generateRSAKeyPairWithKeySize:(int)keySize publicKey:(RSA **)publicKey privateKey:(RSA **)privateKey{
        if (keySize == 512 || keySize == 1024 || keySize == 2048) {
            /* 产生RSA密钥 */
            RSA *rsa = RSA_new();
            BIGNUM* e = BN_new();
            /* 设置随机数长度 */
            BN_set_word(e, 65537);
            /* 生成RSA密钥对 RSA_generate_key_ex()新版本方法 */
            RSA_generate_key_ex(rsa, keySize, e, NULL);
            if (rsa) {
                *publicKey = RSAPublicKey_dup(rsa);
                *privateKey = RSAPrivateKey_dup(rsa);
                return YES;
            }
        }
        return NO;
    }
    + (NSString *)base64EncodedStringKey:(RSA *)rsaKey isPubkey:(BOOL)isPubkey{
        if (!rsaKey) {
            return nil;
        }
        BIO *bio = BIO_new(BIO_s_mem());
        
        if (isPubkey) {
            PEM_write_bio_RSA_PUBKEY(bio, rsaKey);
        }else{
            //此方法生成的是pkcs1格式的,IOS中需要pkcs8格式的,因此通过PEM_write_bio_PrivateKey 方法生成
            // PEM_write_bio_RSAPrivateKey(bio, rsaKey, NULL, NULL, 0, NULL, NULL);
            EVP_PKEY* key = NULL;
            key = EVP_PKEY_new();
            EVP_PKEY_assign_RSA(key, rsaKey);
            PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL);
        }
        
        BUF_MEM *bptr;
        BIO_get_mem_ptr(bio, &bptr);
        BIO_set_close(bio, BIO_NOCLOSE); /* So BIO_free() leaves BUF_MEM alone */
        BIO_free(bio);
        NSString *res = [NSString stringWithUTF8String:bptr->data];
        //将PEM格式转换为base64格式
        return [self base64EncodedStringFromPEM:res];
    }
    
    + (NSString *)base64EncodedStringFromPEM:(NSString *)PEMFormat{
        return [[[PEMFormat componentsSeparatedByString:@"-----"] objectAtIndex:2] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    }
    +(NSString *)PEMKeyFromBase64:(NSString *)base64Key isPubkey:(BOOL)isPubkey{
        NSMutableString *result = [NSMutableString string];
        if (isPubkey) {
            [result appendString:@"-----BEGIN PUBLIC KEY-----\n"];
        }else{
            [result appendString:@"-----BEGIN RSA PRIVATE KEY-----\n"];
        }
        int count = 0;
        for (int i = 0; i < [base64Key length]; ++i) {
            unichar c = [base64Key characterAtIndex:i];
            if (c == '\n' || c == '\r') {
                continue;
            }
            [result appendFormat:@"%c", c];
            if (++count == 64) {
                [result appendString:@"\n"];
                count = 0;
            }
        }
        if (isPubkey) {
            [result appendString:@"\n-----END PUBLIC KEY-----"];
        }else{
            [result appendString:@"\n-----END RSA PRIVATE KEY-----"];
        }
        return result;
    }
    + (RSA *)rsaFromBase64:(NSString *)base64Key isPubkey:(BOOL)isPubkey{
        NSString *result = [self PEMKeyFromBase64:base64Key isPubkey:isPubkey];
        return [self rsaFromPEM:result isPubkey:isPubkey];
    }
    
    #pragma mark ---密钥格式转换
    + (RSA *)rsaFromPEM:(NSString *)KeyPEM isPubkey:(BOOL)isPubkey{
        const char *buffer = [KeyPEM UTF8String];
        BIO *keyBio = BIO_new_mem_buf(buffer, (int)strlen(buffer));
        RSA *rsa;
        if (isPubkey) {
            rsa = PEM_read_bio_RSA_PUBKEY(keyBio, NULL, NULL, NULL);
        }else{
            rsa = PEM_read_bio_RSAPrivateKey(keyBio, NULL, NULL, NULL);
        }
        BIO_free_all(keyBio);
        return rsa;
    }
    
    
        
    
    #pragma mark ---加解密
    + (NSData *)encryptWithPublicKey:(RSA *)publicKey plainData:(NSData *)plainData padding:(RsaPaddingType)padding{
        int paddingSize = 0;
        if (padding == Rsa_PKCS1_PADDING) {
            paddingSize = Rsa_PKCS1_PADDING_SIZE;
        }
        
        int publicRSALength = RSA_size(publicKey);
        double totalLength = [plainData length];
        int blockSize = publicRSALength - paddingSize;
        int blockCount = ceil(totalLength / blockSize);
        size_t publicEncryptSize = publicRSALength;
        NSMutableData *encryptDate = [NSMutableData data];
        for (int i = 0; i < blockCount; i++) {
            NSUInteger loc = i * blockSize;
            int dataSegmentRealSize = MIN(blockSize, totalLength - loc);
            NSData *dataSegment = [plainData subdataWithRange:NSMakeRange(loc, dataSegmentRealSize)];
            char *publicEncrypt = malloc(publicRSALength);
            memset(publicEncrypt, 0, publicRSALength);
            const unsigned char *str = [dataSegment bytes];
            int r = RSA_public_encrypt(dataSegmentRealSize,str,(unsigned char*)publicEncrypt,publicKey,padding);
            if (r < 0) {
                free(publicEncrypt);
                return nil;
            }
            NSData *encryptData = [[NSData alloc] initWithBytes:publicEncrypt length:publicEncryptSize];
            [encryptDate appendData:encryptData];
            
            free(publicEncrypt);
        }
        return encryptDate;
    }
        
    + (NSData *)decryptWithPrivateKey:(RSA *)privateKey cipherData:(NSData *)cipherData padding:(RsaPaddingType)padding{
        
        if (!privateKey) {
            return nil;
        }
        if (!cipherData) {
            return nil;
        }
        int privateRSALenght = RSA_size(privateKey);
        double totalLength = [cipherData length];
        int blockSize = privateRSALenght;
        int blockCount = ceil(totalLength / blockSize);
        NSMutableData *decrypeData = [NSMutableData data];
        for (int i = 0; i < blockCount; i++) {
            NSUInteger loc = i * blockSize;
            long dataSegmentRealSize = MIN(blockSize, totalLength - loc);
            NSData *dataSegment = [cipherData subdataWithRange:NSMakeRange(loc, dataSegmentRealSize)];
            const unsigned char *str = [dataSegment bytes];
            unsigned char *privateDecrypt = malloc(privateRSALenght);
            memset(privateDecrypt, 0, privateRSALenght);
            int ret = RSA_private_decrypt(privateRSALenght,str,privateDecrypt,privateKey,padding);
            if(ret >=0){
                NSData *data = [[NSData alloc] initWithBytes:privateDecrypt length:ret];
                [decrypeData appendData:data];
            }
            free(privateDecrypt);
        }
        
        return decrypeData;
    }
        
    + (NSData *)encryptWithPrivateRSA:(RSA *)privateKey plainData:(NSData *)plainData padding:(RsaPaddingType)padding{
        
        if (!privateKey) {
            return nil;
        }
        if (!plainData) {
            return nil;
        }
        int paddingSize = 0;
        if (padding == Rsa_PKCS1_PADDING) {
            paddingSize = Rsa_PKCS1_PADDING_SIZE;
        }
        
        int privateRSALength = RSA_size(privateKey);
        double totalLength = [plainData length];
        int blockSize = privateRSALength - paddingSize;
        int blockCount = ceil(totalLength / blockSize);
        size_t privateEncryptSize = privateRSALength;
        NSMutableData *encryptDate = [NSMutableData data];
        for (int i = 0; i < blockCount; i++) {
            NSUInteger loc = i * blockSize;
            int dataSegmentRealSize = MIN(blockSize, totalLength - loc);
            NSData *dataSegment = [plainData subdataWithRange:NSMakeRange(loc, dataSegmentRealSize)];
            char *privateEncrypt = malloc(privateRSALength);
            memset(privateEncrypt, 0, privateRSALength);
            const unsigned char *str = [dataSegment bytes];
            int r = RSA_private_encrypt(dataSegmentRealSize,str,(unsigned char*)privateEncrypt,privateKey,padding);
            if (r < 0) {
                free(privateEncrypt);
                return nil;
            }
            
            NSData *encryptData = [[NSData alloc] initWithBytes:privateEncrypt length:privateEncryptSize];
            [encryptDate appendData:encryptData];
            
            free(privateEncrypt);
        }
        return encryptDate;
        
    }
        
    + (NSData *)decryptWithPublicKey:(RSA *)publicKey cipherData:(NSData *)cipherData padding:(RsaPaddingType)padding{
        if (!publicKey) {
            return nil;
        }
        if (!cipherData) {
            return nil;
        }
        
        int publicRSALenght = RSA_size(publicKey);
        double totalLength = [cipherData length];
        int blockSize = publicRSALenght;
        int blockCount = ceil(totalLength / blockSize);
        NSMutableData *decrypeData = [NSMutableData data];
        for (int i = 0; i < blockCount; i++) {
            NSUInteger loc = i * blockSize;
            long dataSegmentRealSize = MIN(blockSize, totalLength - loc);
            NSData *dataSegment = [cipherData subdataWithRange:NSMakeRange(loc, dataSegmentRealSize)];
            const unsigned char *str = [dataSegment bytes];
            unsigned char *publicDecrypt = malloc(publicRSALenght);
            memset(publicDecrypt, 0, publicRSALenght);
            int ret = RSA_public_decrypt(publicRSALenght,str,publicDecrypt,publicKey,padding);
            if(ret < 0){
                free(publicDecrypt);
                return nil ;
            }
            NSData *data = [[NSData alloc] initWithBytes:publicDecrypt length:ret];
            if (padding == Rsa_NO_PADDING) {
                Byte flag[] = {0x00};
                NSData *startData = [data subdataWithRange:NSMakeRange(0, 1)];
                if ([[startData description] isEqualToString:@"<00>"]) {
                    NSRange startRange = [data rangeOfData:[NSData dataWithBytes:flag length:1] options:NSDataSearchBackwards range:NSMakeRange(0, data.length)];
                    NSUInteger s = startRange.location + startRange.length;
                    if (startRange.location != NSNotFound && s < data.length) {
                        data = [data subdataWithRange:NSMakeRange(s, data.length - s)];
                    }
                }
            }
            [decrypeData appendData:data];
            
            free(publicDecrypt);
        }
        return decrypeData;
    }
    
    @end
    

    验签参考HustBroventure的文章

    -(HBRSAHandler *)handler{
        if (!_handler ) {
            if ( _severPubKey) {
                self.handler = [HBRSAHandler new];
                //导入客户端私钥用于签名
                // importKeyWithType: andkeyString: 要求导入的keystr是PEM格式。否则不能正确生成(RSA*)证书
                NSString *privatePEMKey = [LPXRSATool PEMKeyFromBase64:_privateKeyBase64 isPubkey:NO];
                [_handler importKeyWithType:KeyTypePrivate andkeyString:privatePEMKey];
                
                //导入服务端公钥用于验签
                // importKeyWithType: andkeyString: 要求导入的keystr是PEM格式。否则不能正确生成(RSA*)证书
                NSString *severPubPEMKey = [LPXRSATool PEMKeyFromBase64:_severPubKey isPubkey:YES];
                
                [_handler importKeyWithType:KeyTypePublic andkeyString:severPubPEMKey];
                return _handler;
            }else{
                AppLog(@"签名handler 获取失败");
                return nil;
            }
        }
        return _handler;
        
    }
    

    客户端发送请求时,将特定参数用cPriKey签名,将”真正请求参数“用RandKey进行AES256进行加密。

            //data字段内容进行AES加密,再二进制转十六进制(bin2hex)
            NSString *aesData = [MyCommonCrypto AES256EncryptWithContent:[NSString jsonStrFromDictionary:params] andKey:dataManger.randKey];
            [p setObject:[NSString hexStringFormBase64String:aesData] forKey:@"data"];
            
            //请求参数签名
            NSString *sortStr_p = [NSString sortDictionary:p];
            NSString* signStr_p = [dataManger.handler signString:sortStr_p];
            [p setObject:[NSString hexStringFormBase64String:signStr_p] forKey:@"sign"];
            
    
    NSString *sortStr_r = [NSString sortDictionary:reDic];
    NSString* signStr_r = result[@"sign"];
    //服务端的signStr是签名后的data转16进制字符串,反向signStr转data
    NSData *signData_r = [NSString convertHexStrToData:signStr_r];
    //verifyString:withSign: 方法的sing参数是data的base64格式
    NSString *signBase64str = [signData_r base64EncodedStringWithOptions:0];
                            
    if ([dataManger.handler verifyString:sortStr_r withSign:signBase64str]) {
         AppLog(@"验签成功")
         // 将16进制字符串转为NSData
         NSData *resData = [NSString convertHexStrToData:responseData];
         NSData *deData = [MyCommonCrypto AES256DecryptWithContent:resData andKey:dataManger.randKey];
         NSString *base64String = [[NSString alloc]initWithData:deData encoding:NSUTF8StringEncoding];
         NSDictionary *resDic = [NSString  dictinaryFromJsonStr:base64String];
         finshed(YES,resDic);
    }else{
         AppLog(@"验签失败")
         finshed(NO,nil);
    }
    
    总结:

    过程中遇到不少的坑,特别是和服务端互相验签,由于RSA签名后的数据OC是NSData形式、Java是byte[]形式。也就是数据流。HTTP传输过程中是不能用直接用这种形式的。一般第三方封装的方法会把数据转换为字符串形式。我们服务端用的是16进制字符串的形式,oc这边是base64形式。
    故:请求时把签名好的base64String转换成hexString。回执验签时把待验签字符串从hexString转换成base64String。

    更新:

    开发过程中可能会遇到
    PEM_read_bio_RSAPrivateKey() return NULL
    PEM_read_bio_RSA_PUBKEY() return NULL
    需要注意,这里bio中data需要是PEM格式密钥字符串。

    相关文章

      网友评论

        本文标题:iOS使用OpenSSL进行RSA加密、验签的心得

        本文链接:https://www.haomeiwen.com/subject/qhqjrqtx.html