【iOS开发】常用加密算法(二)RSA加密算法
非对称算法
定义
非对称加密需要两个密钥:公钥 (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];
}