zoukankan      html  css  js  c++  java
  • 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

    本文来自博客园,作者:l-coil,转载请注明原文链接:https://www.cnblogs.com/l-coil/p/12797641.html

  • 相关阅读:
    浏览器不兼容原因及解决办法
    VC++ MFC DLL动态链接库编写详解
    Saas是什么?
    用CSS中的Alpha实现渐变
    一种真正意义上的Session劫持[转]
    使用.NET Framework中新的日期时间类型[转]
    Hook、钩子、VC++ 基本概念
    H264
    Windows编程中各种操作文件的方法
    将TCP/IP协议移植到内嵌的弹片机中配合GPRS无线模块开发应用
  • 原文地址:https://www.cnblogs.com/xianquan/p/12797641.html
Copyright © 2011-2022 走看看