zoukankan      html  css  js  c++  java
  • RSA算法及其在iOS中的使用

    因为项目中需要传输用户密码,为了安全需要用RSA加密,所以就学习了下RSA加密在iOS中的应用。
    关于RSA的历史及原理,下面的两篇文章讲的很清楚了:
     
    简单来说,RSA建立在一个数学难题之上,就是大数分解:将两个大素数相乘十分容易,但是想要对其乘积进行因式分解却极其困难。至于为什么难,难在哪里那就是数学家的事了。。。
    明白了这个就可以大致知道RSA的原理:非对称加密
    (1)乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。
    (2)甲方获取乙方的公钥,然后用它对信息加密。
    (3)乙方得到加密后的信息,用私钥解密。
     
    就好比有一套特殊的锁和钥匙,锁是公开的,谁都可以拿这个锁来锁住他的东西,只有有钥匙的人可以打开。
    那么问题来了,既然锁是公开的,难道不能通过锁的结构来倒推出钥匙的形状吗?
    答案是:不能!因为这个锁是特殊的,它就特殊在很难倒推。(这个倒不是绝对的,也许将来某一天大数分解的数学难题解决了,这种算法就不安全了,详见开头链接)
     
    我遇到的应用场景是,客户端有服务器的公钥,客户端要把用户的密码用公钥加密上后上传到服务器,服务器可以用私钥解密。
    所以客户端要做的是,将需要加密的内容用服务器给的公钥进行RSA加密。
    iOS上并没有直接的RSA加密API,所以需要折腾一下。
    gitHub上的代码大同小异,主要是三个方法(抄自https://github.com/ideawu/Objective-C-RSA
    注意代码里有个kSecPaddingPKCS1是作者写死的,而我们的项目中需要传kSecPaddingNone才行!!!
     
    + (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++] != '') return(nil);
    
    // Now make a new NSData from this buffer
    return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
    }
     
     
    + (SecKeyRef)addPublicKey:(NSString *)key{
    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:@"
    " withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"
    " withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"	" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@" "  withString:@""];
    
    // This will be base64 encoded, decode it.
    NSData *data = base64_decode(key);
    data = [RSA stripPublicKeyHeader:data];
    if(!data){
    return nil;
    }
    
    NSString *tag = @"what_the_fuck_is_this";
    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;
    }
     
     1 + (NSString *)encryptData:(NSData *)data publicKey:(NSString *)pubKey{
     2 if(!data || !pubKey){
     3 return nil;
     4 }
     5 SecKeyRef keyRef = [RSA addPublicKey:pubKey];
     6 if(!keyRef){
     7 return nil;
     8 }
     9 
    10 const uint8_t *srcbuf = (const uint8_t *)[data bytes];
    11 size_t srclen = (size_t)data.length;
    12 
    13 size_t outlen = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
    14 if(srclen > outlen - 11){
    15 CFRelease(keyRef);
    16 return nil;
    17 }
    18 void *outbuf = malloc(outlen);
    19 
    20 OSStatus status = noErr;
    21 status = SecKeyEncrypt(keyRef,
    22   kSecPaddingNone, //原作者写的是kSecPaddingPKCS1,经春哥研究这里写成kSecPaddingNone才符合我们使用
    23   srcbuf,
    24   srclen,
    25   outbuf,
    26   &outlen
    27   );
    28 NSString *ret = nil;
    29 if (status != 0) {
    30 //NSLog(@"SecKeyEncrypt fail. Error Code: %ld", status);
    31 }else{
    32 NSData *data = [NSData dataWithBytes:outbuf length:outlen];
    33 ret = base64_encode_data(data);
    34 }
    35 free(outbuf);
    36 CFRelease(keyRef);
    37 return ret;
    38 }
     
    还有一篇文章可以参考:http://blog.iamzsx.me/show.html?id=155002
     
     
    签名机制
    仅仅加密某个参数是不够的,还需要保证请求没有被篡改,所以签名机制就很有必要。
    比较简单和常用就是MD5签名:
    拿到待签名的字符串A(比如某个url),将其与服务器约定好的密钥拼成新的字符串B,对B进行MD5算法得到签名C,
    然后将C作为A的签名一起发送到服务器。
    服务器收到请求后,对A用与客户端约定好的密钥进行相同的算法得到C’,如果C==C’,那就说明改请求没有被篡改过,
    否则验证不通过
     
    当然也可以做RSA签名
    这个要比MD5签名要稍微麻烦一点,因为需要客户端生成公钥私钥对,基本流程也和MD5签名一样
    拿到待签名的字符串A(比如某个url),将其用私钥加密得到的字符串B,然后将B和原数据A还有自己的公钥一起发送给服务器,
    服务器收到请求,用公钥解密得到B',如果B==B',则说明原数据没有被篡改过,否则验证不通过。
     
    也有说这里得到B以后,需要再用服务器的公钥加密一遍得到C,将C和原数据和自己的公钥一起发送给服务器,
    服务器收到之后,现需要用自己的私钥解密一遍得到C',然后再用客户端公钥解密得到B',然后同上。。。
     
    RSA签名及验证我还没用到,所以具体怎么实现的还需要研究下,待补充!!!
     
     
    HTTPS
    https算是对RSA加密的一个典型应用吧,不过这个服务器的公钥私钥不是自己生产的,而是CA颁发的。
    具体原理网上很多,其中一个:http://jingyan.baidu.com/article/2fb0ba4048e15500f3ec5f7e.html
     
     
  • 相关阅读:
    Ajax Slider for GridView Page
    Silverlight 3,Blend 3 ,Asp.NET MVC 1.0
    (牛人莫入) Silverlight 3 之控件DataGrid
    如何改短日期类型为长日期类型?
    获取滚动条的高度和宽度
    form设置为可移动
    时间操作技术之delphi
    调用键盘的回车键来运行程序
    运算符重载
    字符串解析
  • 原文地址:https://www.cnblogs.com/Phelthas/p/4584507.html
Copyright © 2011-2022 走看看