前言
目前移动APP铺天盖地,移动网关也是必备的系统,移动端安全我会单独写一篇文章,这篇文章主要是介绍下移动网关的安全设计。做报文安全无非就是解决三个问题,防窃取、防篡改、防抵赖,下面我们就如何解决这3个问题来给出具体的安全方案。
1. 防抵赖
先从最简单的问题说起,这3个问题中,防抵赖是最简单的实现的,方案在业界也是比较统一的,就是通过双方签名来防抵赖。实现双方签名那就必须要两套密钥,一套是客户端公私钥,一套是网关端公私钥。在客户端保存客户端私钥(CK-private)和网关端公钥(SK-public),网关端保存客户端公钥(CK-public)和网关端私钥(SK-private)。
对报文的签名流程其实比较简单:
Screenshot 2018-01-10 13.31.01.png在整个过程中,因为是经过双方验签的一旦交易成功,所以一旦交易成功双方不能对交易进行抵赖,因为报文签名都是各自使用私钥进行签名,其他人无法仿造签名发起交易,所以双向验签的过程起到了一个防抵赖的作用。
安全问题:整个签名的方案实现防抵赖的前提是客户端私钥、服务端私钥没有泄漏,客户端如何安全保存客户端私钥?这个问题后续会在移动端安全的文章中详解,有一些业界的标准做法,还有一些小的tips可以参考。服务端私钥安全存储问题一般大公司会直接保存在加密机中,很多互联网初创公司没有加密机一般就直接硬编码到代码中或者保存在用C写的代码中打成so包。
加密方式:非对称(RSA、SM2)、散列算法(SHA256、SHA1、MD5、SM3)
2. 防篡改
通过防抵赖的技术一样可以做到防篡改,因为如果报文被篡改了,那么交易签名一定会验证失败,安全问题也是和防抵赖一样,要保存好双方的私钥,只要在私钥没有丢的情况下,报文一旦被篡改,验签一定会失败。
3. 防窃取
报文防窃取其实就是做到报文加密,我们知道报文加密无非是对称加密和非对称加密,但是对加密有些了解的同学应该知道,对称加解密和非对称加解密之间性能差异是巨大的,对称加密的性能是非对称加密的几十倍,所以对于单纯的业务报文加密一般都是使用对称加密,常用的对称加密算法有AES、3DES、SM4,因为对称密钥的安全性比较低,尤其是对于APP客户端,一旦客户端的对称密钥被窃取那么整个报文加密体系将被攻破,那么我们一般会定期进行对称密钥的更新来保证密钥定期被更新。下面就介绍下常用的一些报文加密方案。
3.1 传统报文加密方式
为了保证报文的安全传统的做法一般是客户端和服务端都保存对称密钥使用对称加密算法进行加密传输,对称加密对于一般的交易是可以保证性能和安全性的。但是客户端一定要保存好对称密钥,而且因为需要有一个密钥更新体系,能够做到定期自动更新密钥,如果长期不更新密钥,存在的风险就比较大。
传统的报文对称加密一般通过软件实现,对于安全性有要求的金融类公司一般会使用加密机来提高加解密性能。
加密:对称加密一般通过AES、3DES、SM4来实现,各个语言也都有这些算法的实现。也可以通过加密机使用加密机相关指令进行加密。
密钥更新: 定期进行密钥更新保证对称密钥定期变化,可以通过非对称算法来加密密钥来进行密钥传递,服务端使用服务端私钥对新的对称密钥加密,客户端使用服务端公钥进行解密,得到加密报文的对称密钥。
安全问题:密钥更新(秘钥传输)、密钥安全存储
报文对称加密
对称密钥更新
3.2 一机一密方案
Screenshot 2018-01-10 13.32.33.png安全问题:客户端预制根密钥的安全存储
一机一密的方案目前最大的问题就是交换密钥的性能问题,如果对于金融公司要求安全性极高,那么还可以将交换密钥的请求和相应报文全部改为数字信封的方式,那么性能将更低,但同时也更加安全,对于请求并发量很大的平台类系统其实不建议采用这种方案,这种方案会使得平台本身成为性能瓶颈,对于这类平台有大并发量又要保证安全一个可以通过前置的WAF防火墙之类的硬件来防DDos、洪流等恶意访问,也可以通过在平台照顾性能稍微降低安全性,在后台相关业务系统或者整体的监控系统来做整体的全链路安全,全链路安全也是后续的一个安全的发展方向,因为攻破一个系统的安全防护容易,但是要攻破一堆系统的安全防护那是很难的。
3.3 动态密钥库方案
前面提到的方案其实都涉及到本地的密钥安全存储和密钥传递的安全问题,那么有没有更好的方案可以兼顾这两个问题,并且能保证性能呢?答案肯定是有。动态密钥库的方案,其实可以算是白盒加密的一种简化版,这里先解释下白盒加密,白盒加密属于对称加密,是指能够在白盒环境(密钥和算法完全可见)下抵御攻击的一种特殊加密方法。一般来说算法和密钥匙独立的,白盒加密将算法和密钥紧密捆绑在了一起,由算法和密钥生成一个加密表和一个解密表,然后可以独立用查找加密表来加密,用解密表解密,不再依赖于原来的加解密算法和密钥。
如果对安全不是很了解的朋友可能不太能看得懂,这里大白话说就是在客户端保存了一组密钥,但是密钥是和算法混合在了一起,并不是简单的在代码中静态final变量定义的一个密钥字符串,而是要通过算法一定的运算才能够得到的对称密钥,服务端根据同样的算法生成解密的对称密钥进行报文解密。那么这种方案就很好的解决了一个密钥安全存储的问题和交换密钥的问题,因为根本不需要安全存储密钥和交换密钥。
但是白盒加密算法目前没有开源的,并且都是各个安全公司的核心机密,所以我们可以通过一个变种简化方案来实现类似的功能,虽然安全性比白盒加密算法弱化了,但是相对传统的加密方案还是要安全很多,并且因为全程不需要交换密钥,全程对称加密,所以性能是能够得到很好的保障的。
下面我就介绍下动态秘钥库的简化方案,就是保留加解密密码表,但是还是将加密算法和从密钥表里选取密钥的算法还有密钥表三者分离,这样也实现了密钥的白盒,就算被拿到了,黑客也不知道每次交易要用哪把密钥,这里最重要的就是保证密钥选取算法的安全,密钥选取算法需要进行高强度的混淆并且要用C来编写要尽兴防反编译和防调试特殊处理,下面说下动态秘钥库的具体简化方案,大家可以根据各自公司的特点进行优化和升级,
1)本地预制密钥库,密钥库可以有很多种形式保存,可以保存为一个密钥字符串数组,也可以保存为一个超长密钥字符串类似以前的密码表,每次取其中的几位来作为密钥,具体的密钥库形式多种多样,自己可以自由定制,虽然说密钥库可以被暴露,但是还是建议通过一些手段来安全保存;
//可以将密钥保存在本地用C需要写的代码中,编程成so包,防止被太容易查看到
//这里介绍一种通过超长密码字符串作为密码表的例子,密钥可以在代码中分散保存使用的时候拼接后使用
private static final String key = "45b13X60U4VFqH18rJ8fZdF5rBGt52N02G4N068RVy2c203I3GiDeg36ED0HUo347b81BWZ5wFPu59ufx23C7048fDdr927h9GP6Y49IfR76Ba295A001S6wdNDnM4gOu7608r19i4272NKVF52ahN6GEwW77850U5l4555KQ18c6OMTtAo2gH3E590q51l9gBs45xo6N547445wHoqCxUH9Hd0eO9cx33208Q43B2hRA3600V6hA860sw0e8RiR3Wv1M5601ttgQvgk432xL5IZ73885J4CnB2g9glJ697454W45P2X9310893LpQe39K921u17rSvCRs454r3Rn6N61B3VE2A27d362OV8C8R1x37Bcv4lD1t5434TV5w202WWU1c4mI5grN97H12KW4Epv2v3R904jzj23qEqh532882n3S35vz993P0fQ38iXo8xK8A29tp27Aw3D1Junwh1231y7k5uN597p158Vu8OMM1a0H2T9bn991161HX5MP1680HNgI9a1600Z78K6gujN8QmxsN2p35b10n458eB613W1RJgI3Z4j3344q9G3ZmH083379DT9e9JbGGR0bYyCbJQ1WD8yl4956I33xM762496pqYrWoTxZ3ifLI40ifRbP60gsX3183YI83ayjgoIYG8x462N7srIa6peN0Z55RG68175M7P561c0KD655p11o1363BwLVT0b032M6321mS81c5h5ZbSIkH3G83y9p431bR9JL9I8B55h8D6484Wz7Ai65Hi04qeYcvK4lIz6Q76sYPO";
2)密钥选择算法,在客户端和服务端都需要设计密钥选择算法
上面我们定义了一个密码表,这个密码表,就算被别人拿到了,他也不知道取哪些字符去加解密,这就起到了一个伪白盒的功能,那么别人不知道,程序怎么知道呢?我们这里就需要一个密钥选择算法,这里的密钥选择算法我只是介绍一个简单的例子,大家可以根据各家公司的要求要加强密钥选择算法的强度,对于密钥选择算法一定要用C写好后混淆后编译成so包供客户端和服务端使用,并且要做到so包能防反编译和防动态调试,防反编译和防动态调试可以通过外部的三方工具来实现,目前360加固和梆梆这块做的比较好,之前也测试过却是能够达到比较高的级别。下面介绍一个简单的基于上面密码表的密钥选择算法:
- 获取密码表,这里根据对称加密算法和密码表版本号获取本地的密码表;
2)根据时间戳和简单的字符串裁剪再根据100取模获取到密钥开始索引;
3)根据开始索引号从超长密码字符串中向后偏移8位获取到本次交易对称加密密钥(密码表长度总长度为800,开始索引是根据100取模,所以每次一定能取到一个8个字符长的对称密钥);
4)获取到本次交易的对称密钥后,客户端就可以通过这个密钥进行对称加密了,服务端采用一样的算法取本地密钥后去对称解密。
public String getDesKey(String version, long timeStamp) {
String keys = null;
LOGGER.info("请求时间戳{} ,使用版本{}", timeStamp, version);
keys = sysConfigService.getValue(BaseConstant.DESKEY + version);
int index = chooseByTimeStamp(timeStamp);
String key = keys.substring(index * 8, index * 8 + 8);
return key;
}
private int chooseByTimeStamp(long timeStamp) {
String enStr = PhoneMD5.GetMD5Code(String.valueOf(timeStamp));
String subStr = enStr.substring(enStr.length() - 2, enStr.length());
Integer resNum = Integer.parseInt(subStr, 16);
Integer result = resNum % 100;
return result;
}
因为这个方案中整体使用的是对称加密方案,只是选取密钥花费了一些时间,但是在内存中操作所以是非常快的,耗时可以忽略不计,所以并发性能也是能够保证的。
总结
移动网关的加密方案除了上面介绍的这些其实还有很多,笔者工作过的每家公司都有相关的不同安全方案,个人觉得目前性价比最高的方案当然是动态秘钥库方案,该方案已经能够防住绝大部分的黑客攻击,这个方案之前也可以好几家安全公司做渗透测试的朋友聊过,它们对这个方案也做过评估,并且也测试过,发现在做好密钥选择算法的防防编译和防动态调试的前提下,这个方案确实是可以保证性能的情况下还能提供足够的安全性。当然密钥选择算法大家也有很多选择,甚至可以使用OTP算法来实现,这里就不详细展开了,感兴趣的朋友可以留言一起讨论。