iOS使用OpenSSL進(jìn)行RSA加密、驗(yàn)簽的心得

最近做跨境支付類項(xiàng)目,安全要求等級(jí)比較高。數(shù)據(jù)加密驗(yàn)簽流程比較復(fù)雜。先做一個(gè)復(fù)盤。

工作流程:
  1. App創(chuàng)建RSA密鑰對(duì),將公鑰(cPubKey)和IMEI碼發(fā)送給服務(wù)器,私鑰(cPriKey)保存本地。
  2. 服務(wù)器根據(jù)IMEI也創(chuàng)建RSA密鑰對(duì)和一個(gè)32位隨機(jī)碼(RandKey)將私鑰(serverPriKey)和RandKey根據(jù)IMEI碼保存在服務(wù)端。返回給客戶端服務(wù)器公鑰(serverPubKey)和用cPubKey加密的RandKey, 客戶端用cPriKey解密RandKey保存。
    完成1、2兩步后:服務(wù)器和客戶端雙方都保存對(duì)方的公鑰和自己私鑰及RandKey。通過IMEI號(hào)做關(guān)聯(lián)。
  3. 客戶端發(fā)送請(qǐng)求時(shí),將特定參數(shù)用cPriKey簽名,將”真正請(qǐng)求參數(shù)“用RandKey進(jìn)行AES256進(jìn)行加密。
  4. 服務(wù)端接受請(qǐng)求時(shí),用cPubKey對(duì)請(qǐng)求中簽名進(jìn)行驗(yàn)簽。驗(yàn)簽成功,用RandKey解密”真正請(qǐng)求參數(shù)“。
  5. 服務(wù)端返回請(qǐng)求時(shí),將特定參數(shù)用serverPriKey簽名,將”真正返回?cái)?shù)據(jù)“用RandKey進(jìn)行AES256進(jìn)行加密。
  6. 客戶端接受回執(zhí)時(shí),用serverPubKey對(duì)回執(zhí)中簽名進(jìn)行驗(yàn)簽。驗(yàn)簽成功,用RandKey解密”真正返回?cái)?shù)據(jù)“。

注:iOS不能用真正的IMEI詳情參考seventhboy的文章

總結(jié):
  1. 服務(wù)端和客戶端分別生成密鑰對(duì),通過IMEI碼進(jìn)行綁定。保證每個(gè)用戶和服務(wù)器之間的秘鑰都是單獨(dú)對(duì)應(yīng)的。
  2. 雙方都保存對(duì)方的公鑰和自己私鑰及RandKey。(私鑰簽名、公鑰驗(yàn)簽,公鑰加密、私鑰解密)
  3. 用自己私鑰簽名,將數(shù)據(jù)發(fā)送對(duì)方;收到對(duì)方簽名過的數(shù)據(jù)后,用對(duì)方共鑰驗(yàn)簽。
  4. 用RandKey進(jìn)行AES256進(jìn)行加密核心數(shù)據(jù)。非對(duì)稱加密效率低,加密內(nèi)容短。所以要用aes這樣的對(duì)稱加密來加密data部數(shù)據(jù)。
核心代碼

參考了XPorter的文章和其他幾篇類似文章。

