【iOS开发】常用加密算法(二)RSA加密算法

2021-06-08  本文已影响0人  Dnaleci

非对称算法

定义

非对称加密需要两个密钥:公钥 (publickey) 和私钥 (privatekey)。公钥和私钥是一对,如果用公钥对数据加密,那么只能用对应的私钥解密。如果用私钥对数据加密,只能用对应的公钥进行解密。因为加密和解密用的是不同的密钥,所以称为非对称加密。

特点

算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。对称密码体制中只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。所以保证其安全性就是保证密钥的安全,而非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。这样安全性就大了很多。

主要算法

RSA、Elgamal、背包算法、Rabin、D-H、ECC (椭圆曲线加密算法)。使用最广泛的是 RSA 算法,Elgamal 是另一种常用的非对称加密算法

RSA算法

简介

RSA 是目前最有影响力的公钥加密算法,该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。

原理

基于篇幅问题,有兴趣的推荐看看下面文章:
RSA算法原理(一):https://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
RSA算法原理(二):https://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html

iOS端Security框架使用小结

支持的RSA keySize 大小有:512,768,1024,2048位
支持的RSA 填充方式有三种:NOPadding,PKCS1,OAEP 三种方式 ,填充方式影响最大分组加密数据块的大小
签名使用的填充方式PKCS1, 支持的签名算法有 sha1,sha256,sha224,sha384,sha512
Nopadding填充最大数据块为 下面接口 SecKeyGetBlockSize 大小;
PKCS1 填充方式最大数据为 SecKeyGetBlockSize大小 减去11
OAEP 填充方式最大数据为 SecKeyGetBlockSize 大小减去 42
RSA加密解密签名,适合小块的数据处理,大量数量需要处理分组逻辑;密码学中推荐使用对称加密进行数据加密,使用RSA来加密对称密钥

OC实现RSA算法

导入头文件

#import <security/Security.h>

RSA加密(Ps:笔者这里的密钥是通过字符串去生成,现在一般使用PEM文件,iOS端也用p12文件,请对照相应途径生成密钥)

#pragma mark - 使用公钥字符串加密

/**
 使用公钥字符串加密

 @param str 数据
 @param pubKey 公钥
 @return 返回16进制的hexstring
 */
+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey{
    if(!str)return nil;
    
    NSData *data = [self encryptData:[str dataUsingEncoding:NSUTF8StringEncoding] publicKey:pubKey];
    NSString *ret = [self hexStringFromData:data];
    
    return ret;
}

/**
 加密数据

 @param data 数据
 @param pubKey 公钥
 @return 操作结果
 */
+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey{
    if(!data || !pubKey){
        return nil;
    }
    SecKeyRef keyRef = [self addPublicKey:pubKey];
    if(!keyRef){
        return nil;
    }
    return [self encryptData:data withKeyRef:keyRef];
}

/**
 添加公钥

 @param key 公钥
 @return 公钥对象
 */
+ (SecKeyRef)addPublicKey:(NSString *)key{
    //对于 PEM 格式的密钥文件的密钥多余信息的处理,通常 DER 不需要这一步
    NSRange spos = [key rangeOfString:@"-----BEGIN PUBLIC KEY-----"];
    NSRange epos = [key rangeOfString:@"-----END PUBLIC KEY-----"];
    if(spos.location != NSNotFound && epos.location != NSNotFound){
        NSUInteger s = spos.location + spos.length;
        NSUInteger e = epos.location;
        NSRange range = NSMakeRange(s, e-s);
        key = [key substringWithRange:range];
    }
    key = [key stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@" "  withString:@""];
    
    // This will be base64 encoded, decode it.
    NSData *data = base64_decode(key);
    data = [self stripPublicKeyHeader:data];
    if(!data){
        return nil;
    }
    
    //a tag to read/write keychain storage
    NSString *tag = @"RSAUtil_PubKey";
    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];
    
    // Delete any old lingering key with the same tag
    NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
    [publicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
    [publicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    [publicKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
    SecItemDelete((__bridge CFDictionaryRef)publicKey);
    
    // Add persistent version of the key to system keychain
    [publicKey setObject:data forKey:(__bridge id)kSecValueData];
    [publicKey setObject:(__bridge id) kSecAttrKeyClassPublic forKey:(__bridge id)
     kSecAttrKeyClass];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
     kSecReturnPersistentRef];
    
    CFTypeRef persistKey = nil;
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)publicKey, &persistKey);
    if (persistKey != nil){
        CFRelease(persistKey);
    }
    if ((status != noErr) && (status != errSecDuplicateItem)) {
        return nil;
    }
    
    [publicKey removeObjectForKey:(__bridge id)kSecValueData];
    [publicKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
    [publicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    
    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef = nil;
    status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKey, (CFTypeRef *)&keyRef);
    if(status != noErr){
        return nil;
    }
    return keyRef;
}

