1. 程式人生 > >SHA與PBKDF2加密的實現與應用

SHA與PBKDF2加密的實現與應用

雜湊演算法並不是一種加密演算法。不能對使用者資訊的保護。因為雜湊演算法是單向的,可以將任何大小的資料轉化為定長的“指紋”,而且無法被反向計算。

但Hash演算法常用於對口令的儲存上。另外,即使資料來源只改動了一丁點,雜湊的結果也會完全不同。這樣的特性使得它非常適合用於儲存密碼,因為我們需要加密後的密碼無法被解密,同時也能保證正確校驗每個使用者的密碼。

例如,使用者登入網站需要通過使用者名稱和密碼來進行驗證。如果網站後臺直接儲存使用者的口令明文,一旦資料庫發生洩漏,後果不堪設想。

然而,由於有時候使用者設定的口令的強度不夠,只是一些常見的字串,如password、123456等。有人專門蒐集這些常見的口令,計算對應的Hash值,製成字典。這樣通過Hash值可以快速的查到原始口令。這一型別以空間換時間的攻擊方法包括字典攻擊和彩虹攻擊(只儲存一條Hash鏈的首尾值,相對字典攻擊可以節省儲存空間)等。

為了防範這一類攻擊,一般採用加鹽值(salt)的方法。這樣儲存的不是口令明文的Hash值,而是口令再加上一段隨機字串(即“鹽”)之後的Hash值。鹽是一個新增到使用者的密碼雜湊過程中的一段隨機序列。這個機制能夠防止通過預先計算結果的彩虹表破解。

每個使用者都有自己的鹽,這樣的結果就是即使使用者的密碼相同,通過加鹽後雜湊值也將不同。

為了校驗密碼是否正確,我們需要儲存鹽值。通常和密碼雜湊值一起存放在賬戶資料庫中,或者直接存為雜湊字串的一部分。

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

public class PasswordEncryption {

    /**
     * MD5和SHA1已被破解,SHA-1已被證明不具備“強抗碰撞性”
     * 一般推薦至少使用SHA2-256或更安全的演算法。
     * PBKDF2WithHmacSHA256
     * PBKDF2WithHmacSHA384
     * PBKDF2WithHmacSHA512
     */
    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";

    /**
     * 鹽的長度
     */
    public static final int SALT_BYTE_SIZE = 32 / 2;

    /**
     * 生成密文的長度
     */
    public static final int HASH_BIT_SIZE = 128 * 4;

    /**
     * 迭代次數
     */
    public static final int PBKDF2_ITERATIONS = 1000;

    /**
     * 對輸入的密碼進行驗證
     *
     * @param attemptedPassword
     *            待驗證的密碼
     * @param encryptedPassword
     *            密文
     * @param salt
     *            鹽值
     * @return 是否驗證成功
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static boolean authenticate(String attemptedPassword, String encryptedPassword, String salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 用相同的鹽值對使用者輸入的密碼進行加密
        String encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt);
        // 把加密後的密文和原密文進行比較,相同則驗證成功,否則失敗
        return encryptedAttemptedPassword.equals(encryptedPassword);
    }

    /**
     * 生成密文
     *
     * @param password
     *            明文密碼
     * @param salt
     *            鹽值
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static String getEncryptedPassword(String password, String salt) throws NoSuchAlgorithmException,
            InvalidKeySpecException {

        KeySpec spec = new PBEKeySpec(password.toCharArray(), fromHex(salt), PBKDF2_ITERATIONS, HASH_BIT_SIZE);
        SecretKeyFactory f = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
        return toHex(f.generateSecret(spec).getEncoded());
    }

    /**
     * 通過提供加密的強隨機數生成器生成鹽
     *
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static String generateSalt() throws NoSuchAlgorithmException {
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[SALT_BYTE_SIZE];
        random.nextBytes(salt);

        return toHex(salt);
    }

    /**
     * 十六進位制字串轉二進位制字串
     *
     * @param   hex         the hex string
     * @return  the hex string decoded into a byte array      
     */
    private static byte[] fromHex(String hex) {
        byte[] binary = new byte[hex.length() / 2];
        for (int i = 0; i < binary.length; i++) {
            binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
        }
        return binary;
    }

    /**
     * 二進位制字串轉十六進位制字串
     *
     * @param   array       the byte array to convert
     * @return  a length*2 character string encoding the byte array      
     */
    private static String toHex(byte[] array) {
        BigInteger bi = new BigInteger(1, array);
        String hex = bi.toString(16);
        int paddingLength = (array.length * 2) - hex.length();
        if (paddingLength > 0)
            return String.format("%0" + paddingLength + "d", 0) + hex;
        else
            return hex;
    }

    public static void main(String[] args) {
        String password = "test";
        String salt;
        String ciphertext;
        try {
            salt = PasswordEncryption.generateSalt();
            ciphertext = PasswordEncryption.getEncryptedPassword(password, salt);
            boolean result = PasswordEncryption.authenticate(password, ciphertext, salt);

            System.out.println(password + "  " + password.length());
            System.out.println(salt + "  " + salt.length());
            System.out.println(ciphertext + "  " + ciphertext.length());
            if (result) {
                System.out.println("succeed");
            } else {
                System.out.println("failed");
            }
        } catch (NoSuchAlgorithmException e) {
            System.out.println("NoSuchAlgorithmException");
        } catch (InvalidKeySpecException e) {
            System.out.println("InvalidKeySpecException");
        }
    }

}

--------------------------程式碼實現來載地址--------------------------------------
作者:阿牛哞了一聲 
原文:https://blog.csdn.net/u014375869/article/details/46773995 
----------------------------------------------------------------------------------------