#pragma mark ---生成密鑰對(duì)
+ (BOOL)generateRSAKeyPairWithKeySize:(int)keySize publicKey:(RSA **)publicKey privateKey:(RSA **)privateKey{
    if (keySize == 512 || keySize == 1024 || keySize == 2048) {
        /* 產(chǎn)生RSA密鑰 */
        RSA *rsa = RSA_new();
        BIGNUM* e = BN_new();
        /* 設(shè)置隨機(jī)數(shù)長(zhǎng)度 */
        BN_set_word(e, 65537);
        /* 生成RSA密鑰對(duì) 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;
}

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

//生成本地密鑰對(duì)
        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 ---密鑰格式轉(zhuǎn)換
+ (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 ---生成密鑰對(duì)
+ (BOOL)generateRSAKeyPairWithKeySize:(int)keySize publicKey:(RSA **)publicKey privateKey:(RSA **)privateKey{
    if (keySize == 512 || keySize == 1024 || keySize == 2048) {
        /* 產(chǎn)生RSA密鑰 */
        RSA *rsa = RSA_new();
        BIGNUM* e = BN_new();
        /* 設(shè)置隨機(jī)數(shù)長(zhǎng)度 */
        BN_set_word(e, 65537);
        /* 生成RSA密鑰對(duì) 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格式轉(zhuǎn)換為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 ---密鑰格式轉(zhuǎn)換
+ (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

驗(yàn)簽參考HustBroventure的文章

-(HBRSAHandler *)handler{
    if (!_handler ) {
        if ( _severPubKey) {
            self.handler = [HBRSAHandler new];
            //導(dǎo)入客戶端私鑰用于簽名
            // importKeyWithType: andkeyString: 要求導(dǎo)入的keystr是PEM格式。否則不能正確生成(RSA*)證書
            NSString *privatePEMKey = [LPXRSATool PEMKeyFromBase64:_privateKeyBase64 isPubkey:NO];
            [_handler importKeyWithType:KeyTypePrivate andkeyString:privatePEMKey];
            
            //導(dǎo)入服務(wù)端公鑰用于驗(yàn)簽
            // importKeyWithType: andkeyString: 要求導(dǎo)入的keystr是PEM格式。否則不能正確生成(RSA*)證書
            NSString *severPubPEMKey = [LPXRSATool PEMKeyFromBase64:_severPubKey isPubkey:YES];
            
            [_handler importKeyWithType:KeyTypePublic andkeyString:severPubPEMKey];
            return _handler;
        }else{
            AppLog(@"簽名handler 獲取失敗");
            return nil;
        }
    }
    return _handler;
    
}

客戶端發(fā)送請(qǐng)求時(shí),將特定參數(shù)用cPriKey簽名,將”真正請(qǐng)求參數(shù)“用RandKey進(jìn)行AES256進(jìn)行加密。

        //data字段內(nèi)容進(jìn)行AES加密,再二進(jìn)制轉(zhuǎn)十六進(jìn)制(bin2hex)
        NSString *aesData = [MyCommonCrypto AES256EncryptWithContent:[NSString jsonStrFromDictionary:params] andKey:dataManger.randKey];
        [p setObject:[NSString hexStringFormBase64String:aesData] forKey:@"data"];
        
        //請(qǐng)求參數(shù)簽名
        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"];
//服務(wù)端的signStr是簽名后的data轉(zhuǎn)16進(jìn)制字符串,反向signStr轉(zhuǎn)data
NSData *signData_r = [NSString convertHexStrToData:signStr_r];
//verifyString:withSign: 方法的sing參數(shù)是data的base64格式
NSString *signBase64str = [signData_r base64EncodedStringWithOptions:0];
                        
if ([dataManger.handler verifyString:sortStr_r withSign:signBase64str]) {
     AppLog(@"驗(yàn)簽成功")
     // 將16進(jìn)制字符串轉(zhuǎn)為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(@"驗(yàn)簽失敗")
     finshed(NO,nil);
}
總結(jié):

過程中遇到不少的坑,特別是和服務(wù)端互相驗(yàn)簽,由于RSA簽名后的數(shù)據(jù)OC是NSData形式、Java是byte[]形式。也就是數(shù)據(jù)流。HTTP傳輸過程中是不能用直接用這種形式的。一般第三方封裝的方法會(huì)把數(shù)據(jù)轉(zhuǎn)換為字符串形式。我們服務(wù)端用的是16進(jìn)制字符串的形式,oc這邊是base64形式。
故:請(qǐng)求時(shí)把簽名好的base64String轉(zhuǎn)換成hexString。回執(zhí)驗(yàn)簽時(shí)把待驗(yàn)簽字符串從hexString轉(zhuǎn)換成base64String。

更新:

開發(fā)過程中可能會(huì)遇到
PEM_read_bio_RSAPrivateKey() return NULL
PEM_read_bio_RSA_PUBKEY() return NULL
需要注意,這里bio中data需要是PEM格式密鑰字符串。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容