发表于: 2017-06-24 22:50:17
2 1254
今天完成的事情:
了解注解@RequestParm,加盐,MessageDigest,SecureRandom,弱伪随机数.
搞了个加密解密的程序.运行成功
明天计划的事情:
写加盐,搞懂各个步骤的含义
遇到的问题:
无
收获:
1:
注解:@RequestParm
在springMVC后台控制层获取参数的方式主要有两种:
1:request.getParameter("name")
2:用注解@RequestParm
2:
加盐
为什么要加盐(salt)?
如果直接对密码进行散列,那么黑客可以通过获得这个密码散列值,然后通过查散列值字典(如MD5密码破解网站),得到某用户密码.加盐可以一定程度上解决这一问题.
加盐是什么?
加盐方法,就是加点"佐料"
当用户首次提供密码时(通常为注册时),由系统自动往这个密码里散一些"佐料",然后再散列.
当用户登录时,系统为用户提供的代码撒上同样的"佐料",然后散列,再对比散列值,确定密码是否正确.
"佐料"被称为"salt值".这个值是由系统随机生成的,并且只有系统知道.
加盐值是随机生成的一组字符串,可以包括随机的大小写字母,数字,字符,位数可以根据要求而不一样,使用不同的加盐值产生的最终密文是不一样的
加盐的作用:
不同的两个用户用了一个密码,由于系统为它们生成的salt值不同,它们的散列值也是不同的.即使黑科可以通过自己的密码和自己生成的散列值来找具有特定密码的用户,但这个几率太小了,因为密码和salt值都得和黑客使用的一样才行(个人理解这也解释了为什么不要用123456等简单密码的原因,因为用的人多了总有和黑客同样salt值的人)
加盐的流程 :
用户注册时:
1:用户输入账号与密码
2:系统为用户生成盐值
3:系统将salt值和密码连接到一起
4:对连接后的值进行散列,得到hash值
5:将hash值1和salt值分别放到数据库中
用户登录时
1:用户输入账号和密码
2:系统通过用户名找到与之对应的hash值和salt值
3:系统将salt值和密码连接到一起
4:对连接后的值进行散列,得到hash值2(注意是即时运算出来的值)
5:比较hash值1和2是否相等,相等则表示密码正确,否则表示密码错误
有时候,为了减轻开发压力,程序员会统一使用一个salt值(储存在某个地方),而不是每个用户都生成私有的salt值
注:散列即hash
代码中使用加盐值:
1:首先得到明文的hash值
2:进行计算获取md5明文hash值
3:随机生成加盐值并插入
4:md5插入加盐值得到的hash
5:得到最终密文
如何破解出带有加盐值的密文?
因为像windows hash(未加密),非加盐值都可以通过大型的密码表(如彩虹表)进行对比解密,所以相对而言相当轻松,而带有加盐值的密文就复杂得多.
现在的MD5表大概是260+G,如何加盐值的可能性有1w个,那么密码表的应该是MD5*1w,就可以解密出原md5表能够解密的密码了.一些网站也提供了对应的salt解密,但是效果并不是非常好.毕竟md5是不可逆的,带入随机值解密出最终密码的可能性则更低
3:
MessageDigest
java.security.MessageDigest类用于为应用程序提供信息摘要算法功能.即用于生成散列码.
MessageDigest类也是一个工厂类,其构造器是受保护的,不允许直接使用new MessageDigist()来创建对象,必须通过其静态方法getInstance()生成MessageDigest对象.
其中传入的参数指定计算消息摘要所使用的算法,常用的有"MD5","SHA"等.
算法名不区分大小写,例如,以下所有调用都是相等的
MessageDigest.getInstance("SHA");
MessageDigest.getInstance("sha");
MessageDigest.getInstance("SHa");
public static MessageDigest geInstance(String algorithm,String provider)
调用getInstance将返回已初始化过的MessageDigest对象,因此,它不需要进一步的初始化.
例子:MessageDigest类提供计算消息摘要(即生成散列码)的方法,首先生成对象,执行update()方法可将原始数据传递给该对象,然后执行digest()方法即可得到消息摘要.
1:生成MessageDigest对象
MessageDigest m=MessageDigest.getInstance("MD5");
2:传入需要计算的字符串
m.update(x.getBytes("UTF8"));
其中,x为需要计算的字符串,update传入的参数是字节类型或字节类型数组,对于字符串,需要先使用getBytes()方法生成字符串数组.
3:计算消息摘要
byte[] s[]=m.digest();
执行MessageDigest对象的digest()方法完成计算,计算结果通过字节类型的数组返回
4:处理计算结果
(非必要):可将计算结果(byte数组)转换成字符串
static String convertToHexString(byte data[]){
StringBuffer strBuffer=new StringBuffer();
for(int i=0,i<data.length,i++){
strBuffer.append(Integer.toHexString(0xff & data[i]));
}
return strBuffer.toString();
}
4:SecureRandom
SecureRandom是强随机数生成器,主要应用的场景为:用于安全目的的数据数,例如生成秘钥或者会话标示(session ID).
使用SecureRandom这样的强随机数生成器会降低出问题的风险,相比弱随机数生成器安全.
扩展:伪随机数:
真正的随机数是使用物理现象产生的,比如掷钱币,掷骰子,转轮等,这样的随机数发生器叫物理性随机数发生器,虽然可靠,但是难以使用计算机来实现,它们的缺点是技术要求比较高.
在实际应用中使用伪随机数就足够了,这些数列看似是随机的数,实际它们是通过一个固定的,可以重复的计算方法产生的.
计算机产生的随机数有很长的周期性,它们不真正地随机,因为它们实际上是可以计算出来的,但是它们具有类似随机数的统计特征,这样的发生器叫伪随机数发生器.
伪随机数有强弱之分,强随机数一般指相对难以猜解的随机数.比如服务器占用的内存数量作为随机数,而弱伪随机数指相对容易猜解的随机数,典型例子是当前的时间戳.
举个例子:
强伪随机数RNG实现java.security.SecureRandom类,该类使用临时文件夹中大小,线程休眠时间等的值作为随机数种子
弱伪随机数实现PRNG java.util.Random类,默认使用当前时间作为种子,并且采用线性同余法计算下一个随机数
弱随机数的不良后果:
1 2 3 4 5 | Random r = new Random(10000); //10000作为seed,默认使用当前时间作为seed for (int i=0;i<5;++i) { System.out.println(r.nextInt()); } |
以上这段代码,怎么跑都会打印出一=以下结果
-498702880
-858606152
1942818232
-1044940345
-858606152
1942818232
-1044940345
1588429001
这是一个稳定的结果。这就是由于线性同余法带来的后果。那么,在我们的程序,如果使用Random类生成一个随机数,事实上很容易通过上一个产生的随机数来推断下一个随机数。
接下来,我们来分析一些常用的随机数应用场景,并且分析一下出错的原因。
很多账号体系都有一个找回密码功能,找回密码时给手机发送的验证码,给邮箱发送的验证码或者重置密码链接,以上种种都使用了伪随机数。
下面以某网站通过邮箱重置密码链接找回密码为例,通过页面操作之后,会在密保邮箱中发现以下重置密码的链接:
http://www.xxx.com/findpwd/setpwdfromemail?vc=2ABB36620A927644607491393EF0D5EF&u=xxx%40gmail.com
通过分析,我们发现,vc=2ABB36620A927644607491393EF0D5EF是一串md值,解开之后值是1339744000,是个unix时间戳!那么可以猜测,用户取回密码时产生一个时间戳与帐号绑定,那么修改这个用户密码只需知道这个时间戳就可以。况且,一般服务器时间都是跟标准时间同步,也就是说unix时间戳是可以预测的。我们可以通过暴力破解遍历当前标准时间+一个网络延迟来进行暴力破解。
伪随机数的应用里,验证码是另外一种典型应用。对于安全而言,验证码是一个非常有效的保护机制和人机区分机制,可以保障口令不被暴力破解,可以防止刷票,刷屏,重复提交恶意数据等。除了作为验证码之外,类似的应用还存在于一些活动的优惠券或者兑换码,如果兑换码设计不当,很容易被破解而破坏活动的公平性。
总结一下,使用随机数的场景需要注意以下几点:
不要使用时间戳作为随机数
保证不同用处的随机数使用不同的种子
接下来,我们来分析一些常用的随机数应用场景,并且分析一下出错的原因。
很多账号体系都有一个找回密码功能,找回密码时给手机发送的验证码,给邮箱发送的验证码或者重置密码链接,以上种种都使用了伪随机数。
下面以某网站通过邮箱重置密码链接找回密码为例,通过页面操作之后,会在密保邮箱中发现以下重置密码的链接:
http://www.xxx.com/findpwd/setpwdfromemail?vc=2ABB36620A927644607491393EF0D5EF&u=xxx%40gmail.com
通过分析,我们发现,vc=2ABB36620A927644607491393EF0D5EF是一串md值,解开之后值是1339744000,是个unix时间戳!那么可以猜测,用户取回密码时产生一个时间戳与帐号绑定,那么修改这个用户密码只需知道这个时间戳就可以。况且,一般服务器时间都是跟标准时间同步,也就是说unix时间戳是可以预测的。我们可以通过暴力破解遍历当前标准时间+一个网络延迟来进行暴力破解。
伪随机数的应用里,验证码是另外一种典型应用。对于安全而言,验证码是一个非常有效的保护机制和人机区分机制,可以保障口令不被暴力破解,可以防止刷票,刷屏,重复提交恶意数据等。除了作为验证码之外,类似的应用还存在于一些活动的优惠券或者兑换码,如果兑换码设计不当,很容易被破解而破坏活动的公平性。
总结一下,使用随机数的场景需要注意以下几点:
不要使用时间戳作为随机数
保证不同用处的随机数使用不同的种子
对于安全性要求高的随机数,使用强伪随机数产生
SecureRandom继承于random
两者常见的两个方法如下:
获得一个随机的int数:
1 2 3 4 5 6 7 8 9 10 11 12 | final protected int next(int numBits) { int numBytes = (numBits + 7) / 8; byte b[] = new byte[numBytes]; int next = 0; nextBytes(b); for (int i = 0; i < numBytes; i++) { next = (next << 8) + (b[i] & 0xFF); } return next >>> (numBytes * 8 - numBits); } |
获得随机的字节数组:
1 2 3 4 | synchronized public void nextBytes(byte[] bytes) { secureRandomSpi.engineNextBytes(bytes); } |
评论