本文是对Java开发做安全的架构设计。
攻击场景说明:在客户端发起请求时,如果URL的请求参数被篡改为网页脚本的话,而且后台没有对参数做处理的话,在当前页面中会受到恶意攻击。
如何解决:在后台编写一个过滤器,对后台接收到的请求参数做过滤处理。
代码说明:
@Component
@WebFilter
public class XssFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//重写该方法(对接收的参数做处理)
XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssHttpServletRequestWrapper, response);
}
@Override
public void destroy() {
}
}
```java
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private HttpServletRequest request;
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
//重写该方法,对接收到的参数做过滤处理
public String getParameter(String name) {
String oldValue = super.getParameter(name);
if(StringUtils.isEmpty(oldVa[添加链接描述](https://pc.qq.com/detail/10/detail_3330.html)lue)){
return oldValue;
}
String newValue = StringEscapeUtils.escapeHtml4(oldValue);
return newValue;
}
}
攻击场景说明:可以使用第三方抓包工具,对请求前后实现代理,可以修改参数请求内容和参数响应内容。
Fiddler4抓包工具下载地址:
https://pc.qq.com/detail/10/detail_3330.html
使用Fiddler4篡改请求之前:
解决方案:
使用MD5对参数做加密处理,防止参数被篡改
使用MD5可以直接验证签名参数 MD5 属于单向加密,只能够暴力破解。
MD5应用场景:在nacos分布式配置中心中,使用MD5 比对文件内容是否发生改变
MD5在线暴力破解地址:https://www.cmd5.com/
攻击场景说明:对接口实现对称加密,抓包的时候看不到明文的数据,但是可以被破解,因为客户端与服务器都是采用同一个密钥实现加解密,可以通过反编译客户端代码得出密钥实现破解。
对称加密
常用对称加密框架:
1.DES(Data Encryption Standard)
2.3DES(DES加强版,使用3次DES计算,Triple DES,DESede)
3.AES(Advanced Encryption Standard,3DES加强版)
工具类代码如下:
/**
* 字符串加密解密工具,可逆加密,秘钥很重要,一定要自己改秘钥,打死也不要告诉其他人
*/
public class DESUtil {
// 密钥,是加密解密的凭据,长度为8的倍数
private static String PASSWORD_CRYPT_KEY = "12345678";
private final static String DES = "DES";
/**
* 加密
*
* @param src 数据源
* @param key 密钥,长度必须是8的倍数
* @return 返回加密后的数据
* @throws Exception
*/
public static byte[] encrypt(byte[] src, byte[] key) throws Exception {
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密匙数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密匙工厂,然后用它把DESKeySpec转换成
// 一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密匙初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
// 现在,获取数据并加密
// 正式执行加密操作
return cipher.doFinal(src);
}
/**
* 解密
*
* @param src 数据源
* @param key 密钥,长度必须是8的倍数
* @return 返回解密后的原始数据
* @throws Exception
*/
public static byte[] decrypt(byte[] src, byte[] key) throws Exception {
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密匙数据创建一个DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
// 一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance(DES);
// 用密匙初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
// 现在,获取数据并解密
// 正式执行解密操作
return cipher.doFinal(src);
}
/**
* 密码解密
*
* @param data
* @return
* @throws Exception
*/
public final static String decrypt(String data) {
try {
return new String(decrypt(hex2byte(data.getBytes()), PASSWORD_CRYPT_KEY.getBytes()));
} catch (Exception e) {
}
return null;
}
/**
* 密码加密
*
* @param password
* @return
* @throws Exception
*/
public final static String encrypt(String password) {
try {
return byte2hex(encrypt(password.getBytes(), PASSWORD_CRYPT_KEY.getBytes()));
} catch (Exception e) {
}
return null;
}
/**
* 二行制转字符串
*
* @param b
* @return
*/
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1)
hs = hs + "0" + stmp;
else
hs = hs + stmp;
}
return hs.toUpperCase();
}
public static byte[] hex2byte(byte[] b) {
if ((b.length % 2) != 0)
throw new IllegalArgumentException("长度不是偶数");
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += 2) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
// 测试用例,不需要传递任何参数,直接执行即可。
public static void main(String[] args) {
String basestr = "mayikt";
String str1 = encrypt(basestr);
System.out.println("原始值: " + basestr);
System.out.println("加密后: " + str1);
System.out.println("解密后: " + decrypt(str1));
}
}
非对称加密
公钥和私钥互换,生成一对秘钥对,分别为公钥和私钥。
工具类代码如下:
import org.apache.commons.codec.binary.Base64;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSAUtils {
public static final String CHARSET = "UTF-8";
public static final String RSA_ALGORITHM = "RSA"; // ALGORITHM ['ælgərɪð(ə)m] 算法的意思
public static Map<String, String> createKeys(int keySize) {
// 为RSA算法创建一个KeyPairGenerator对象
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
}
// 初始化KeyPairGenerator对象,密钥长度
kpg.initialize(keySize);
// 生成密匙对
KeyPair keyPair = kpg.generateKeyPair();
// 得到公钥
Key publicKey = keyPair.getPublic();
String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
// 得到私钥
Key privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
// map装载公钥和私钥
Map<String, String> keyPairMap = new HashMap<String, String>();
keyPairMap.put("publicKey", publicKeyStr);
keyPairMap.put("privateKey", privateKeyStr);
// 返回map
return keyPairMap;
}
/**
* 得到公钥
* @param publicKey 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// 通过X509编码的Key指令获得公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
return key;
}
/**
* 得到私钥
* @param privateKey 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// 通过PKCS#8编码的Key指令获得私钥对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
return key;
}
/**
* 公钥加密
* @param data
* @param publicKey
* @return
*/
public static String publicEncrypt(String data, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 私钥解密
* @param data
* @param privateKey
* @return
*/
public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 私钥加密
* @param data
* @param privateKey
* @return
*/
public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
//每个Cipher初始化方法使用一个模式参数opmod,并用此模式初始化Cipher对象。此外还有其他参数,包括密钥key、包含密钥的证书certificate、算法参数params和随机源random。
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 公钥解密
* @param data
* @param publicKey
* @return
*/
public static String publicDecrypt(String data, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
}
}
//rsa切割解码 , ENCRYPT_MODE,加密数据 ,DECRYPT_MODE,解密数据
private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
int maxBlock = 0; //最大块
if (opmode == Cipher.DECRYPT_MODE) {
maxBlock = keySize / 8;
} else {
maxBlock = keySize / 8 - 11;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] buff;
int i = 0;
try {
while (datas.length > offSet) {
if (datas.length - offSet > maxBlock) {
//可以调用以下的doFinal()方法完成加密或解密数据:
buff = cipher.doFinal(datas, offSet, maxBlock);
} else {
buff = cipher.doFinal(datas, offSet, datas.length - offSet);
}
out.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
}
} catch (Exception e) {
throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
}
byte[] resultDatas = out.toByteArray();
IOUtils.closeQuietly(out);
return resultDatas;
}
// 简单测试____________
public static void main(String[] args) throws Exception {
Map<String, String> keyMap = RSAUtils.createKeys(1024);
String publicKey = keyMap.get("publicKey");
String privateKey = keyMap.get("privateKey");
System.out.println("公钥: \n\r" + publicKey);
System.out.println("私钥: \n\r" + privateKey);
System.out.println("公钥加密——私钥解密");
String str="mayikt";
String encodedData = RSAUtils.publicEncrypt(str, RSAUtils.getPublicKey(privateKey)); //传入明文和公钥加密,得到密文
System.out.println("密文:\r\n" + encodedData);
String decodedData = RSAUtils.privateDecrypt(encodedData, RSAUtils.getPrivateKey(publicKey)); //传入密文和私钥,得到明文
System.out.println("解密后文字: \r\n" + decodedData);
}
}
注意:在Http协议通讯过程中,如果传递特殊字符的情况下,比如:+!等特殊字符统一会变为空格。所以注意需要对url参数实现UrlEncode