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

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

工作流程:

  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格式密钥字符串。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenSSL是一个开源的加密库,它提供了RSA加密、解密、签名和验签的功能。 对于RSA加密和解密,我们可以使用OpenSSL提供的命令行工具或者API来实现。 使用命令行工具,我们可以通过以下命令进行RSA加密openssl rsautl -encrypt -in <input file> -out <output file> -inkey <public key file> -pubin 其中,<input file>是要加密的文件,<output file>是加密后的文件,<public key file>是存储公钥的文件,-pubin参数表示输入的是公钥。 使用命令行工具,我们可以通过以下命令进行RSA解密: openssl rsautl -decrypt -in <input file> -out <output file> -inkey <private key file> 其中,<input file>是要解密的文件,<output file>是解密后的文件,<private key file>是存储私钥的文件。 对于RSA签名和验签,我们可以使用以下命令进行签名: openssl rsautl -sign -in <input file> -out <output file> -inkey <private key file> 其中,<input file>是要签名的文件,<output file>是签名后的文件,<private key file>是存储私钥的文件。 使用以下命令进行验签openssl rsautl -verify -in <input file> -out <output file> -inkey <public key file> -pubin 其中,<input file>是要验签的文件,<output file>是验签后的文件,<public key file>是存储公钥的文件,-pubin参数表示输入的是公钥。 使用OpenSSL的API进行RSA加密、解密、签名和验签的操作也是类似的,我们可以通过调用相应的函数来实现。需要注意的是,API的使用需要在代码中显式引入OpenSSL的头文件和链接OpenSSL的库文件。 总之,OpenSSL提供了便捷的工具和API来实现RSA加密、解密、签名和验签的功能,无论是命令行工具还是API,都可以选择合适的方式进行操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值