/**
 制作公钥数据头部

 @param d_key 公钥
 @return 操作结果数据
 */
+ (NSData *)stripPublicKeyHeader:(NSData *)d_key{
    // Skip ASN.1 public key header
    if (d_key == nil) return(nil);
    
    unsigned long len = [d_key length];
    if (!len) return(nil);
    
    unsigned char *c_key = (unsigned char *)[d_key bytes];
    unsigned int  idx     = 0;
    
    if (c_key[idx++] != 0x30) return(nil);
    
    if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
    else idx++;
    
    // PKCS #1 rsaEncryption szOID_RSA_RSA
    static unsigned char seqiod[] =
    { 0x30,   0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
        0x01, 0x05, 0x00 };
    if (memcmp(&c_key[idx], seqiod, 15)) return(nil);
    
    idx += 15;
    
    if (c_key[idx++] != 0x03) return(nil);
    
    if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
    else idx++;
    
    if (c_key[idx++] != '\0') return(nil);
    
    // Now make a new NSData from this buffer
    return ([NSData dataWithBytes:&c_key[idx] length:len - idx]);
}

/**
 加密数据

 @param data 数据
 @param keyRef 密钥
 @return 操作结果
 */
+ (NSData *)encryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef{
    const uint8_t *srcbuf = (const uint8_t *)[data bytes];
    size_t srclen = (size_t)data.length;
    
    size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
    void *outbuf = malloc(block_size);
    size_t src_block_size = block_size - 11;
    
    NSMutableData *ret = [[NSMutableData alloc] init];
    for(int idx=0; idx<srclen; idx+=src_block_size){
        size_t data_len = srclen - idx;
        if(data_len > src_block_size){
            data_len = src_block_size;
        }
        
        size_t outlen = block_size;
        OSStatus status = noErr;
        status = SecKeyEncrypt(keyRef,
                               kSecPaddingPKCS1,
                               srcbuf + idx,
                               data_len,
                               outbuf,
                               &outlen
                               );
        if (status != 0) {
            ret = nil;
            break;
        }else{
            [ret appendBytes:outbuf length:outlen];
        }
    }
    
    free(outbuf);
    CFRelease(keyRef);
    return ret;
}

RSA解密

#pragma mark - 使用私钥字符串解密
/**
 使用私钥字符串解密
 
 @param base64Str 解密内容:必须传base64编码str,不然解密失败
 @param priKey 解密私钥
 @return 解密结果
 */
+ (NSString *)decryptString:(NSString *)base64Str privateKey:(NSString *)priKey{
    if(!base64Str)return nil;

    NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Str options:NSDataBase64DecodingIgnoreUnknownCharacters];
    data = [self decryptData:data privateKey:priKey];

    NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    return ret;
}

/**
 解密数据

 @param data 数据
 @param priKey 私钥
 @return 操作结果
 */
+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)priKey{
    if(!data || !priKey){
    return nil;
    }
    SecKeyRef keyRef = [self addPrivateKey:priKey];
    if(!keyRef){
    return nil;
    }
    return [self decryptData:data withKeyRef:keyRef];
}

/**
 添加私钥

 @param key 私钥
 @return 私钥对象
 */
