最近公司外包给别人做的一个APP项目上线了,拿到源码一看那代码质量真是一言难尽啊!
刚上线用户比较少倒也没出啥问题,不过随着用户慢慢变多,问题逐渐暴露出来了。
最严重的问题就是我们的APP与服务器的通信接口没有加密处理被人抓包了,有人非法请求我们的接口获取数据。
怎么处理这个问题呢?领导又把这个光荣而艰巨的任务分给了我,没办法只能硬着头皮上啊,经过几天摸索终于总结出了一套还算安全的APP与服务端通信的机制。
身份认证指只有经过合法授权的用户才能调用我们的接口,这里我们采用的是Token验证机制。
APP与服务端的整个通信过程如下:
Token验证机制解决了什么问题?
设想一个场景,我们检测到API接口正在被恶意调用,因为所有的接口都必须带Token才能调用,根据Token我们就能快速反查到对应的用户,所以Token验证机制可以帮助我们快速确定调用者的身份。
发现恶意调用,我们通过Token确定调用者的身份后可以采取Token失效、封禁帐号等措施来阻止恶意调用继续。
Token验证机制能防止抓包吗?
Token验证机制并不能防止APP被抓包,因为Token同样存在泄露的风险,恶意调用者只需要带上Token再请求我们的API接口同样还是能获取到数据。
因为APP与服务端都是明文通信,一抓包就能看到请求参数以及返回数据,所以为了防止被抓包我们必须要对数据进行加密处理。
数据加密的过程,就是对原来明文传输的数据按某种加密算法进行加密处理,使其成为不可读无意义的密文。
加密算法大体上可分为对称加密、非对称加密和散列算法等几种方式,后面我们的方案都会涉及到。
对称加密
对称加密是一种可逆的加密算法,其中“对称”的意思是加密过程和解密过程使用的是同一个密钥。
常见的对称加密算法有DES、3DES、AES、IDEA等。
使用对称加密算法一次完整的加解密过程为:
对称加密算法的特点
对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高。对称加密算法的安全性依赖于密钥,任何人只要拿到密钥就能对数据进行加解密操作。
由于参与通信的双方都需要持有密钥,任何一方的秘钥泄露,那么双方的通信将无安全性可言,所以怎么安全的保存和传递密钥是使用对称加密最需要关注的问题。
非对称加密
顾名思义非对称加密指的是加密过程和解密过程使用不同的密钥,非对称加密算法需要一对密钥(公钥和私钥),公钥用来加密数据、私钥用来解密数据。
常见的非对称加密算法有RSA、ECC、ElGamal等。
非对称加密一次完成的加解密过程为:
非对称加密算法特点
非对称加密算法使用公钥加密、私钥解密,私钥不需要公开传输所以安全性较高。同时私钥可以对数据进行签名、公钥可以用来验证签名,可以解决中间人攻击和信息被篡改的问题。
由于加解密过程使用不同的密钥,所以对大量数据进行加解密运算的话速度是比较慢的,通常情况下非对称加密算法只适合对少量数据进行加解密操作。
对称加密算法运算速度快但安全性不足、非对称加密算法安全性高但运算速度慢,那么我们的数据加密方案采用哪种加密算法呢?
既然两种加密算法都有优缺点,那我们可以将两者结合一下:用对称加密算法加解密数据这样可以保证运算速度,用非对称加密算法加密对称加密算法的密钥这样可以兼顾密钥的安全性。
数据加密之后再进行通信虽然抓包之后看不到明文数据了,但是这并不能阻止不怀好意之人发起重放攻击。
拦截到请求之后只需再原样发送该请求到服务端就可以发起重放攻击,如果接口内有一些查库之类的比较耗性能的逻辑,那么在短时间内发起大量重放攻击的话将会直接导致服务端崩溃。
怎么解决这个问题?
道理其实很简单,我们只需要保证请求只能被正确处理一次即可,这里我们采用时间戳+随机字符串的解决方案。
时间戳
我们在发送的数据里加入当前的时间戳,服务端在收到请求数据后首先取出时间戳与服务器当前时间进行比较,如果两者相差超过一定时间(比如5分钟),那么我们就认为本次请求超时,直接拒绝执行或返回错误就可以。
随机字符串
我们在发送的数据中加入一个随机生成的字符串,服务端在收到请求数据后首先在缓存中查找该字符串,如果在缓存中找到则认为这是一次重复请求直接拒绝处理,否则将该字符串加入缓存并继续执行正确逻辑。
在请求中加入时间戳与随机字符串之后,服务端收到请求后会首先对时间戳和随机字符串进行校验,校验通过才会执行正常的业务处理逻辑。
为了防重放攻击,我们在数据中加入了时间戳与随机字符串,但是别人在拦截到我们的请求之后也可以对时间戳和随机字符串进行篡改,面对这种情况服务端要怎么分辨呢?
为了防止数据在传输过程中被篡改,我们引入数字签名机制。
信息摘要算法
信息摘要算法(或者叫散列算法)是一种不可逆算法,任意长度的明文数据经过信息摘要算法计算后都可以得出一个固定长度的值(签名)。
常见的信息摘要算法有MD5、SHA-1等。
详细的签名过程:
为什么进行信息摘要计算要“加盐”?
举个例子就明白了,比如说123经过MD5计算后的签名值是abc,那么就会产生123->abc这样的对应关系,看到签名值abc我就能反查到原值为123。如果有人收集并保存了足够多的这种对应关系,那么就有可能从签名值反推出原值。
这个时候加盐操作就派上了用场,首先我们生成一个加盐值qwe,这个加盐值qwe并不会在网络传输,只有通信双方自己知道。
我们不直接计算123的签名值,我们将加盐值附加到123的后面得到123qwe,接着我们对123qwe进行MD5计算得到一个不一样的签名值def。
所以说即使原值一样,但只要加盐值不同那么最后得到签名值就不一样,这样也就无法从签名值反推出原值了。
因为我主要搞Java开发,所以就用Java语言实现了一套加解密方案,对称加密采用AES算法、非对称加密采用RSA算法,信息摘要算法采用MD5算法。
完整代码执行流程:
下面分步骤进行详细介绍:
准备工作:APP与服务端各自生成一对RSA密钥对(公钥和私钥),公钥给到对方、私钥各自私密保存;
APP发送加密数据流程
1、生成一个随机的AES算法密钥;
2、使用服务端的RSA公钥对AES密钥明文进行加密得到AES密钥密文;
3、对参数明文进行AES加密得到参数密文;
4、生成当前请求时间的时间戳;
5、为该次请求生成一个随机字符串;
6、将参数密文、时间戳、随机字符串和AES密钥密文进行MD5计算得到md5值;
7、使用APP自己的RSA私钥对md5值进行签名得到签名值;
8、将参数密文、时间戳、随机字符串、AES密钥密文和签名值一起发送到服务端;
服务端解密数据流程
1、校验时间戳与服务器当前时间的差值是否在合理的区间,超过则认为该次请求超时;
2、校验随机字符串是否已经在缓存中,如果已经在缓存中说明该次请求为重复请求,否则将该字符串加入缓存;
3、从收到的数据中取出参数密文、时间戳、随机字符串和AES密钥密文进行MD5计算得到md5值;
4、使用APP的RSA公钥对计算得到的md5值和请求数据中的签名值进行验证,签名验证通过则说明请求数据没有被篡改;
5、服务端使用自己的RSA私钥解密AES密钥密文得到AES密钥明文;
6、使用AES密钥明文对参数密文进行AES解密操作得到参数明文;
7、拿到参数明文之后进行正常的业务处理逻辑;
8、服务端数据需要经过同样的加密操作之后才能返回给APP;
APP与服务端肯定是要使用HTTPS协议进行通信的,再搭配上RAS+AES混合加密算法以及数字签名机制,相信这套方案在绝大部分情况下是可以保证通信及数据安全的。
当然了,不排除APP有被人破解的可能,这种情况下任何加密机制都是白搭。但是不能说我们的加密机制就没用了,我们只需要将破解我们APP的成本提高到一定程度就可以了。
这个道理其实就跟门锁一样,市面上绝大部分门锁只要有时间都可以被开锁的人打开,那你能说门锁就没有存在的意义了吗?