今年五月份去融贝网, 猎豹移动面试的时候, 被问到 API 安全加密的问题, 很惭愧, 自己回答的很不全面. 自己也知道那是没有彻底弄明白原理. 然后, 8 月份的时候, 上家公司新项目启动时也和同事探讨过, 然后就去彻彻底底了解了一下, 趁着热乎劲儿还没过去总结出来吧.
安全是相对的, 下面是根据安全级别分析. 我用简单的 PHP 代码演示出来, 只是演示, 代码写的不严谨请轻喷啊!!!
1, 完全开放的接口
这种我们称之为 "裸奔 API", 只要连上网就能调用, 不存在安全, 一般只能查询, 不能执行增, 删, 改的操作.
- <?PHP
- // 接口对外开放
- public function noSecure($string)
- {
- $data = DB('table')->where('string', $string)->get();
- if(!is_null($data)) {
- return $data;
- }
- }
2, 接口参数加密 (基础加密)
这种加密方式, 只想让特定的调用方使用. 好比你把这些调用的人叫到一个小屋子, 给他们宣布说我这里有个接口只打算给你们用, 我给你们每人一把钥匙, 你们用的时候拿着这把钥匙即可.
这把钥匙就是我上文说到的参数加密规则, 有了这个规则就能调用.
这有安全问题啊, 这里面的某个成员如果哪个不小心丢了钥匙或者被人窃取, 掌握钥匙的人是不是也可以来掉用接口了呢? 而且他可以复制很多钥匙给不明不白的人用.
相当于有人拿到了你的请求链接, 如果业务没有对链接唯一性做判断 (实际上业务逻辑通常不会把每次请求的加密签名记录下来, 所以不会做唯一性判断), 就会被重复调用, 有一定安全漏洞, 怎么破? 先看这个场景的代码, 然后继续往下看!
- <?PHP
- // 接口加密
- public function secureBySign($string, $appKey, $sign)
- {
- // 检验签名是否合法
- $string = $_POST("string");
- $appKey = $_POST("appKey");
- $sign = $_POST("sign");
- $signHelper = new SignHelper();
- $currentSign = $signHelper->getSign($appKey, $string, ......);
- if($sign !== $currentSign) {
- return "签名不合法";
- }
- $data = DB('table')->where('string', $string)->get();
- if(!is_null($data)) {
- return $data;
- }
- }
3, 接口参数加密 + 接口时效性验证 (一般达到这个级别已经非常安全了)
继上一步, 你发现有不明不白的人调用你的接口, 你很不爽, 随即把真正需要调用接口的人又叫来, 告诉他们每天给他们换一把钥匙. 和往常一样, 有个别伙伴的钥匙被小偷偷走了, 小偷煞费苦心, 经过数天的踩点观察, 准备在一个月黑风高的夜晚动手. 拿出钥匙, 捣鼓了半天也无法开启你的神圣之门, 因为小偷不知道你天天都在换新钥匙.
小偷不服, 经过一段时间琢磨, 小偷发现了你们换钥匙的规律. 在一次获得钥匙之后, 不加思索, 当天就动手了, 因为他知道他手里的钥匙在第二天你更换钥匙后就失效了.
结果, 小偷如愿. 怎么破? 先看这个场景的代码, 然后继续往下看!
- <?PHP
- // 接口加密并根据时间戳判断有效性
- public function secureBySignExpired($string, $appKey, $sign, $timestamp)
- {
- // 判断请求是否过期 --- 假设过期时间是 20 秒
- $requestTime = GetDateTimeByTicks($timestamp);
- if(($requestTime + 20) <$_SERVER["REQUEST_TIME"]) {
- return "接口过期";
- }
- // 检验签名是否合法
- $string = $_POST("string");
- $appKey = $_POST("appKey");
- $sign = $_POST("sign");
- $signHelper = new SignHelper();
- $currentSign = $signHelper->getSign($appKey, $string, ......);
- if($sign !== $currentSign) {
- return "签名不合法";
- }
- $data = DB('table')->where('string', $string)->get();
- if(!is_null($data)) {
- return $data;
- }
- }
4, 接口参数加密 + 时效性验证 + 私钥 (达到这个级别安全性固若金汤)
继上一步, 你发现道高一尺魔高一丈, 仍然有偷盗事情发生. 咋办呢? 你打算下血本, 给每个人配一把钥匙的基础上, 再给每个人发个暗号, 即使钥匙被小偷弄去了, 小偷没有暗号, 任然无法如愿. 即使小偷真正的如愿, 这样也很容易定位是谁的暗号泄漏问题, 找到问题根源, 只需要给当前这个人换下钥匙就行了, 不用大动干戈.
但这个并不是万无一失的, 因为钥匙和暗号毕竟还有可能被小偷搞到. 代码如下:
- <?PHP
- // 接口加密并根据时间戳判断有效性而且带着私有 key 校验
- // 在调用接口时动态从库里取, 每个调用方在调用时带上他的 appSecret, 调用方一般把自己的 appSecret 放到网站或 App 配置文件中
- public function secureBySignExpiredKeySecret($string, $appKey, $sign, $timestamp)
- {
- // 判断请求是否过期 --- 假设过期时间是 20 秒
- $requestTime = GetDateTimeByTicks($timestamp);
- if(($requestTime + 20) <$_SERVER["REQUEST_TIME"]) {
- return "接口过期";
- }
- // 根据 appkey 查库获取 appSecret 值
- $appSecret = DB('table')->where('appKey', $appKey)->get('appSecret');
- // 检验签名是否合法
- $string = $_POST("string");
- $appKey = $_POST("appKey");
- $sign = $_POST("sign");
- // 把 appSecret 加入进行加密
- $signHelper = new SignHelper();
- $currentSign = $signHelper->getSign($appKey, $appSecret, $string, ......);
- if($sign !== $currentSign) {
- return "签名不合法";
- }
- $data = DB('table')->where('string', $string)->get();
- if(!is_null($data)) {
- return $data;
- }
- }
5, 接口参数加密 + 时效性验证 + 私钥 + Https(我把这个级别称之为金钟罩, 世间最安全莫过于此)
安全第五层, 在第四层的基础上加 HTTPS, 关于 HTTPS 怎么加, 可以自行去了解, 网上相关资料一堆.
关于加密算法可以参考: API 接口加密策略
来源: https://juejin.im/post/5bf10deb6fb9a049ae077b5b