+ (SecKeyRef)addPrivateKey:(NSString *)key{
    //对于 PEM 格式的密钥文件的密钥多余信息的处理,通常 DER 不需要这一步
    NSRange spos = [key rangeOfString:@"-----BEGIN PRIVATE KEY-----"];
    NSRange epos = [key rangeOfString:@"-----END PRIVATE KEY-----"];
    
    if(spos.location != NSNotFound && epos.location != NSNotFound){
    NSUInteger s = spos.location + spos.length;
    NSUInteger e = epos.location;
    NSRange range = NSMakeRange(s, e-s);
    key = [key substringWithRange:range];
    }
    key = [key stringByReplacingOccurrencesOfString:@"\r"withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\n"withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\t"withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@" "withString:@""];

    // This will be base64 encoded, decode it.
    NSData *data = base64_decode(key);
    data = [self stripPrivateKeyHeader:data];
    if(!data){
    return nil;
    }
    
    //a tag to read/write keychain storage
    NSString *tag =@"RSAUtil_PrivKey";
    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

    // Delete any old lingering key with the same tag
    NSMutableDictionary *privateKey = [[NSMutableDictionary alloc] init];
    [privateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
    [privateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    [privateKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
    SecItemDelete((__bridge CFDictionaryRef)privateKey);
    
    // Add persistent version of the key to system keychain
    [privateKey setObject:data forKey:(__bridge id)kSecValueData];
    [privateKey setObject:(__bridge id) kSecAttrKeyClassPrivate forKey:(__bridge id)kSecAttrKeyClass];
    [privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
    kSecReturnPersistentRef];

    CFTypeRef persistKey =nil;
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)privateKey, &persistKey);
    if(persistKey !=nil){
    CFRelease(persistKey);
    }

    if((status != noErr) && (status != errSecDuplicateItem)) {
    return nil;
    }

    [privateKey removeObjectForKey:(__bridge id)kSecValueData];
    [privateKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
    [privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
    [privateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];

    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef =nil;

    status = SecItemCopyMatching((__bridge CFDictionaryRef)privateKey, (CFTypeRef *)&keyRef);
    if(status != noErr){
    return nil;
    }

    return keyRef;
}

/**
 制作私钥数据头部

 @param d_key 私钥
 @return 操作结果数据
 */
+ (NSData *)stripPrivateKeyHeader:(NSData *)d_key{
    // Skip ASN.1 private key header
    if(d_key ==nil)return (nil);
    unsigned long len = [d_key length];

    if(!len)return(nil);

    unsigned char *c_key = (unsigned char*)[d_key bytes];
    unsigned int idx = 22;//magic byte at offset 22
    if(0x04 != c_key[idx++]) return nil;

    //calculate length of the key
    unsigned int c_len = c_key[idx++];

    int det = c_len &0x80;
    if(!det) {
    c_len = c_len &0x7f;
    }else{
    int byteCount = c_len &0x7f;
    if(byteCount + idx > len) {
    //rsa length field longer than buffer
    return nil;
    }

    unsigned int accum =0;
    unsigned char *ptr = &c_key[idx];
        
    idx += byteCount;
    while(byteCount) {
    accum = (accum <<8) + *ptr;
    ptr++;
    byteCount--;
    }
    c_len = accum;
    }

    // Now make a new NSData from this buffer
    return[d_key subdataWithRange:NSMakeRange(idx, c_len)];
}

/**
 解密数据

 @param data 数据
 @param keyRef 密钥
 @return 操作结果
 */
+ (NSData *)decryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef{
    const uint8_t *srcbuf = (const uint8_t *)[data bytes];
    size_t srclen = (size_t)data.length;
    
    size_t block_size = SecKeyGetBlockSize(keyRef) *sizeof(uint8_t);
    UInt8 *outbuf = malloc(block_size);
    size_t src_block_size = block_size;
    
    NSMutableData *ret = [[NSMutableData alloc] init];
    for(int idx=0; idx<srclen; idx+=src_block_size) {
        size_t data_len = srclen - idx;
        if (data_len > src_block_size) {
            data_len = src_block_size;
        }
        
        size_t outlen = block_size;
        OSStatus status = noErr;
        status = SecKeyDecrypt(keyRef,
                               kSecPaddingPKCS1,
                               srcbuf + idx,
                               data_len,
                               outbuf,
                               &outlen
                               );
        
        if (status !=0) {
            NSLog(@"SecKeyDecrypt fail. Error Code: %d", status);
            ret =nil;
            break;
        } else {
            //the actual decrypted data is in the middle, locate it!
            int idxFirstZero = -1;
            int idxNextZero = (int)outlen;
            
            for (int i =0; i < outlen; i++ ) {
                if (outbuf[i] == 0) {
                    if (idxFirstZero < 0) {
                        idxFirstZero = i;
                    } else {
                        idxNextZero = i;
                        break;
                    }
                }
            }
            [ret appendBytes:&outbuf[idxFirstZero+1] length:idxNextZero-idxFirstZero-1];
        }
    }

    free(outbuf);
    CFRelease(keyRef);
    return ret;
}

工具方法

#pragma mark - Utils

/**
 base64解码
 
 @param str 数据
 @return 解码后结果
 */
static NSData *base64_decode(NSString *str){
    NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
    return data;
}

static inline char itoh(int i) {
    if (i > 9) return 'A' + (i - 10);
    return '0' + i;
}

//data数据转hex string
+ (NSString *) hexStringFromData:(NSData*) data
{
    NSUInteger i, len;
    unsigned char *buf, *bytes;
    
    len = data.length;
    bytes = (unsigned char*)data.bytes;
    buf = (unsigned char *)malloc(len*2);
    for (i=0; i<len; i++) {
        buf[i*2] = itoh((bytes[i] >> 4) & 0xF);
        buf[i*2+1] = itoh(bytes[i] & 0xF);
    }
    
    return [[NSString alloc] initWithBytesNoCopy:buf
                                          length:len*2
                                        encoding:NSASCIIStringEncoding
                                    freeWhenDone:YES];
}

Ps:一个小问题值得一看

为什么RSA公钥每次加密得到的结果都不一样?

上一篇下一篇

猜你喜欢

热点阅读