散列算法进行数据验证与加密

散列算法进行数据验证与加密

 

散列算法

散列是信息的提炼,通常其长度要比信息小得多,且为一个固定长度。加密性强的散列一定是不可逆的,这就意味着通过散列结果,无法推出任何部分的原始信息。任何输入信息的变化,哪怕仅一位,都将导致散列结果的明显变化,这称之为雪崩效应。散列还应该是防冲突的,即找不出具有相同散列结果的两条信息。具有这些特性的散列结果就可以用于验证信息是否被修改。

 

单向散列函数一般用于产生消息摘要,密钥加密等,常见的有:

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个方面: 文件校验、数字签名和鉴权协议。

虽然散列算法并不是加密算法,由于拥有不可逆的特性也常用于密码的加密保存。

 

干扰项 - 盐(Salt)

相同的明文用同样的加密方法(如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

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

 

MessageDigest示例

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秒的值。这样,你的程序就可以尽可能保证安全,而又不影响到用户体验。

 

BCrypt示例

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
明文与密文匹配成功
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章