用户密码MD5加密以及验证

MD5概念:https://baike.baidu.com/item/MD5/212708?fr=aladdin

盐值概念:https://baike.baidu.com/item/salt%E5%80%BC

注册:

1、生成固定长度的随机盐;
2、用户密码加密生成32位16进制字符串;(建议用户的注册密码经过严格的校验,至少输入3类字符,长度至少10位、注册密码有包含大小写字母等等)
3、按照规则将盐和加密的32位16进制字符串拼接成最终密码;
4、将用户手机号、姓名、年龄、密码保存到数据库。

登录验证密码:

1、根据用户手机号,获取保存到数据库中的最终密码;
2、从最终密码中获取用户注册时输入的密码生成的32位16进制字符串;
3、获取用户登录输入的密码的32位16进制字符串;
4、验证这两个字符串是否相同。

盐值注意点:

1、盐值不能固定,如果数据库信息泄露,盐值就没有任何作用了,攻击者可以利用盐值生成密码表;
2、盐的长度不能太短,如果太短,攻击者就会穷举出所有的可能。

盐值和用户输入密码的组合注意点:
1、组合规则应该复杂一点,不能是简单的字符串拼接

防止机器人登录验证方式:

1、验证码,虽然可以抵御攻击,但是还是和后端不断的交互,发一条验证码也要钱,而且机器人越多,接口的性能越低,严重影响正常用户;
2、当前最流行的滑块验证,是最有效的方法。

代码实现:

package com.cn.dl;

import java.security.MessageDigest;
import java.security.SecureRandom;

/**
 * Created by yanshao on 2018/12/17.
 */
public class MD5Utils {

    //盐值长度
    private static final int SALT_LENGTH = 30;
    //加密算法
    private static final String  ALGORITHM = "MD5";
    //编码
    private static final String CHARSET = "UTF-8";
    //密码加密长度
    private static final int MD5_LENGTH = 32;

    /**
     * byte数组转换成十六进制字符串
     * @param bytes byte数组
     * @return
     */
    private static String bytesToHexStr(byte[] bytes) {
        String tmp = "";
        StringBuilder sb = new StringBuilder("");
        for (int i = 0; i < bytes.length; i++) {
            tmp = Integer.toHexString(bytes[i] & 0xFF);
            sb.append((tmp.length() == 1) ? "0" + tmp : tmp);
        }
        return sb.toString().toUpperCase().trim();
    }

