在Java开发中,免不了和其他系统的业务交互,如果我们想保证数据传输的安全,对接口出参加密,入参解密,身份认证。
思路:
一个公钥对应一个私钥。
公钥开放给所有人都可以知道,私钥自己保留,不能泄露。
用其中一个密钥加密数据,则只有对应的那个密钥才可以解密。
用其中一个密钥可以进行解密数据,则该数据必然是对应的那个密钥进行的加密。
一、加密:公钥加密私钥解密
保证数据的安全性,主要用于将数据资料加密,防止被其他人获取。使用公钥进行加密,只有私钥可以解密,即使被第三方拦截获取,没有私钥无法解密,从而保证数据的安全性。
1.A在自己电脑上生成RSA钥匙文件,一个私钥文件一个公钥文件,B获取到A的公钥。
2.B传消息给A,B用A的公钥加密消息,然后发送给A,(计算数据被人获取到,没有A的私钥也无法进行解密)
3.A获取到消息后,用私钥进行解密B的消息。
二、认证:私钥签名公钥验签
主要用于身份验证的真实性,数据完整性校验和消息未被篡改等场景。
私钥签名,用私钥将一段数据进行加密,生成签名。
公钥验签,用对应的公钥来解密这段签名,通过验证签名来确定数据的完整性和真实性,确保数据在传输过程中没有被篡改或者伪造。
A验证B是否真实用户,
1.B将自己的公钥给A
2.B将内容用私钥签名,然后传送给A
3.A根据B的公钥验证签名,成功则用户身份认证通过
实现:
一、pom文件引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.15</version>
</dependency>
二、定义工具类
RSA加解密工具类
import java.io.ByteArrayOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Encoder;
public class RSAEncryptionUtil {
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 类型
*/
public static final String ENCRYPT_TYPE = "RSA";
/**
* 获取密钥对
*
* @return 密钥对
*/
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance(ENCRYPT_TYPE);
generator.initialize(1024);
return generator.generateKeyPair();
}
/**
* 获取私钥
*
* @param privateKey 私钥字符串
* @return
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPT_TYPE);
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return keyFactory.generatePrivate(keySpec);
}
/**
* 获取公钥
*
* @param publicKey 公钥字符串
* @return
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPT_TYPE);
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}
/**
* 公钥加密
* @param data
* @param publicKey
* @return
* @throws Exception
*/
public static String encrypt(String data, String publicKey) throws Exception {
if(data == null) {
data = "";
}
return encrypt(data, getPublicKey(publicKey));
}
/**
* RSA加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return
*/
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(ENCRYPT_TYPE);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = data.getBytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
// 加密后的字符串
return new String(Base64.encodeBase64String(encryptedData));
}
/**
* RSA解密
* @param data 待解密数据
* @param privateKey 私钥
* @return
* @throws Exception
*/
public static String decrypt(String data, String privateKey) throws Exception {
return decrypt(data, getPrivateKey(privateKey));
}
/**
* RSA解密
*
* @param data 待解密数据
* @param privateKey 私钥
* @return
*/
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(ENCRYPT_TYPE);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
// 解密后的内容
return new String(decryptedData, "UTF-8");
}
/**
* 获取签名
* @param data 待签名数据
* @param privateKey 私钥
* @return 签名
*/
public static String getSign(String data, String privateKey) throws Exception {
return sign(data, getPrivateKey(privateKey));
}
/**
* 签名
*
* @param data 待签名数据
* @param privateKey 私钥
* @return 签名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
byte[] keyBytes = privateKey.getEncoded();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPT_TYPE);
PrivateKey key = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(key);
signature.update(data.getBytes());
return new String(Base64.encodeBase64(signature.sign()));
}
/**
* 验签
* @param srcData
* @param publicKey
* @param sign
* @return
* @throws Exception
*/
public static boolean verify(String srcData, String publicKey, String sign) throws Exception {
return verify(srcData, getPublicKey(publicKey), sign);
}
/**
* 验签
*
* @param srcData 原始字符串
* @param publicKey 公钥
* @param sign 签名
* @return 是否验签通过
*/
public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
byte[] keyBytes = publicKey.getEncoded();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPT_TYPE);
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(key);
signature.update(srcData.getBytes());
return signature.verify(Base64.decodeBase64(sign.getBytes()));
}
/**
* 获取公私钥-请获取一次后保存公私钥使用
* @param publicKeyFilename 公钥生成的路径
* @param privateKeyFilename 私钥生成的路径
*/
public static void generateKeyPair(String publicKeyFilename, String privateKeyFilename) {
FileWriter pub = null;
FileWriter pri = null;
try {
KeyPair pair = getKeyPair();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
// 获取 公钥和私钥 的 编码格式(通过该 编码格式 可以反过来 生成公钥和私钥对象)
byte[] pubEncBytes = publicKey.getEncoded();
byte[] priEncBytes = privateKey.getEncoded();
// 把 公钥和私钥 的 编码格式 转换为 Base64文本 方便保存
String pubEncBase64 = new BASE64Encoder().encode(pubEncBytes);
String priEncBase64 = new BASE64Encoder().encode(priEncBytes);
pub = new FileWriter(publicKeyFilename);
pri = new FileWriter(privateKeyFilename);
pub.write(pubEncBase64);
pri.write(priEncBase64);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(pub != null) {
try {
pub.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(pri != null) {
try {
pri.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
try {
// 生成密钥对
KeyPair keyPair = getKeyPair();
String privateKey = new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded()));
String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));
System.out.println("私钥:" + privateKey);
System.out.println("公钥:" + publicKey);
// 公钥RSA加密
String data = "待加密的文字内容";
String encryptData = encrypt(data, getPublicKey(publicKey));
System.out.println("加密后内容:" + encryptData);
// 私钥RSA解密
String decryptData = decrypt(encryptData, getPrivateKey(privateKey));
System.out.println("解密后内容:" + decryptData);
// 私钥RSA签名
String sign = sign(data, getPrivateKey(privateKey));
// 公钥RSA验签
boolean result = verify(data, getPublicKey(publicKey), sign);
System.out.println("验签结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("加解密异常");
}
}
}
三、服务端实现
定义过滤器类
package com.ruoyi.common.api.filter;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.api.wrapper.ModifyRequestBodyWrapper;
import com.ruoyi.common.api.wrapper.ModifyResponseBodyWrapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.response.ApiResponse;
import com.ruoyi.common.core.utils.RSAEncryptionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 过滤器
* @author cw
* @date 2024/1/10 17:53
*/
@Component
public class HttpRequestFilter extends OncePerRequestFilter {
// r1
String priKey = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKOsFboL7Vmdq6b0Dpcliw9BxdiVr+0ldYLHGr0SBxluPbwMQx7T1SQDebxKnFd+rGyC50thRov9USI/qTg4VCUv510PlGpT9a4SqwuvLJ62p8ur6SZHQqiwCOVwtIevmwzwyXRtOqYLKMi2ZOiQ/mrvqw7xNts6UE/VHpNk8mRfAgMBAAECgYEAgmTy2knuSlsnCdeeWCR86SbJbIe0pQYW3J2rW74lEcmWPV63irPRNuPd9Hisnbg5vil8A7336kdigBqfwj5+KenwQ38xzTlcRh5woQMO+9ycapaP19EDP0UjK8m9ylMhmTin6D/yQzcN6qFKzaoHRTpSIX+L66UiP5vHsq4AV0ECQQDltXcKzYWAyOzYwYzrrPW64drmQbWmQ3/79DvvAEfVzMwF609VNqHkla2jl5ZS1CVKaKhyQjgZR4KFkldP/rK3AkEAtme6NRNmSkX1jusMF3+WQXRbaNEfyFf7YJ5CAgAXDXcJJgNYV3MGtqQzEC/CwTrZH2N8v2oZfAG856UcVzoTmQJBAJDUxnbYOGmHxdWwy1I5HkIzbxooqghnnMVWK4e4rJI/6w98WuvGY3LFcNKpnA0jV1PAAxzvZYGC1eez6js0wqcCQQCDs/GnURKSnmDniJ96eA0txa4ayKSCK8S9xocymo4KkuBwKgonWk6GoZidH18n1i3flB4Gt70e5MJZH8aO1MhBAkBjV/qe15qwuDtnuN2vWEbV4I2yEUccYE3UD8gYNk00WhKjHLfl4qcCHcK0tg6s3oNjYwAUin8xfu/3eXfq5mVI";
// p1
String pubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjrBW6C+1Znaum9A6XJYsPQcXYla/tJXWCxxq9EgcZbj28DEMe09UkA3m8SpxXfqxsgudLYUaL/VEiP6k4OFQlL+ddD5RqU/WuEqsLryyetqfLq+kmR0KosAjlcLSHr5sM8Ml0bTqmCyjItmTokP5q76sO8TbbOlBP1R6TZPJkXwIDAQAB";
// p2
String pubKey2 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjrBW6C+1Znaum9A6XJYsPQcXYla/tJXWCxxq9EgcZbj28DEMe09UkA3m8SpxXfqxsgudLYUaL/VEiP6k4OFQlL+ddD5RqU/WuEqsLryyetqfLq+kmR0KosAjlcLSHr5sM8Ml0bTqmCyjItmTokP5q76sO8TbbOlBP1R6TZPJkXwIDAQAB";
private static final Logger logger = LoggerFactory.getLogger(HttpRequestFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
ModifyRequestBodyWrapper modifyRequestBodyWrapper = new ModifyRequestBodyWrapper(request);
String oldRequestBody = modifyRequestBodyWrapper.getOldRequestBody();
("path:{}",request.getRequestURI());
("oldRequestBody:{}",oldRequestBody);
String newRequestBody = null;
try {
newRequestBody = changeRequestBody(oldRequestBody,priKey);
("newRequestBody:{}",newRequestBody);
} catch (Exception e) {
throw new RuntimeException("888888:请求参数不合法");
}
Map<String,String> params = JSON.parseObject(newRequestBody,Map.class);
String sign = params.get("sign");
String appId = params.get("app_id");
String content = params.get("content");
("pubKey:{}",pubKey);
("签名sign:{}",sign);
try {
if(!RSAEncryptionUtil.verify(content, pubKey, sign)) {
throw new RuntimeException("888888:验签失败");
}
} catch (Exception e) {
throw new RuntimeException("888888:验签失败");
}
//构造新请求体
modifyRequestBodyWrapper.setNewRequestBody(content);
ModifyResponseBodyWrapper modifyResponseBodyWrapper = new ModifyResponseBodyWrapper(response);
filterChain.doFilter(modifyRequestBodyWrapper,modifyResponseBodyWrapper);
String oldResponseBody = modifyResponseBodyWrapper.getResponseBody();
("oldResponseBody:{}",oldResponseBody);
AjaxResult ajaxResult = JSON.parseObject(oldResponseBody, AjaxResult.class);
ApiResponse apiResponse = new ApiResponse();
Object data = ajaxResult.get("data");
apiResponse.setResp_code(String.valueOf(ajaxResult.get("code")));
apiResponse.setResp_msg(String.valueOf(ajaxResult.get("msg")));
apiResponse.setData(JSON.toJSONString(data));
String newResponseBody = null;
try {
String resSign = RSAEncryptionUtil.getSign(apiResponse.getData(), priKey);
apiResponse.setSign(resSign);
newResponseBody = changeResponseBody(JSON.toJSONString(apiResponse),pubKey2);
} catch (Exception e) {
throw new RuntimeException("888888:响应参数异常");
}
("newResponseBody:{}",newResponseBody);
response.setContentType(request.getContentType());
byte[] responseBodyData = newResponseBody.getBytes(StandardCharsets.UTF_8);
response.setHeader("Content-Length",String.valueOf(responseBodyData.length));//解决数据过长导致截断问题
ServletOutputStream out = response.getOutputStream();
out.write(responseBodyData);
}
/**
* 修改请求体
* @param oldRequestBody 修改前的请求体
* @return 修改后的请求体
*/
public String changeRequestBody(String oldRequestBody,String priKey) throws Exception {
System.out.println("------------changeRequestBody");
return RSAEncryptionUtil.decrypt(oldRequestBody, priKey);
}
/**
* 修改响应体
* @param oldResponseBody 修改前的响应体
* @return 修改够的响应体
*/
public String changeResponseBody(String oldResponseBody,String pucKey) throws Exception {
System.out.println("--------------------changeResponseBody");
return RSAEncryptionUtil.encrypt(oldResponseBody, pucKey);
}
}
自定义HttpServletRequestWrapper 用于修改请求体
import .IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 自定义HttpServletRequestWrapper 用于修改请求体
*/
public class ModifyRequestBodyWrapper extends HttpServletRequestWrapper {
private String oldRequestBody;
private String newRequestBody;
public ModifyRequestBodyWrapper(HttpServletRequest request) throws IOException {
super(request);
this.oldRequestBody = IOUtils.toString(request.getInputStream(),request.getCharacterEncoding());
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
final ByteArrayInputStream bis = new ByteArrayInputStream(newRequestBody.getBytes(StandardCharsets.UTF_8));
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bis.read();
}
};
}
public String getOldRequestBody() {
return oldRequestBody;
}
public void setOldRequestBody(String oldRequestBody) {
this.oldRequestBody = oldRequestBody;
}
public String getNewRequestBody() {
return newRequestBody;
}
public void setNewRequestBody(String newRequestBody) {
this.newRequestBody = newRequestBody;
}
}
自定义HttpServletResponseWrapper 修改响应体
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
/**
* 自定义HttpServletResponseWrapper 修改响应体
*/
public class ModifyResponseBodyWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream bos;
public ModifyResponseBodyWrapper(HttpServletResponse response) {
super(response);
this.bos = new ByteArrayOutputStream();
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new ServletOutputStream() {
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public void write(byte[] b) throws IOException {
bos.write(b);
}
};
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(new OutputStreamWriter(bos));
}
public String getResponseBody() throws IOException {
ServletOutputStream outputStream = this.getOutputStream();
outputStream.flush();
PrintWriter writer = this.getWriter();
writer.flush();
return bos.toString(this.getCharacterEncoding());
}
}
四、客户端请求签名、加解密
请求对象类
import lombok.Data;
/**
* <p>Title: Request</p>
* <p>Description: </p>
*
* 个@author cw
* @date 2024/1/8 20:53
*/
@Data
public class ApiRequest {
private String appId;
private String content; // 业务参数的json字段
private String charset;
private String url;
private String privateKey; // 私钥
private String publicKey; // 公钥
}
请求服务类
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.request.ApiRequest;
import com.ruoyi.common.core.utils.HttpClientUtil;
import com.ruoyi.common.core.utils.RSAEncryptionUtil;
import java.util.HashMap;
import java.util.Map;
/**
* <p>Title: 接口请求服务</p>
* <p>Description: </p>
* 个@author cw
* @date 2024/1/8 20:58
*/
public class ApiService {
public Map<String, String> execute(ApiRequest request) throws Exception {
String content = request.getContent();
// 私钥+请求内容签名
String sign = RSAEncryptionUtil.getSign(content, request.getPrivateKey());
Map<String,String> params = new HashMap<>();
params.put("sign", sign);
params.put("content", content);
params.put("app_id", request.getAppId());
String json = JSON.toJSONString(params);
// 对请求参数加密
json = RSAEncryptionUtil.encrypt(json, request.getPublicKey());
// 发送HTTP请求
String res = HttpClientUtil.sendPosts(request.getUrl(),json, null);
if (null == res) {
throw new RuntimeException("888888:响应为空");
} else {
try {
if(request.getPrivateKey() != null) {
// 请求内容解密
res = RSAEncryptionUtil.decrypt(res, request.getPrivateKey());
}
Map<String,String> resMap = JSON.parseObject(res, Map.class);
String resSign = resMap.get("sign”);
// 验证服务端签名
if(resSign != null && RSAEncryptionUtil.verify(res, request.getPublicKey(), resSign)) {
resMap.remove("sign");
return resMap;
} else {
throw new RuntimeException("888888:响应参数不合法");
}
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
}
}