散列算法進行數據驗證與加密

散列算法進行數據驗證與加密

 

散列算法

散列是信息的提煉,通常其長度要比信息小得多,且爲一個固定長度。加密性強的散列一定是不可逆的,這就意味着通過散列結果,無法推出任何部分的原始信息。任何輸入信息的變化,哪怕僅一位,都將導致散列結果的明顯變化,這稱之爲雪崩效應。散列還應該是防衝突的,即找不出具有相同散列結果的兩條信息。具有這些特性的散列結果就可以用於驗證信息是否被修改。

 

單向散列函數一般用於產生消息摘要,密鑰加密等,常見的有:

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