1. 程式人生 > >使用者密碼MD5加密以及驗證

使用者密碼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加密也不是當前最好的加密方式,期待下一篇