数据库字段加盐

为保证数据安全,数据库密码类字段设计成不可逆的定长密文,转为密文的过程需加盐。
写了个编码工具类,数据库字段的安全性可以保障。

  • 防撞库(密码碰撞攻击),大多数用户习惯问题,多个网站使用同一密码, 某个网站数据泄露后,其他网站密码若使用明文存储,就太尴尬了,用户账号安全性完全没有保障。
  • 防拖库后用户密码泄露,若数据库通过sql注入被拖库,攻击者即便拿到数据,密码为不可逆的密文,保证了用户密码不会被泄露。(不过话说回来,已经被拖库了,虽然密码不会暴露,但用户信息已经泄露了。。。)
  • 防彩虹表攻击。攻击者一般会事先建表或直接使用网上已有的各种库,单次哈希算法基本都已无效,虽然算法不可逆,但是查表可得密码。这种情况就需要算法不暴露,当然最关键的还是加盐。

Encode工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import java.security.MessageDigest;
/**
* Created by Bean on 2017/12/1.
*/
public class EncodeUtil {
/**
* 密码加盐,预防保障数据库泄露的用户信息安全,防密码碰撞攻击
*/
private static final String PASSWORD_SALT = "helloBeanSherry%salt&ok=monkey_bean";
private static final int HASH_COUNT = 3;
/**
* 密码编码
*/
public static String encodePassword(String password) {
return encodeStrMd5(password, PASSWORD_SALT, HASH_COUNT);
}
/**
* 对密码做md5摘要+base64
* @param origin 原始字符串
* @param salt 盐
* @param count hash次数
*/
public static String encodeStrMd5(String origin, String salt, int count) {
String originAddSalt = twoStringXor(origin, salt);
String destMd5 = encodeMd5ByteDirectToBase64(originAddSalt);
if (count == 1) {
return destMd5;
} else {
return encodeStrMd5(destMd5, salt, --count);
}
}
/**
* 两个字符串异或
*/
private static String twoStringXor(String firstStr, String secondStr){
byte[] firstBytes = firstStr.getBytes();
byte[] secondBytes = secondStr.getBytes();
byte[] shortBytes, longBytes;
if(firstBytes.length >= secondBytes.length){
shortBytes = secondBytes;
longBytes = firstBytes;
}else{
shortBytes = firstBytes;
longBytes = secondBytes;
}
byte[] xorBytes = new byte[longBytes.length];
int i = 0;
for(; i<shortBytes.length; i++){
xorBytes[i] = (byte)(shortBytes[i] ^ longBytes[i]);
}
for(; i<longBytes.length; i++){
xorBytes[i] = longBytes[i];
}
return new String(xorBytes);
}
/**
* encode
* MD5 bye[] direct to Base64
*/
private static String encodeMd5ByteDirectToBase64(String origin){
// // 库MessageDigest
// String result = "";
// try {
// MessageDigest md;
// md = MessageDigest.getInstance("MD5");
// md.update(origin.getBytes());
// result = new String(Base64.encodeBase64(md.digest()));
// } catch (Exception e) {
// e.printStackTrace();
// }
// return result;
// 库DigestUtils
return new String(Base64.encodeBase64(DigestUtils.md5(origin)));
}
/**
* encode
* MD5 string, then to base64
*/
private static String encodeMd5HexToBase64(String origin){
String Md5Hex = DigestUtils.md5Hex(origin);
return new String(Base64.encodeBase64(Md5Hex.getBytes()));
}
}

思想:明文密码传入, 与盐做异或运算,得值,将值Md5,得到字节数据,直接进行base64运算。将以上步骤重复多次,得到结果即为存储值。

测试类

仅查看输出结果唯一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import junit.framework.TestCase;
/**
* Created by Bean on 2017/12/1.
*/
public class EncodeUtilTest extends TestCase {
public void setUp() throws Exception {
super.setUp();
}
public void tearDown() throws Exception {
}
public void testEncodePassword() throws Exception {
String origin = "a12345678";
String result = EncodeUtil.encodePassword(origin);
System.out.println("origin: " + origin);
System.out.println("result: " + result);
assertTrue(EncodeUtil.encodePassword(origin).equals(result));
}
}

测试输出

md5得到字节数组直接进行base64(第一种) 与 md5转为字符串再base64(第二种)不是一回事,结果完全不同。

第一种

encodeStrMd5调用encodeMd5ByteDirectToBase64

1
2
origin: a12345678
result: 8yWH0mpMi2UxufmvlfPLqQ==

第二种

encodeStrMd5调用encodeMd5HexToBase64

1
2
origin: a12345678
result: YmI4YmZmNjY5OTAzYzU5NmJlM2FhODZhODYyOGI2NjQ=