伴随着移动产品广泛应用与快速发展,移动客户端的安全问题越来越受重视。我这里主要谈谈iOS移动端的安全架构设计。
一、前言
移动端的安全一般可以理解为两个方面:本地安全、通信安全。
1、本地安全:
本地安全主要指客户端本地环境与数据的安全,以及代码被破解获得所导致的安全问题,如:明文存储问题,恶意二次打包问题,越权操作问题等。2、通信安全:
通信安全主要指客户端与服务端进行数据交互时被拦截破获导致的安全问题。如采取明文进行密码或者数据传输等问题。
关于一些移动端安全的认识还有web安全等等,请参考文章:移动端本地安全解决方案
二、安全加密基础的快速认识
加密分为对称加密和非对称加密。
1、对称加密
加密使用的密钥和解密使用的密钥是相同的。也就是说,加密和解密都是使用的同一个密钥,也就是共享密钥密码。(ps:这个很好理解吧) 常用的对称加密算法:
名称 | 英文 |
---|---|
DES | Data Encryption Standard |
3DES | Triple DES |
AES | dvanced Encryption Standard |
2、非对称加密
加密使用的密钥和解密使用的密钥是不相同的。 (ps:这个不好理解?不好理解请好好看看非对称加密算法原理) 简单来说就是有一套算法可以实现你用公钥加密的信息,对方用保留的私钥进行解密。
常说的公钥密码就是指非对称密码,公钥加密就是一种非对称加密。
最常用的是RSA算法。RSA的演算方法是:
1)用户选择2个够大的保密质数q、p(一般为100位以上十进数) 2)令n=pq,n是公开的,从n分解除qp是极其困难的。 n的欧拉函数:Φ(n)=(p-1)(q-1) Φ(n)小于等于n,并与n互质 3)选择一个相对大的整数e作为加密指数,使e与Φ(n)互质, 4)解同等方程: ed=1modΦ(n) 求出解密指数d 5)设M、C分别为要加密的明文和被加密的密文(M、C小于n) 则:加密运算为:C=Memod n 解密运算为:M=Cdmod n 6)每个用户都有一组密钥(e、d、n) (e,n)为PK'可以公开在手册上的公钥,e为加密指数, (d,n)为SK’(或PV)是用户保密的私钥 将p.q销毁 7)要求明文X 举例: 1) 选两个质数: p=47 q=71 2)计算: n=pq=3337 Φ(n)=(47-1)(71-1)=3220 3) e必须与Φ(n)互质,选e=79 4) 计算:ed=1modΦ(n)=1mod(3220) d=1019 将e、n公布,d保密,p.q消毁 如有一明文 M=6882326879666683要加密,则先将M分割成多块: m1=688,m2=232,m3=687,m4=966,m5=668,m6=3 将第1块M1加密后得密文C1: C1=m1e(mod3337)=68879(mod3337)=1570 依次对各区块加密后得密文C: C=15702756271422762423158 对C1解密得m1 M1=C1d(mod3337)=15701019(mod3337)=688 依次解密得原文M。
那么问题来了,我们遇到的PBE(Password Based Encryption,基于口令加密)加密啊都是什么呢。详情可以去了解下,这里强调一点,PBE算法的应用还是在对称加密范畴。至于哪种加密方案适合自己的场景,这也是我们今天iOS移动端安全架构设计的时候需要考虑的。
知识点延伸:
1、对称密码与公钥密码对比,消息认证码与数字签名的对比
对象属性 | 对称密码 | 公钥密码(非对称密码) |
---|---|---|
发送者 | 用共享密钥加密-双方持有 | 用公钥加密 |
接收者 | 用共享密钥加密-双方持有 | 用私钥解密 |
密钥配送问题 | 存在 | 不存在,但公钥需要另外认证 |
机密性 | ✅ | ✅ |
对象属性 | 消息认证码 | 数字签名 |
---|---|---|
发送者 | 用共享密钥计算MAC值 | 用私钥生成签名 |
接收者 | 用共享密钥计算MAC值 | 用公钥验证签名 |
密钥配送问题 | 存在 | 不存在,但公钥需要另外认证 |
完整性 | ✅ | ✅ |
认证 | ✅(仅限通信对象双方) | ✅ (可适用于任何第三方) |
防止否认 | ❌ | ✅ |
2、公钥密码与数字签名的密钥适用方式
对象属性 | 公钥 | 私钥 |
---|---|---|
公钥密码 | 发送者加密时使用 | 接收者解密时使用 |
数字签名 | 验证者验证签名时使用 | 签名者生成签名时使用 |
谁持有密钥? | 只要需要,可任何人都可以持有 | 个人持有 |
数字签名就是利用非对称加密的原理,只是用法下不一样,反着的。另外为公钥加上数字签名就是证书。
二、iOS移动端设计的安全考量
移动客户端设计时,除了上述宽泛的本地安全和通信安全需要考量,具体到模块与业务需要开发者作哪些思考呢?本地密码存储安全怎么保证,客户端web页面怎么防止被劫持,客户端在设计交易时如何考虑安全性等等问题。如今苹果开始强制接口使用HTTPS协议也是为为了增强安全性,但开发者在客户端设计之初也需要考虑这些安全问题,建立一些基础的安全模块。
(ps:至于涉及到金融类App考虑的安全问题就更多,如交易安全,私钥如何存储等)
1、安全需求分析
加密原则:
- App代码安全,包括代码混淆,加密或者app加壳。
- App数据存储安全,主要指在磁盘做数据持久化的时候所做的加密。
- App网络传输安全,指对数据从客户端传输到Server中间过程的加密,防 止网络世界当中其他节点对数据的窃听。
本地存储安全:
1)、明文存储,过度依赖系统安全性(iOS:Keychain/UserDefault)
2)、恶意二次打包
3)、容易被逆向,被调试
4)、敏感信息写入代码
网络通信安全:
1)、HTTP传输被劫持(苹果开始强制接口使用HTTPS协议)
2)、明文传输数据
另外由本地安全延伸出来:
1)、本地自动登录如何存储私钥,如何解决又方便用户,又保证安全。
2)、金融类、支付类App如何解决支付安全问题,二维码支付的安全问题等等。
2、需求技术准备
1)、针对本地明文存储的安全漏洞,自然是要本地进行加密,本地加密只是增加了一些复杂度,但也是有效的。比如利用对称算法,通过简单的URLENCODE + BASE64编码防止数据明文传输
2)、对普通请求、返回数据,生成MD5校验(MD5中加入动态密钥),进行数据完整性(简单防篡改,安全性较低,优点:快速)校验。发送请求也有PBE加密等等
3)、对于重要数据,使用RSA进行数字签名,起到防篡改作。
4)、 对于比较敏感的数据,如用户信息(登陆、注册等),客户端发送使用RSA加密,服务器返回使用DES(AES)加密。
类型 | 简介 |
---|---|
本地数据加密 | 对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息。 |
URL编码加密 | 对程序中出现的URL进行编码加密,防止URL被静态分析 |
网络传输数据加密 | 对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取 |
方法体,方法名高级混淆 | 对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码 |
程序结构混排加密 | 对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低 |
三、iOS移动端安全模块设计
1、基础模块
1)、对称加密工具类
提供iOS客户端的单向散列和基础对称加密算法(MD5 \ SHA \ DES \ 3DES \ RC2和RC4 \ IDEA \ DSA \ AES)
//Base64加密
+(NSString *)encodeBase64:(NSString *)input {
NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
data = [GTMBase64 encodeData:data];
NSString *base64String = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"encodeBase64 == %@",base64String);
return base64String;
}
//Base64编码
+(NSString *)base64EncodeString:(NSString *)string {
//1.先把字符串转换为二进制数据
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
//2.对二进制数据进行base64编码,返回编码后的字符串
//这是苹果已经给我们提供的方法
NSString *str = [data base64EncodedStringWithOptions:0];
return str;
}
//Base64解密
+(NSString *)decodeBase64:(NSString *)input {
NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
data = [GTMBase64 decodeData:data];
NSString *base64String = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"decodeBase64 == %@",base64String);
return base64String;
}
//对base64编码后的字符串进行解码
+(NSString *)base64DecodeString:(NSString *)string {
//1.将base64编码后的字符串『解码』为二进制数据
//这是苹果已经给我们提供的方法
NSData *data = [[NSData alloc]initWithBase64EncodedString:string options:0];
//2.把二进制数据转换为字符串返回
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
return str;
}
//Base64 字符串解码成 字符串
+(NSString *)decodeBase64ToHexString:(NSString *)input {
//1.将base64编码后的字符串『解码』为二进制数据
//这是苹果已经给我们提供的方法
NSData *myD = [[NSData alloc]initWithBase64EncodedString:input options:0];
Byte *bytes = (Byte *)[myD bytes];
//下面是Byte 转换为16进制。
NSString *hexStr=@"";
for(int i=0;i<[myD length];i++) {
NSString *newHexStr = [NSString stringWithFormat:@"%x",bytes[i]&0xff];///16进制数
if([newHexStr length]==1)
hexStr = [NSString stringWithFormat:@"%@0%@",hexStr,newHexStr];
else
hexStr = [NSString stringWithFormat:@"%@%@",hexStr,newHexStr];
}
return hexStr;
}
//DES 加密
+(NSData *)encryptUseDES:(NSData *)plainText key:(Byte *)key {
NSData *textData = plainText;
NSUInteger dataLength = [textData length];
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES,
kCCOptionECBMode,
key, kCCKeySizeDES,
nil,
[textData bytes], dataLength,
buffer, 1024,
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
return data;
}
return nil;
}
//DES 解密
+(NSData *)decrypUseDES:(NSData *)plainText key:(Byte *)key {
NSData *cipherdata = plainText;
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmDES,
kCCOptionECBMode,
key, kCCKeySizeDES,
nil,
[cipherdata bytes], [cipherdata length],
buffer, 1024,
&numBytesDecrypted);
if(cryptStatus == kCCSuccess) {
NSData *plaindata = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
return plaindata;
}
return nil;
}
/MD5加密返回Nsdata
+(NSData *)encodeMD5:(NSData *)input {
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(input.bytes, (CC_LONG)input.length, result);
NSData *data =[[NSData alloc] initWithBytes:result length:CC_MD5_DIGEST_LENGTH];
return data;
}
如下,生成是小写的MD5的字符串,如果要生成大写的,只需要把
[ret appendFormat:@"%02X",result[i]];中的 "%02X"的 X改成小写的 x即可。
//MD5加密返回Nsstring
+(NSString *)MD5HexDigest:(NSData *)input {
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(input.bytes, (CC_LONG)input.length, result);
NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
for (int i = 0; i<CC_MD5_DIGEST_LENGTH; i++) {
[ret appendFormat:@"%02X",result[i]];
}
return ret;
}
2)、非对称加密工具类
iOS中的Security.framework提供了对RSA算法的支持。这种方式需要对密匙对进行处理,根据public key生成证书,通过private key生成p12格式的密匙。
除了Secruty.framework,也可以将openssl库编译到iOS工程中,这可以提供更灵活的使用方式。
开源的:OpenSSLRSAWrapper
2、一些场景方案设计举例:(登录场景token)
1)、一般的登陆:
客户端第一次发出登录请求时, 用户密码以明文的方式传输, 一旦被截获, 后果严重。因此密码需要加密,例如可采用RSA非对称加密。具体流程如下:
- 客户端向服务器第一次发起登录请求(不传输用户名和密码)。
- 服务器利用RSA算法产生一对公钥和私钥。并保留私钥, 将公钥发送给客户端。
- 客户端收到公钥后, 加密用户密码, 向服务器发起第二次登录请求(传输用户名和加密后的密码)。
- 服务器利用保留的私钥对密文进行解密,得到真正的密码。
2)、token加密:
再仔细核对上述登录流程, 我们发现服务器判断用户是否登录, 完全依赖于sessionId, 一旦其被截获, 黑客就能够模拟出用户的请求。于是我们需要引入token的概念: 用户登录成功后, 服务器不但为其分配了sessionId, 还分配了token, token是维持登录状态的关键秘密数据。在服务器向客户端发送的token数据,也需要加密。于是一次登录的细节再次扩展:
- 客户端向服务器第一次发起登录请求(不传输用户名和密码)。服务器利用RSA算法产生一对公钥和私钥。并保留私钥, 将公钥发送给客户端。
- 客户端收到公钥后, 加密用户密码,向服务器发送用户名和加密后的用户密码; 同时另外产生一对公钥和私钥,自己保留私钥, 向服务器发送公钥; 于是第二次登录请求传输了用户名和加密后的密码以及客户端生成的公钥。
- 服务器利用保留的私钥对密文进行解密,得到真正的密码。 经过判断, 确定用户可以登录后,生成sessionId和token, 同时利用客户端发送的公钥,对token进行加密。最后将sessionId和加密后的token返还给客户端。
- 客户端利用自己生成的私钥对token密文解密, 得到真正的token。
图示如下:
3)、登录保持(也就是http/https数据请求阶段)
引入token后,http/https请求被获取问题便可得到解决。 客户端将token和其它的一些变量, 利用散列加密算法得到签名后,连同sessionId一并发送给服务器; 服务器取出保存于服务器端的token,利用相同的法则生成校验签名, 如果客户端签名与服务器的校验签名一致, 就认为请求来自登录的客户端。(支付宝一样的机制)结构图如下:
注:token失效的两种情况:
1、用户登录出系统
2、token在后台的规定时间内失效(每个token都是有时间效应的)
失效原理:在服务器端的redis中删除相应key为session的键值对。
四、总结
本文只是初步介绍了一些加密的基础知识和iOS客户端安全设计时的基本思路,为大家提供一种参考。当然一些金融类和支付类的App对安全性的要求更高,在支付、扫码等环节的安全设计方案可能需要更加的完善,甚至离线支付时的私钥分端保存等等,这些都是需要思考的,也希望大家给出好的建议和方案。