使用者密碼MD5加密以及驗證
阿新 • • 發佈:2019-01-03
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加密也不是當前最好的加密方式,期待下一篇