密码和Java中的加解密之MD5加点盐

3 篇文章 0 订阅
1 篇文章 0 订阅
    

很多人都用MD5+Base64方式存储密码,这种存储方式 方便、速度快而且由于MD5杂凑算法的几乎不可还原性,攻击者只能通过"猜"去破解密码。


但是MD5对相同的数据返回的信息永远是一样的,"123456"通过MD5+Base64编码后,永远是"4QrcOUm6Wau+VuBX8g+IPg==",攻击者只需要一个简单的sql语句:select * from userInfo where password='4QrcOUm6Wau+VuBX8g+IPg==' 就可以知道有几个用户密码是"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杂凑,与以前存储的密码比较是否一致。

package test1;

import java.security.MessageDigest;
import java.util.Random;

import sun.misc.BASE64Encoder;

/**
 * 对帐号口令加密
 * @author shanl
 *
 */
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:未通过
	 */
	synchronized
	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
	 */
	synchronized 
	public static String toPasswd(String inputPasswd){
		byte[] salt = getSaltOfASCII(2);
		return toPasswd(inputPasswd, salt);
	}
	
	/**
	 * 将客户输入的密码加密
	 * @param inputPasswd 客户输入的密码 
	 * @param salt 盐
	 * @return 加密后的字符串
	 */
	synchronized
	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;
	}
}

 

测试类:

package test1;

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"验证,多个加密后的密码。
 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值