目录
原理
解决方案
重放攻击的基本原理就是把以前窃听到的数据原封不动地重新发送给接收方
首先,常见的解决方案就是在请求报文里面加上时间戳,并参与加签。
当接收方收到报文,经过验签之后。
首先第一个事儿就是拿着请求中的时间戳字段和本地时间做个对比。
如果时间误差在指定时间,比如 60 秒内,那么认为这个请求是合理的,程序可以继续处理。
为什么要有一个时间容错范围,能理解吧?
因为报文的传输、解密、验签是需要时间,不能假设我这一秒发出去,下一秒服务端就收到了。
所以,得有时间容错范围。
但是这个容错范围又带来了另外一个问题。
不能完全避免重放攻击。
至少时间容错范围内,比如 60 秒,重发过来的请求,服务端认为是有效的。
那么怎么办呢?
我们在请求报文里面加个随机串,然后让它参与加签。
接受方收到报文,验签之后,把随机串拿出来,来判断一下这个随机串是否已经处理过了。比如判断一下是否存在于 Redis 里面。
当请求再次重放过来的时候,发现这个随机串已经被用过了呀,不处理了。
在这个情况下,随机串就得保证唯一性了,还得历史全局唯一。
因为你指不定哪天就收到一个几天前的被重放过来的请求。
确实是解决了请求重放的问题,但是弊端也很明显:历史全局唯一。
我还得存储下来,而且存储的数据量还会越来越大,是不是有点麻烦了?
确实麻烦了。
这个思想就和用全局唯一流水号去保证接口幂等性很像了。
时间戳的问题是有一定的时间容错窗口,这个时间窗口内的重放攻击是防不住的。
随机串的问题是要保证历史全局唯一,保存随机串成了一个麻烦的事情。
那么当我们把这两个方案揉在一起的时候,神奇的事情就发生了:
我只需要保证时间窗口内的生成的随机串不重复就行。
而且假设时间窗口为 60 秒,我们用 Redis 来记录出现过的随机串,那么这个串在后台的超时时间设置为 60 秒就行。
一般来说这个时间窗口都不会太长了,我对接过这么多各种各样的渠道,见过最长的也就 5 分钟。
保证 5 分钟内生成的两个随机串不重复,这个需求比保证实现一个历史全局唯一的流水号容易实现多了吧?
另外,最关键的一句话一定要说:时间戳和随机串得参与到加签逻辑中去。
这个很好理解吧?
接受方看报文是否被篡改,看的就是签名是否能匹配上。
而签名的结果是和参与签名的字段的值有直接关系的。
要是你时间戳和随机串不参与加签,那么任意修改时间戳或者随机串,都不会引起签名的变化,那不白忙活一场吗?
中间人咔一下拦截到请求,发现有时间戳和随机串,正准备放弃的时候,想着死马当做活马医,把随机串一改,又扔给接收方了。
结果收到正确的响应了。