iOS RSA加密与解密

作者: fuaiyi | 来源:发表于2017-04-24 10:50 被阅读477次

    RSA加密现在有很多项目中都会用到,网上资料很多,方法也不止一种,我这里整理了其中一种,希望对大家有所帮助

    上图:

    Untitled.gif

    说明

    1.本文使用的RSA相关操作是使用的三方库openSSL
    2.本文RSA使用的是1024位秘钥加密(能满足一般的需求,除非需要非常高的安全性)

    相关知识

    1.生成密文的长度等于密钥长度(密钥长度越大,生成密文的长度就越大,加密的速度就越慢,而密文也就越难被破解掉)。
    2.不管明文长度是多少,RSA生成的密文长度总是固定的。
    3.明文长度不能超过密钥长度,否则就会出问题(使用分段加密)。
    4.RSA加密每次结果是不一样的(加密时对加密内容使用了长度为11位的随机填充)。
    5.RSA本身不支持中文(一般使用URL编码来解决中文问题)。
    6.公钥加密私钥解密,私钥加密公钥解密

    前期准备:

    1.公钥/私钥(一般应该只会用到一个,另外一个是后台使用),这个一般是后台给的,自己生成也很简单,可以去网站上生成也可以使用终端生成
    2.openSSL三方库(建议使用coocpods导入)

    RSA加密解密

    1.私钥/公钥格式化(需要把密钥/私钥按进行特定的格式化才能进行 加解密)

    //0.2格式化key
    + (NSString *)formattKey:(NSString *)key
    {
        if (key == nil) return @"";
        
        NSInteger count = key.length / 64;
        NSMutableString *formattKey = key.mutableCopy;
        for (int i = 0; i < count; i ++)
        {
            [formattKey insertString:@"\n" atIndex:64 + (64 + 1) * i];
        }
        return formattKey == nil ? @"" : formattKey;
    }
    
    //0.3写入key文件
    + (BOOL)writeKey:(NSString *)key type:(KeyType)type;
    {
    
        NSString*keyPath = [self RSAKeyFillePath:type];
        
        if ([[NSFileManager defaultManager] fileExistsAtPath:keyPath])
        {
            return YES;
        }
        
        NSString *formattKey = [self formattKey:key];
        
        NSString *keyStr;
        switch (type)
        {
            case eKeyTypePublic:
            {
                keyStr = [NSString stringWithFormat:@"-----BEGIN PUBLIC KEY-----\n%@\n-----END PUBLIC KEY-----",formattKey];
                break;
            }
            case eKeyTypePrivate:
            {
                keyStr = [NSString stringWithFormat:@"-----BEGIN RSA PRIVATE KEY-----\n%@\n-----END RSA PRIVATE KEY-----",formattKey];
                break;
            }
            default:
            {
                return NO;
                
            }
        }
        NSLog(@"格式话处理过的key:\n%@\n",keyStr);
        NSError *error = nil;
        return [keyStr writeToFile:keyPath atomically:YES encoding:NSASCIIStringEncoding error:&error];
    }
    

    格式化后的结果:

    -----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLyQGhLLE9+xtR5ZzPv1vMEOIv
    YJxEcMbapskPqqlYewyXD5esHa/BZwQgMTPDMSFw4TuXOUWaM/Khelon8p4iKF4O
    4m1Uc5AmEY+vM4gITg1KkmyuN2f43insnBWkMiV8dhfeIB/pCCAL1RExrBfehIVd
    kwe3bemsNnowLIyOHwIDAQAB
    -----END PUBLIC KEY-----
    

    这里说明一下,密钥/公钥文件可以存放到沙盒中,也可以打包到项目中,都可以,只要使用的时候能拿到文件就行
    2.加密/解密,把密钥/私钥处理好之后就可以开始加密/解密了(很简单,三步)

    //1.导入key为RSA结构体对象
    - (BOOL)importRSAKey:(KeyType)type
    {
        
    //    这里是从项目中读取公钥和私钥,你也可以把公钥和私钥存入沙河中,从沙河中读取
        
        switch (type)
        {
            case eKeyTypePublic:
            {
                FILE *file = fopen([[[NSBundle mainBundle] pathForResource:@"public_key.pem" ofType:nil] UTF8String], "rb");
                if (file == NULL) return NO;
                _publicRSA = PEM_read_RSA_PUBKEY(file, NULL, NULL, NULL);
                assert(_publicRSA != nil);
                fclose(file);
                return (_publicRSA != NULL) ? YES : NO;
            }
            case eKeyTypePrivate:
            {
                FILE *file = fopen([[[NSBundle mainBundle] pathForResource:@"private_key.pem" ofType:nil] UTF8String], "rb");
                if (file == NULL) return NO;
                _privateRSA = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);
                assert(_privateRSA != NULL);
                fclose(file);
                return (_privateRSA != NULL) ? YES : NO;
            }
            default:
            {
                return NO;
            }
        }
    }
    
    
    //2.1加密处理
    - (NSData *)encryptToData:(NSString*)content type:(KeyType)type
    {
        
        if([self RSAWithType:type] == NULL)
        {
            if (![self importRSAKey:type]) return nil;
        }
        
        RSA *rsa = [self RSAWithType:type];
        
        int status = 0;
        int length  = (int)content.length;
        unsigned char input[length + 1];
        bzero(input, length + 1);
        
        for (int i = 0; i < length; i++)
        {
            input[i] = [content characterAtIndex:i];
        }
        
        NSInteger  flen = [self getBlockSize:eRSAPaddingTypePKCS1 type:type];
        char *encryptData = (char*)malloc(flen);
        bzero(encryptData, flen);
        
        switch (type)
        {
            case eKeyTypePublic:
                status = RSA_public_encrypt(length, (unsigned char*)input, (unsigned char*)encryptData, rsa, eRSAPaddingTypePKCS1);
                break;
                
            default:
                status = RSA_private_encrypt(length, (unsigned char*)input, (unsigned char*)encryptData, rsa, eRSAPaddingTypePKCS1);
                break;
        }
        
        if (status)
        {
            NSData *returnData = [NSData dataWithBytes:encryptData length:status];
            free(encryptData);
            encryptData = NULL;
            return returnData;
        }
        
        free(encryptData);
        encryptData = NULL;
        
        return nil;
    }
    
    //2.2解密处理
    - (NSString *)decryptToData:(NSString*)content type:(KeyType)type
    {
        
        if([self RSAWithType:type] == NULL)
        {
            if (![self importRSAKey:type]) return nil;
        }
        
        RSA *rsa = [self RSAWithType:type];
        int status;
        NSData *data = [content base64Decode];
        int length = (int)data.length;
        
        NSInteger flen = [self getBlockSize:eRSAPaddingTypePKCS1 type:type];
        char *decryptData = (char*)malloc(flen);
        bzero(decryptData, flen);
        
        switch (type)
        {
            case eKeyTypePublic:
                status = RSA_public_decrypt(length, (unsigned char*)[data bytes], (unsigned char*)decryptData, rsa, eRSAPaddingTypePKCS1);
                break;
                
            default:
                status = RSA_private_decrypt(length, (unsigned char*)[data bytes], (unsigned char*)decryptData, rsa, eRSAPaddingTypePKCS1);
                break;
        }
        
        if (status)
        {
            NSMutableString *decryptString = [[NSMutableString alloc] initWithBytes:decryptData length:strlen(decryptData) encoding:NSASCIIStringEncoding];
            free(decryptData);
            decryptData = NULL;
            return decryptString;
        }
        
        free(decryptData);
        decryptData = NULL;
        
        return nil;
    }
    
    
    //3.1实现中文兼容和分段加密
    - (NSString *)encrypt:(NSString *)content type:(KeyType)type
    {
        content = content.URLEncode;
        NSMutableString *encryptContent = @"".mutableCopy;
        for (NSInteger i = 0; i < ceilf(content.length / 117.0); i ++)
        {
            NSString *subStr = [content substringWithRange:NSMakeRange(i * 117, MIN(117, content.length - i * 117))];
            NSString *encryptSubStr = [[self encryptToData:subStr type:type] base64Encode];
            [encryptContent appendString:encryptSubStr];
        }
        return encryptContent;
    }
    
    //3.2实现中文兼容和分段解密
    - (NSString *)decrypt:(NSString *)content type:(KeyType)type
    {
        NSMutableString *decryptResult = @"".mutableCopy;
        for (NSInteger i = 0; i < ceilf(content.length / 117); i ++)
        {
            NSString *subStr = [content substringWithRange:NSMakeRange(i * 117, 117)];
            NSString *decryptSubStr = [self decryptToData:subStr type:type];
            NSString *decryptStr = decryptSubStr.length <= 117 ? decryptSubStr : [decryptSubStr substringToIndex:117];
            [decryptResult appendString:decryptStr];
        }
        
        return decryptResult.URLDecode;
    }
    
    

    这里说明一下:
    1.使用openSSL需要把公钥/私钥先转成RSA结构对象,然后进行加密解密
    2.解决中文的问题是使用url编码来解决的,这个要跟后台对接好就行,一般都会使用url编码,当然也可以使用其他的,只不过是做了个转换而已
    3.因为密文长度不能大于密钥长度,所以一般会进行分段加密,分段加密时密文控制是117,因为加密时对密文使用了长度为11的填充(随机的),加起来就是128(1024位对应的密钥/密文长度)

    //长度获取
    - (int)getBlockSize:(RSAPaddingType)type type:(KeyType)keyType
    {
        int len = RSA_size([self RSAWithType:keyType]);
        
        if (type != eRSAPaddingTypeNone)
        {
            len -= 11;
        }
        
        return len;
    }
    

    好了,RSA加密和解密就可以使用了
    本文Demo

    相关文章

      网友评论

      • 终成陌生人:请问分段加密是否需要自己实现?
        fuaiyi:@终成陌生人 文中已经实现了分段加密
      • Jiar_:请问172是怎么得出来的呢?
        Jiar_:@fuaiyi 好的,谢谢。
        fuaiyi:不好意思 这里写错了 应该是117 128 - 填充的11位 = 117

      本文标题:iOS RSA加密与解密

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