散列是信息的提炼,通常其长度要比信息小得多,且为一个固定长度。加密性强的散列一定是不可逆的,这就意味着通过散列结果,无法推出任何部分的原始信息。任何输入信息的变化,哪怕仅一位,都将导致散列结果的明显变化,这称之为雪崩效应。散列还应该是防冲突的,即找不出具有相同散列结果的两条信息。具有这些特性的散列结果就可以用于验证信息是否被修改。
单向散列函数一般用于产生消息摘要,密钥加密等,常见的有:
MD5(Message Digest Algorithm 5):
MD5的全称是Message-Digest Algorithm 5(信息-摘要算法)。这种算法较为古老,由于MD5的弱点被不断发现以及计算机能力不断的提升,通过碰撞的方法有可能构造两个具有相同MD5的信息,使MD5算法在目前的安全环境下有一点落伍。 |
SHA(Secure Hash Algorithm):
安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。SHA家族的五个算法,分别是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,后四者有时并称为SHA-2。SHA-1在许多安全协议中广为使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被视为是MD5(更早之前被广为使用的散列函数)的后继者。但SHA-1的安全性如今被密码学家严重质疑。虽然至今尚未出现对SHA-2有效的攻击,它的算法跟SHA-1基本上仍然相似;因此有些人开始发展其他替代的散列算法。 |
散列算法在信息安全方面的应用主要体现在以下的3个方面: 文件校验、数字签名和鉴权协议。
虽然散列算法并不是加密算法,由于拥有不可逆的特性也常用于密码的加密保存。
相同的明文用同样的加密方法(如MD5)进行加密会得到相同的密文。
如用MD5的方式加密“123456”,你总会得到密文“E10ADC3949BA59ABBE56E057F20F883E”。
那么,当数据库信息泄漏时,如果你的密码设置的比较简单,对方是很容易猜到你的密码,或者通过彩虹表来破解你的密码。
因此,你需要在明文中添加干扰项-盐(Salt)。
对于只加密,但不解密的算法,如MD5,SHA1。我们需要把盐和密文都存在数据库中,用户输入密码时,我们把用户密码和盐组成新的明文,进行加密,然后得到密文,最后对比该密文是否与库中密文匹配。
在生成盐值时我们要注意一下几点来提高破解难度:
1、不要使用太短的盐值
如果盐值太短,攻击者可以构造一个查询表包含所有可能的盐值。
2、不要使用重复的盐值
每次哈希加密都使用相同的盐值是很容易犯的一个错误,这个盐值要么被硬编码到程序里,要么只在第一次使用时随机获得。这样加盐的方式是做无用功,因为两个相同的密码依然会得到相同的哈希值。攻击者仍然可以使用反向查表法对每个值进行字典攻击,只需要把盐值应用到每个猜测的密码上再进行哈希即可。
对于每个用户的每个密码,盐值都应该是独一无二的。每当有新用户注册或者修改密码,都应该使用新的盐值进行加密。并且这个盐值也应该足够长,使得有足够多的盐值以供加密。一个好的标准的是:盐值至少和哈希函数的输出一样长;盐值应该被储存和密码哈希一起储存在账户数据表中。
3、不要将盐值简单的添加在前后
由于往往盐与密文都是同时存储在数据库中的,所以很多情况都是盐与秘文同时泄露。所以加盐时最好不要过于简单的将盐直接加在明文前后。而是使用打散或多次插入的方式混入明文提高算法破解难度。
4、盐值应该使用基于加密的伪随机数生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)来生成。
CSPRNG和普通的随机数生成器有很大不同。物如其名,CSPRNG专门被设计成用于加密,它能提供高度随机和无法预测的随机数。我们显然不希望自己的盐值被猜测到,所以一定要使用CSPRNG。Java的java.security.SecureRandom类可以用来生成随机数。
MessageDigest是java.security提供的一个散列算法类。
java se 8中支持以下类型的散列算法:
Algorithm Name | Description |
---|---|
MD2 | The MD2 message digest algorithm as defined in RFC 1319. |
MD5 | The MD5 message digest algorithm as defined in RFC 1321. |
SHA-1 SHA-224 SHA-256 SHA-384 SHA-512 |
Hash algorithms defined in the FIPS PUB 180-4. Secure hash algorithms - SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 - for computing a condensed representation of electronic data (message). When a message of any length less than 2^64 bits (for SHA-1, SHA-224, and SHA-256) or less than 2^128 (for SHA-384 and SHA-512) is input to a hash algorithm, the result is an output called a message digest. A message digest ranges in length from 160 to 512 bits, depending on the algorithm. |
API:https://docs.oracle.com/javase/8/docs/api/index.html
import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* 散列算法(MessageDigest实现)
*/
public class HashAlgorithm {
public static void main(String[] args){
HashAlgorithm hashAlgorithm = new HashAlgorithm();
//生成盐值
String salt = hashAlgorithm.getSalt();
System.out.println("盐值:" + salt);
//使用明文生成散列值
String message = "ceshimima";
String hash = hashAlgorithm.getHash(message,salt,"SHA-512");
System.out.println("散列值:" + hash);
//验证明文与散列是否匹配
boolean b = hashAlgorithm.verification(message,salt,"SHA-512",hash);
if(b){
System.out.println("明文与散列匹配成功");
}else{
System.out.println("明文与散列匹配失败");
}
}
/**
* 生成盐值
* @return
*/
public String getSalt(){
//使用SecureRandom类生成伪随机数作为盐值
SecureRandom random = new SecureRandom();
byte bytes[] = new byte[20];
random.nextBytes(bytes);
String salt = Hex.encodeHexString(bytes);
return salt;
}
/**
* 给明文加盐
* @param message 明文
* @return
*/
private String addSalt(String message,String salt){
//明文加在盐值的中间,增加破解难度
StringBuilder saltb = new StringBuilder(salt);
saltb.insert(15,message);
return saltb.toString();
}
/**
* 生成散列
* @param message 明文
* @param salt 盐值
* @param algorithm 算法 MD5、SHA-1、SHA-256、SHA-512等
* @return
*/
public String getHash(String message,String salt,String algorithm){
//明文加盐
String salt_message = addSalt(message,salt);
try {
//使用MessageDigest生成散列
MessageDigest md = MessageDigest.getInstance(algorithm);
//添加明文
md.update(salt_message.getBytes());
//生成散列值
byte[] toChapter1Digest = md.digest();
return Hex.encodeHexString(toChapter1Digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/**
* 验证明文与散列是否匹配
* @param message 明文
* @param salt 盐值
* @param algorithm 算法 MD5、SHA-1、SHA-256、SHA-512等
* @param hash 散列
* @return
*/
public boolean verification(String message,String salt,String algorithm,String hash){
//使用明文重新生成散列与散列值比较
String hash1 = getHash(message,salt,algorithm);
boolean b = hash1.equals(hash);
return b;
}
}
执行结果
盐值:e4cf17d1437377256970dc80965f3c6e5e5d772f
散列值:d90cfa4a922b385e0c5f1816156b98895a0917334efd66379033ebb661ffbdd675fd860c634c41164965e8bf4e489563265404c955f528f3d81a3974ee997aed
明文与散列匹配成功
加盐使攻击者无法采用特定的查询表和彩虹表快速破解大量哈希值,但是却不能阻止他们使用字典攻击或暴力攻击。高端的显卡(GPU)和定制的硬件可以每秒进行数十亿次哈希计算,因此这类攻击依然可以很高效。为了降低攻击者的效率,我们可以使用一种叫做密钥扩展的技术。
这种技术的思想就是把哈希函数变得很慢,于是即使有着超高性能的GPU或定制硬件,字典攻击和暴力攻击也会慢得让攻击者无法接受。最终的目标是把哈希函数的速度降到足以让攻击者望而却步,但造成的延迟又不至于引起用户的注意。
密钥扩展的实现是依靠一种CPU密集型哈希函数。不要尝试自己发明简单的迭代哈希加密,如果迭代不够多,是可以被高效的硬件快速并行计算出来的,就和普通哈希一样。应该使用标准的算法,比如PBKDF2或者bcrypt。
这类算法使用一个安全因子或迭代次数作为参数,这个值决定了哈希函数会有多慢。对于桌面软件或者手机软件,获取参数最好的办法就是执行一个简短的性能基准测试,找到使哈希函数大约耗费0.5秒的值。这样,你的程序就可以尽可能保证安全,而又不影响到用户体验。
jBCrypt官网:http://www.mindrot.org/projects/jBCrypt/
import org.mindrot.jbcrypt.BCrypt;
/**
* 散列算法(BCrypt实现)
*/
public class BCryptHashAlgorithm {
public static void main(String[] args){
BCryptHashAlgorithm hashAlgorithm = new BCryptHashAlgorithm();
//使用BCrypt算法加密
String password = "ceshimima";
String ciphertext = hashAlgorithm.encryption(password);
System.out.println("密文:" + ciphertext);
//验证明文与密文是否匹配
boolean b = hashAlgorithm.verification(password, ciphertext);
if(b){
System.out.println("明文与密文匹配成功");
}else{
System.out.println("明文与密文匹配失败");
}
}
/**
* 加密方法
* @param password 密码明文
* @return 密码密文
*/
public String encryption(String password){
//创建盐值(log_rounds代表重复处理的轮次。轮次越多破解难度越大,但是效率也越低。
// 其复杂度是指数级递增,也就是传4的时候是2的4次方,默认传输为10的时候是2的10次方)
String salt = BCrypt.gensalt(12);
//生成密文
String hashed = BCrypt.hashpw(password,salt);
return hashed;
}
/**
* 验证方法
* @param password 密码明文
* @param ciphertext 密码密文
* @return 是否一致 true 一致 false 不一致
*/
public boolean verification(String password,String ciphertext){
//验证明文与密文是否匹配
//BCrypt的盐值是包含于密文之中的,所以不需要另外保存和传递盐值
boolean b = BCrypt.checkpw(password, ciphertext);
return b;
}
}
执行结果
密文:$2a$12$MpHWUewiJOHrLRyZAc5tReGFfo102cggHTKsqf4N/hFfr3XvMz8Oa
明文与密文匹配成功