前后端安全策略初探

前后端的通信安全,若想完全保障,则要使用https,从tcp层保证传输安全,但https从证书申请、搭建、到使用,成本感人,目前国内只有主流大型网站使用。目前大多数网站还是使用的http。
用户的密码在传输时必须是密文,由于前端代码本就是暴露的,即便通过加密混淆也可以反解,所以前端的加密方法必须是不可逆的,若可逆则只能防小白。
所以通过算法的安全性保证传输安全。
以登录为例。

密码安全性

方法一,N次Md5

1.数据库存储的密码字段为md5Hex + DES(AES)。
2.前端首先对密码进行单次Md5摘要CryptoJS.MD5(value).toString(CryptoJS.enc.Base64),Date.now()获取当前时间stime,根据stime尾数(0-9)再进行n次Md5,尾数为6,也就是循环6遍Md5摘要,stime的随机性使每次Md5摘要值都不同,可简单防范。
3.后端对比密码是否正确时,首先从数据库读取密码存储的密文字段,再进行DES解密, 然后根据stime, 对dbPassword进行多次Md5运算,将运算结果与前端传的摘要密码对比即可。过程如下

1
2
3
4
5
6
7
8
9
public static final String key = "helloBean";
public static boolean isRightPassword(String stime, String passwordMd5, String dbPassword){
dbPassword = DesUtil.decryptPsWithSlat(dbPassword, key);
int loop = Integer.parseInt(stime.substring(stime.length() - 1));
for (int i = 0; i < loop; i++) {
dbPassword = DigestUtils.md5Hex(dbPassword);
}
return dbPassword.equals(passwordMd5);
}

DesUtil工具类代码如下:

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
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.SecureRandom;
public class DesUtil {
/**
* 加密
*
* @param src byte[]
* @param key String
* @return byte[]
*/
public static byte[] encrypt(byte[] src, String key) {
try {
SecureRandom random = new SecureRandom();
DESKeySpec desKey = new DESKeySpec(key.getBytes());
//创建一个密匙工厂,然后用它把DESKeySpec转换成
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(desKey);
//Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance("DES");
//用密匙初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey, random);
//现在,获取数据并加密
//正式执行加密操作
return cipher.doFinal(src);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
/**
*
* DES解密
*
* @param src byte[]
* @param key String
* @return byte[]
*/
public static byte[] decrypt(byte[] src, String key) throws Exception {
// DES算法要求有一个可信任的随机数源
SecureRandom random = new SecureRandom();
// 创建一个DESKeySpec对象
DESKeySpec desKey = new DESKeySpec(key.getBytes());
// 创建一个密匙工厂
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
// 将DESKeySpec对象转换成SecretKey对象
SecretKey securekey = keyFactory.generateSecret(desKey);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance("DES");
// 用密匙初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, securekey, random);
// 真正开始解密操作
return cipher.doFinal(src);
}
/**
* 账户密码,DES算法盐作为密钥加密,
*/
public static String encryptPassWithSlat(String passwdMd5, String key) {
String desPassword = "";
try {
byte[] enc = encrypt(passwdMd5.getBytes(), key);
//加密结果需要转换成hex才能存入数据库
desPassword = Hex.encodeHexString(enc);
} catch (Exception e) {
e.printStackTrace();
}
return desPassword;
}
/**
* 账户密码,DES算法盐作为密钥解密,
*/
public static String decryptPsWithSlat(String desPassword, String key) {
String passwdMd5 = "";
try {
//desPassword是hex格式,应该先转换
byte[] hex = Hex.decodeHex(desPassword.toCharArray());
byte[] dec = decrypt(hex, key);
passwdMd5= new String(dec);
} catch (Exception e) {
e.printStackTrace();
}
return passwdMd5;
}
}

方法二,前端加盐

1.数据库存储的密码字段为加盐后的摘要。
2.前端加盐后再单次摘要, 加盐建议重写异或而不是简单字符串拼接。然后根据stime做n次摘要,方法同上
3.后端从数据库取出字段,做n次摘要后,与前端传输的密码比对即可。

传输安全,身份认证,防简单攻击

  • 所有请求,均加摘要。前端获取当前时间ostime,与请求参数拼接后做一次摘要,即MD5(params + ostime),同时将ostime传给后端,后端收到请求后,按相同方法算一次摘要,与前端传的摘要比对,相同则合法,否则返回401。防重放攻击,但攻击者通过分析前端代码可以模拟正常请求。
  • 后端对所有请求进行时间合法性判断,与服务器时间比对,30秒以内则认为合法。简单的防重放攻击。
  • 后端记录每个ip的每个路径的最近访问时间,本次访问时间-上次访问时间大于100ms,否则不可访问接口,返回401。
  • 用户身份信息存储在session中,对于登录后调用的接口,后端判断session中是否有身份信息,没有则返回403。攻击者可以进行cookie劫持和session获取进行攻击,开发者可通过使用session的同时引入token防御。
  • 能使用https一定不要使用http。

题外话

之前写了一个客户端内嵌WebView与浏览器访问页面的安全机制,在那个项目,用户身份认证使用的token策略,token身份认证一般是用于客户端和服务器通信(CS),客户端可将token存储到本地数据库;前后端(BS)的身份认证则是cookie-session。