很多人都用MD5+Base64方式存储密码,这种存储方式 方便、速度快而且由于MD5杂凑算法的几乎不可还原性,攻击者只能通过”猜”去破解密码。
但是MD5对相同的数据返回的信息永远是一样的,”123456″通过MD5+Base64编码后,永远是”4QrcOUm6Wau+VuBX8g+IPg==”,攻击者只需要一个简单的sql就可以知道有几个用户密码是”123456″,这样十分危险。
//这个循环无论执行几次,返回都是一样。
static void t3(){
for(int i=0;i<5;i++){
try {
String inputPwd = “123456”;
MessageDigest md;
BASE64Encoder b64Encoder = new BASE64Encoder();
byte[] bys = null;
md = MessageDigest.getInstance(“MD5″,”SUN”);
md.reset();
md.update(inputPwd.getBytes(“UTF-8”));
bys = md.digest();
System.out.println(“编码后:”+b64Encoder.encode(bys));
} catch (Exception e) {
e.printStackTrace();
}
}
}
一种简单有效的解决方法,就是在MD5杂凑前加点随机码,这样攻击者将无法通过一个简单的SQL语句来查找密码是”123456″的用户。
随机码长度是应该固定,且同用户输入的密码一起进行MD5杂凑,随机码应该在Base64编码后的固定位置。
校验时,先取出随机码,再同输入一起进行MD5杂凑,与以前存储的密码比较是否一致。
import java.security.MessageDigest;
import java.util.Random;
import sun.misc.BASE64Encoder;
/**
* 对帐号口令加密
*/
public class PasswdEncryption {
private static final MessageDigest md;
private static final BASE64Encoder b64Encoder;
static{
try {
md = MessageDigest.getInstance(“MD5”, “SUN”);
b64Encoder = new BASE64Encoder();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 检查密码
* @param inputPasswd 用户输入的密码
* @param storePasswd 已存储的密码
* @return true:通过检查,false:未通过
*/
public static boolean checkPasswd(String inputPasswd, String storePasswd){
boolean ok = false;
try{
byte[] saltBys = storePasswd.substring(0,2).getBytes(“UTF-8”);
String inPwd = toPasswd(inputPasswd, saltBys);
ok = inPwd.equals(storePasswd);
}catch(Exception ex){
ex.printStackTrace();
ok = false;
}
return ok;
}
/**
* 将客户输入的密码加密
* @param inputPasswd
* @return
*/
public static String toPasswd(String inputPasswd){
byte[] salt = getSaltOfASCII(2);
return toPasswd(inputPasswd, salt);
}
/**
* 将客户输入的密码加密
* @param inputPasswd 客户输入的密码
* @param salt 盐
* @return 加密后的字符串
*/
private static String toPasswd(String inputPasswd, byte[] salt){
String pwd = “”;
try{
md.reset();
md.update(salt);
md.update(inputPasswd.getBytes(“UTF-8”));
byte[] bys = md.digest();;
//1:AASLexNtFtI7e1IuQIg88ZNA==
//879:AA/lCM5NEwVQJ25YYomE1ldQ==
//1000:AARXKQat7z+/iu2w6KpKgLQA==
//for(int i=0; i<1000; i++){
//md.reset();
//bys = md.digest(bys);
//}
pwd += (char)salt[0];
pwd += (char)salt[1];
pwd += b64Encoder.encode(bys);
}catch(Exception ex){
ex.printStackTrace();
pwd = “”;
}
return pwd;
}
/**
* 返回指定长度的盐(ASCII码)
* @param len 长度
* @return
*/
private static byte[] getSaltOfASCII(int len){
byte[] salt = new byte[len];
Random rand = new Random();
for(int i=0; i<len; i++){
//salt[i] = ‘A’;
salt[i] = (byte) ((rand.nextInt(‘~’-‘!’)+’!’) & 0x007f);
}
return salt;
}
}
//测试类:
public class Test8 {
public static void main(String[] args){
t2();
//t1();
}
static void t2(){
String[] userPwds = {
“123456”,
“123456”,
“123456”,
“123456”,
“123456”,
“1234561”, //error input
};
//通过t1()得到
String[] dbPwds = {
“u:rkoqEBZ+IZlcugSyVkhNVg==”,
“vDEbVZ9JlSveGnB/Qdz69aAQ==”,
“hbgon/TjwH8s7YSuqVSDl9Yg==”,
“0-ExML8oTOSCeF3ETf1yvDHg==”,
“1TeHpWiXOhx5ilscC6D6G98g==”,
“1TeHpWiXOhx5ilscC6D6G98g==”,
};
String msg = “”;
for(int i=0,end=dbPwds.length; i<end; i++){
msg = “user input passwd:”+userPwds[i];
msg += “,db store passwd:”+dbPwds[i];
msg += “,check passwd:”+PasswdEncryption.checkPasswd(userPwds[i],dbPwds[i]);
System.out.println(msg);
}
}
/**
* 加密测试
*/
static void t1(){
String pwd = “123456”;
String pwd1 = null;//PasswdEncryption.toPasswd(pwd);
for(int i=0; i<5; i++){
pwd1 = PasswdEncryption.toPasswd(pwd);
System.out.println(pwd1);
}
}
}
t1()结果显示,在很小的情况下会出现相同的加密后密码,攻击者很难用一个简单SQL去查找同一密码的用户。
t2()则可以用同一个密码”123456″验证,多个加密后的密码。
转载请注明来源网站:blog.ytso.com谢谢!
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/14722.html