    /**
     * 十六进制字符串转成byte数组
     * @param hexStr   十六进制字符串
     * @return
     * */
    private static byte[] hexStrToBytes(String hexStr) {
        byte[] bytes = new byte[hexStr.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) Integer.parseInt(hexStr.substring(2 * i, 2 * i + 2), 16);
        }
        return bytes;
    }

    /**
     * 生成随机盐
     * @return 返回长度为SALT_LENGTH * 2的盐
     * */
    public static String createSaltValue(){
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[SALT_LENGTH];
        random.nextBytes(salt);
        return bytesToHexStr(salt);
    }

    /**
     * 用户密码使用md5加密
     * @param password  用户密码
     * @return          返回长度为32位的16进制字符串
     * */
    public static String md5EncodePwd(String password){
        try {
            MessageDigest digest = MessageDigest.getInstance(ALGORITHM);
            byte[] result = digest.digest(password.getBytes(CHARSET));
            return bytesToHexStr(result);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 按照salt+pwd的顺序返回最终要保存到数据库的密码
     * @param salt           随机盐
     * @param md5encodePwd  经过md5加密的字符串
     * @return               按照规则返回的最终密码
     * */
    public static String getFinalPwd(String salt,String md5encodePwd){
        StringBuilder stringBuilder = new StringBuilder();
        /**
         * 前64位规则:
         * 奇数位是用户真实密码的hash值
         * 偶数为是盐值
         * 后16位:是剩余的盐值
         * */
        // TODO: 2018/12/17 根据实际需求定义规则 
        for(int i=0 ; i<md5encodePwd.length(); i++){
            stringBuilder.append(md5encodePwd.substring(i,i+1))
                    .append(salt.substring(i,i+1));
        }
        stringBuilder.append(salt.substring(md5encodePwd.length(),salt.length()));
        return stringBuilder.toString();
    }

    /**
     * 按照salt+pwd的顺序返回最终要保存到数据库的密码
     * @return    按照规则返回的最终密码
     * */
    public static String createHashPwd(String password){
        return getFinalPwd(createSaltValue(),md5EncodePwd(password));
    }

    /**
     * 从数据库中保存的最终密码,解析出用户真实祕密的md5加密串
     * @return    返回真实祕密的加密串
     * */
    public static String getUserPwdMD5(String finalPwd){
        try {
            StringBuilder stringBuilder = new StringBuilder();
            for(int i=0 ; i < MD5_LENGTH * 2 ; i+=2){
                stringBuilder.append(finalPwd.substring(i,i+1));
            }
            return stringBuilder.toString();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 验证用户输入的密码是否正确
     * @param password  用户输入的密码
     * @param finalPwd  数据库中保存的密码
     * @return           验证结果:true密码正确,反之密码错误
     * */
    public static boolean verifyPwd(String password,String finalPwd){
        return password == null ?
                false : md5EncodePwd(password).equals(getUserPwdMD5(finalPwd));
    }

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("");
        for( int i=0;i<100;i++){
            /**
             * 生成加密密码
             * */
            String userPwd = "[email protected]"+i;
            String salt = createSaltValue();
            System.out.println("盐值>>"+salt);
            String md5encodePwd = md5EncodePwd(userPwd);
            System.out.println("md5>>"+md5encodePwd);
            String finalPwd = getFinalPwd(salt,md5encodePwd);
            if(sb.toString().contains(finalPwd)){
                System.out.println("重复的密码>>"+finalPwd);
                break;
            }
            sb.append(finalPwd);
            System.out.println("最终密码>>"+finalPwd);
            /**
             * 用户登录输入密码,验证祕密是否正确
             * */
            boolean verify = verifyPwd(userPwd,finalPwd);
            System.out.println("密码正确");
            if (verify == false){
                System.out.println("输入的密码>>"+userPwd+">>密码验证失败>>"+getUserPwdMD5(finalPwd));
                break;
            }
        }
    }

}

测试结果:

盐值>>D02C3734BB29F53E55D45670F176CB97D7E45ED3BCBD656C6D0CD552B457
md5>>5355C5CAE5A061DAECB6CD06060A47E9
最终密码>>5D30525CC357C3A4EB5BA2096F15D3AEE5C5BD64C5D607600F6107A64C7BE997D7E45ED3BCBD656C6D0CD552B457
密码正确
盐值>>A4F5C9A24DA2BF58E4E247C6E58CD9CC2CF517DEDC0DE8DC8E2E6A7770C0
md5>>3798D88CA7F090943425C908AFBB6370
最终密码>>3A749F85DC898AC2A47DFA029B0F95483E442E52C4970C86AEF5B8BC6D397C0C2CF517DEDC0DE8DC8E2E6A7770C0
密码正确
盐值>>6EF8E5C18A7DA5A5992C5E3B6727D2618E53025CC8DD68557CF6005F0A26
md5>>B7967A9BA741F4D98EFA856667AF99D2
最终密码>>B67E9F687EA59CB1A87A471DFA45DA9589E9F2AC855E636B6677A2F79D92D6218E53025CC8DD68557CF6005F0A26
密码正确
盐值>>B1D21E8489CF6C1D126BAEF4D1B0F25B02AE482770A86102522813C35700
md5>>5541317B0985F40C0859294E0E91DDE5
最终密码>>5B514D12311E78B408998C5FF64C01CD0182569B2A9E4FE40DE19B10DFD2E55B02AE482770A86102522813C35700
密码正确

MD5加密也不是当前最好的加密方式,期待下一篇

 

点赞