因为项目中需要传输用户密码,为了安全需要用RSA加密,所以就学习了下RSA加密在iOS中的应用。
关于RSA的历史及原理,下面的两篇文章讲的很清楚了:
http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
简单来说,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 headerif(d_key == nil)return(nil);
unsignedlonglen =[d_key length];if(!len)return(nil);
unsignedchar*c_key = (unsignedchar*)[d_key bytes];
unsignedintidx =0;if(c_key[idx++] !=0x30)return(nil);if(c_key[idx] >0x80) idx += c_key[idx] -0x80+1;elseidx++;//PKCS #1 rsaEncryption szOID_RSA_RSAstaticunsignedcharseqiod[] ={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;elseidx++;if(c_key[idx++] !='\0')return(nil);//Now make a new NSData from this bufferreturn([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:@"\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=[RSA stripPublicKeyHeader:data];if(!data){returnnil;
}
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 tagNSMutableDictionary *publicKey =[[NSMutableDictionary alloc] init];
[publicKey setObject:(__bridgeid) kSecClassKey forKey:(__bridgeid)kSecClass];
[publicKey setObject:(__bridgeid) kSecAttrKeyTypeRSA forKey:(__bridgeid)kSecAttrKeyType];
[publicKey setObject:d_tag forKey:(__bridgeid)kSecAttrapplicationTag];
SecItemDelete((__bridge CFDictionaryRef)publicKey);//Add persistent version of the key to system keychain[publicKey setObject:data forKey:(__bridgeid)kSecValueData];
[publicKey setObject:(__bridgeid) kSecAttrKeyClassPublic forKey:(__bridgeid)
kSecAttrKeyClass];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridgeid)
kSecReturnPersistentRef];
CFTypeRef persistKey=nil;
OSStatus status= SecItemAdd((__bridge CFDictionaryRef)publicKey, &persistKey);if(persistKey !=nil){
CFRelease(persistKey);
}if((status != noErr) && (status !=errSecDuplicateItem)) {returnnil;
}
[publicKey removeObjectForKey:(__bridgeid)kSecValueData];
[publicKey removeObjectForKey:(__bridgeid)kSecReturnPersistentRef];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridgeid)kSecReturnRef];
[publicKey setObject:(__bridgeid) kSecAttrKeyTypeRSA forKey:(__bridgeid)kSecAttrKeyType];//Now fetch the SecKeyRef version of the keySecKeyRef keyRef =nil;
status= SecItemCopyMatching((__bridge CFDictionaryRef)publicKey, (CFTypeRef *)&keyRef);if(status !=noErr){returnnil;
}returnkeyRef;
}
1+ (NSString *)encryptData:(NSData *)data publicKey:(NSString *)pubKey{2if(!data || !pubKey){3returnnil;4}5SecKeyRef keyRef =[RSA addPublicKey:pubKey];6if(!keyRef){7returnnil;8}910constuint8_t *srcbuf = (constuint8_t *)[data bytes];11size_t srclen =(size_t)data.length;1213size_t outlen = SecKeyGetBlockSize(keyRef) *sizeof(uint8_t);14if(srclen > outlen -11){15CFRelease(keyRef);16returnnil;17}18void*outbuf =malloc(outlen);1920OSStatus status =noErr;21status =SecKeyEncrypt(keyRef,22kSecPaddingNone,//原作者写的是kSecPaddingPKCS1,经春哥研究这里写成kSecPaddingNone才符合我们使用23srcbuf,24srclen,25outbuf,26&outlen27);28NSString *ret =nil;29if(status !=0) {30//NSLog(@"SecKeyEncrypt fail. Error Code: %ld", status);31}else{32NSData *data =[NSData dataWithBytes:outbuf length:outlen];33ret =base64_encode_data(data);34}35free(outbuf);36CFRelease(keyRef);37returnret;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