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