打赏
Fork me on GitHub

PBKDF2加密的实现

做过网站的人都知道用户密码必须经过加密的,其中用的最普遍的就是MD5加密了.但是随着彩虹桥技术的兴起,MD5加密已经不再安全.

如对于MD5加密来说攻击者只需要一个简单的sql语句`:select * from userInfo where password=’4QrcOUm6Wau+VuBX8g+IPg==’` 就可以知道有几个用户密码是”123456”,这对一个项目来说十分危险。

所以一般在加密之前,配上一个一串的随机序列。称之为salt。

PBKDF2(Password-Based Key Derivation Function)。

PBKDF2算法通过多次hash来对密码进行加密。原理是通过password和salt进行hash,然后将结果作为salt在与password进行hash,多次重复此过程,生成最终的密文。此过程可能达到上千次,逆向破解的难度太大,破解一个密码的时间可能需要几百年,所以PBKDF2算法是安全的.

密码加盐。盐是一个添加到用户的密码哈希过程中的一段随机序列。这个机制能够防止通过预先计算结果的彩虹表破解。每个用户都有自己的盐,这样的结果就是即使用户的密码相同,通过加盐后哈希值也将不同。为了校验密码是否正确,我们需要储存盐值。通常和密码哈希值一起存放在账户数据库中,或者直接存为哈希字符串的一部分。

PasswordEncryption工具类如下

package com.xianquan.web.util;

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 {

    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";

    /**
     * 盐的长度
     */
    public static final int SALT_BYTE_SIZE = 32 / 2;

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

    /**
     * 迭代次数
     */
    public static final int PBKDF2_ITERATIONS = 10000;

    /**
     * 对输入的密码进行验证
     *
     * @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        
     * @return      
     */
    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      
     * @return
     */
    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;
        }
    }
    
}
PasswordEncryption

生成密文:首先要生成一个盐值salt,再把原始密码和salt加密得到密文。

验证密文:把用户输入的密码和同样的盐值salt使用相同的加密算法得到一个密文,将这个密文和原密文相比较,相同则验证通过,反之则不通过。

调用实例如下

    public static void main(String[] args) {
        String password = "admin";
        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 + "  " + password.length());
            System.out.println("salt" + salt + "  " + salt.length());
            System.out.println("ciphertext" + 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");
        }
    }
Test

 

posted @ 2020-04-28 22:08  l-coil  阅读(1224)  评论(0编辑  收藏  举报