【如何设计安全可靠的开放接口】系列
1. 如何设计安全可靠的开放接口—之Token
2. 如何设计安全可靠的开放接口—之AppId、AppSecret
3. 如何设计安全可靠的开放接口—之签名(sign)
4. 如何设计安全可靠的开放接口【番外篇】—关于MD5应用的介绍
5. 如何设计安全可靠的开放接口—还有哪些安全保护措施
6. 如何设计安全可靠的开放接口—对请求参加密保护
7. 如何设计安全可靠的开放接口【番外篇】— 对称加密算法
前言
在提到对于开放接口的安全设计时,一定少不了对于摘要算法的应用(MD5算法是其实现方式之一),在接口设计方面它可以帮助我们完成数据签名的功能,也就是说用来防止请求或者返回的数据被他人篡改。
本文我们单从安全的角度出发,看看到底哪些场景下的需求可以借助MD5的方式来实现。
密码存储
在一开始的时候,大多数服务端对于用户密码的存储肯定都是明文的,这就导致了一旦存储密码的地方被发现,无论是黑客还是服务端维护人员自己,都可以轻松的得到用户的账号、密码,并且其实很多用户的账号、密码在各种网站上都是一样的,也就是说一旦因为有一家网站数据保护的不好,导致信息被泄露,那可能对于用户来说影响的则是他的所有账号密码的地方都被泄露了,想想看这是多少可怕的事情(这点CSDN应该深入感触。。。)
所以,那应该要如何存储用户的密码呢?最安全的做法当然就是不存储,这听起来很奇怪,不存储密码那又如何能够校验密码,实际上不存储指的是不存储用户直接输入的密码。
如果用户直接输入的密码不存储,那应该存储什么呢?到这里,MD5就派上用场了,经过MD5计算后的数据有这么几个特点:1. 其长度是固定的、2. 其数据是不可逆的、3. 一份原始数据每次MD5后产生的数据都是一样的。
下面我们来实验一下
public static void main(String[] args) {
String pwd = "123456";
String s = DigestUtils.md5Hex(pwd);
System.out.println("第一次MD5计算:" + s);
String s1 = DigestUtils.md5Hex(pwd);
System.out.println("第二次MD5计算:" + s1);
pwd = "123456789";
String s3 = DigestUtils.md5Hex(pwd);
System.out.println("原数据长度变长,经过MD5计算后长度固定:" + s3);
}
第一次MD5计算:e10adc3949ba59abbe56e057f20f883e
第二次MD5计算:e10adc3949ba59abbe56e057f20f883e
原数据长度变长,经过MD5计算后长度固定:25f9e794323b453885f5181f1b624d0b
有了这样的特性后,我们就可以用它来存储用户的密码了。
public static Map<String, String> pwdMap = Maps.newConcurrentMap();
public static void main(String[] args) {
// 一般情况下,用户在前端输入密码后,向后台传输时,就已经是经过MD5计算后的数据了,所以后台只需要直接保存即可
register("1", DigestUtils.md5Hex("123456"));
// true
System.out.println(verifyPwd("1", DigestUtils.md5Hex("123456")));
// false
System.out.println(verifyPwd("1", DigestUtils.md5Hex("1234567")));
}
// 用户输入的密码,在前端已经经过MD5计算了,所以到时候校验时直接比对即可
public static boolean verifyPwd(String account, String pwd) {
String md5Pwd = pwdMap.get(account);
return Objects.equals(md5Pwd, pwd);
}
public static void register(String account, String pwd) {
pwdMap.put(account, pwd);
}
MD5后就安全了吗?
目前为止,虽然我们已经对原始数据进行了MD5计算,并且也得到了一串唯一且不可逆的密文,但实际上还远远不够,不信,我们找一个破解MD5的网站试一下!
我们把前面经过MD5计算后得到的密文查询一下试试,结果居然被查询出来了!
之所以会这样,其实恰好就是利用了MD5的特性之一:一份原始数据每次MD5后产生的数据都是一样的。
试想一想,虽然我们不能通过密文反解出明文来,但是我们可以直接用明文去和猜,假设有人已经把所有可能出现的明文组合,都经过MD5计算后,并且保存了起来,那当拿到密文后,只需要去记录库里匹配一下密文就能得到明文了,正如上图这个网站的做法一样。
对于保存这样数据的表,还有个专门的名词:彩虹表,也就是说只要时间足够、空间足够,也一定能够破解出来。
加盐
正因为上述情况的存在,所以出现了加盐的玩法,说白了就是在原始数据中,再掺杂一些别的数据,这样就不会那么容易破解了。
String pwd = "123456";
String salt = "wylsalt";
String s = DigestUtils.md5Hex(salt + pwd);
System.out.println("第一次MD5计算:" + s);
第一次MD5计算:b9ff58406209d6c4f97e1a0d424a59ba
你看,简单加一点内容,破解网站就查询不到了吧!
攻防都是在不断的博弈中进行升级,很遗憾,如果仅仅做成这样,实际上还是不够安全,比如攻击者自己注册一个账号,密码就设置成1
。
String pwd = "1";
String salt = "wylsalt";
String s = DigestUtils.md5Hex(salt + pwd);
System.out.println("第一次MD5计算:" + s);
第一次MD5计算:4e7b25db2a0e933b27257f65b117582a
虽然要付费,但是明显已经是匹配到结果了。
所以说,无论是密码还是盐值,其实都要求其本身要保证有足够的长度和复杂度,这样才能防止像彩虹表这样被存储下来,如果再能定期更换一个,那就更安全了,虽说无论再复杂,理论上都可以被穷举到,但越长的数据,想要被穷举出来的时间则也就越长,所以相对来说也就是安全的。
数字签名
摘要算法另一个常见的应用场景就是数字签名了,这点我们在 如何设计安全可靠的开放接口—之签名(sign)文章中有具体应用。
大致流程,百度百科也有介绍