简介
通过整合Spring Boot与RSA加密算法,你可以实现安全的数据传输和保护敏感信息。确保密钥的安全管理,以及在加密和解密过程中处理异常,是确保系统安全性的关键步骤。
一.Spring Boot 整合 RSA
可以自己生成一个公钥和私钥,也可以在网站中在线生成:在线RSA密钥对生成工具 - UU在线工具
application.yml
# 数据加密
data-encryption:
public-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD15L5jt1JxlUWcA6Om4HT7DA7P4wvrWaJ3weypmZky13AGxG3onOW5ijQWvW/GIWx1enN2EFhg9haQC3MVA1007PtR7zE0gZJhbsj8G25sAtiaX8nEfKaTa5WjAeZME0Jr4THfnek4ypqOSgObCvs/cvdiJlnw3fK8is41Nr4muQIDAQAB
private-key: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAPXkvmO3UnGVRZwDo6bgdPsMDs/jC+tZonfB7KmZmTLXcAbEbeic5bmKNBa9b8YhbHV6c3YQWGD2FpALcxUDXTTs+1HvMTSBkmFuyPwbbmwC2JpfycR8ppNrlaMB5kwTQmvhMd+d6TjKmo5KA5sK+z9y92ImWfDd8ryKzjU2via5AgMBAAECgYEAuw/+qxtuk8wsfYjjOfOt6sJ8jjg6/Btflt2nrkqUmnH83pkWyAx8U09JWzdYThFlUmYKann60iDEf2bGcOjzQO9t/zmR27NShklT5Vs/cEJ769XQDc34uUyabYNpvpPPAxgrMAWXUZ2lGiJsmu2qpdR/9mBiDaIxtv6F2WdapFECQQD/hF7qlksJbGXlFzSUlExKdpCuHU4cTFUnoG+EFPowHGJOtB6OON4Pz+Qh+qwJwULfV0vuoRYExLGJx/4hmf/NAkEA9lu3eSgrwdcYY/LNMdkv0WCvkPdf0c+ZpqHpKu/4BwgovnVFSnQfnTzkWveY29kiNpC8FjX/UZoSFrGGJ9tenQJABySEIrqgzlqhXo4PgjfDCjHWSYe4L0JS/xCVDXQoqj+g3+JZGFCupHKDRg/jz0H/xI6EKN+H5go5jEhy6i2L4QJADgR/9wvbGYd0otNA67Iw71fyBWyd7iFC2+FglAndcgD72IJxs9/RoK/DSo3GC9RaBwLLQPRHl8wNdW8adBAeXQJAI+DMwPowmxzkSM+zA9JNUMy8vMzBBIpsJJ6WXgMPn/sEd+s5d2CSe7/Q1+Bjv6xvqKKjBjmke3dJs8o3VnN5Tg==
注意:公钥和私钥要放为一行,不然会报错。
RequestBodyAdvice
我们在请求之前处理数据,我们与前端的对接是
请求体是否需要解密,根据请求头里的 Encryption-Type 字段判断,为 1 时表示需要解密,为 0 或者没有这个字段 表示不需要解密
import com.general.exception.UnifiedException;
import com.general.tools.RSAUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
/**
* 请求前数据处理
*/
@ControllerAdvice("com.general.controller")
@Slf4j
public class RsaDecodeRequestBodyAdvice implements RequestBodyAdvice {
@Value("${data-encryption.private-key}")
private String privateKay;
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
HttpHeaders headers = httpInputMessage.getHeaders();
String encryptionType = headers.getFirst("Encryption-Type");
boolean decode = "1".equals(encryptionType);
if (decode) {
log.info("对方法method :【" + methodParameter.getMethod().getName() + "】请求体数据进行解密");
return new MyHttpInputMessage(httpInputMessage);
} else {
return httpInputMessage;
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
class MyHttpInputMessage implements HttpInputMessage {
private HttpHeaders httpHeaders;
private InputStream inputStream;
public MyHttpInputMessage(HttpInputMessage httpInputMessage) throws IOException {
this.httpHeaders = httpInputMessage.getHeaders();
String easpString = easpString(IOUtils.toString(httpInputMessage.getBody(), "UTF-8"));
this.inputStream = IOUtils.toInputStream(RSAUtils.decryptDataOnJava(easpString, privateKay));
}
@Override
public InputStream getBody() throws IOException {
return this.inputStream;
}
@Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
/**
* @param requestData
* @return
*/
public String easpString(String requestData) {
if (requestData != null && !requestData.isEmpty()) {
String prefix = "{\"requestData\":";
if (!requestData.startsWith(prefix)) {
throw new UnifiedException("数据解密失败!");
}
int openIndex = prefix.length();
int closeIndex = requestData.length() - 1;
if (closeIndex <= openIndex) {
throw new UnifiedException("数据参数格式错误!");
}
return requestData.substring(openIndex, closeIndex);
}
return null;
}
}
}
ResponseBodyAdvice
我们对返回给前端的数据进行加密,我们与前端的对接是检查请求头是否有 PublicKey 字段,如果有则用其加密返回数据,如果没有就直接明文返回(PublicKey为前端公钥)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.general.tools.RSAUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 返回数据加密
*/
@Component
@ControllerAdvice("com.general.controller")
@Slf4j
public class ResponseBodyAdvice implements ResponseBodyAdvice {
@Value("${data-encryption.public-key}")
private String publicKey;
/**
* 是否支持这个类
*/
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
// 默认是不使用,改为true
return true;
}
/**
* 返回前处理
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
log.info("------响应数据加密-------");
String publicKeyHeader = serverHttpRequest.getHeaders().getFirst("PublicKey");
if (publicKeyHeader != null) {
log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
ObjectMapper objectMapper = new ObjectMapper();
try {
String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
return RSAUtils.encryptByPublicKey(result.getBytes(), publicKeyHeader).toString();
} catch (Exception e) {
e.printStackTrace();
log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密出现异常:" + e.getMessage());
return encryptErrorData(e.getMessage());
}
}
return body;
}
// 加密错误信息
private String encryptErrorData(String errorMessage) {
// 这里可以根据需要加密错误信息
return errorMessage;
}
}
RSAUtils
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RSAUtils {
public static final String KEY_ALGORITHM = "RSA";
private static final int MAX_ENCRYPT_BLOCK = 245;
private static final int MAX_DECRYPT_BLOCK = 1280000;
/**
* 私钥解密
*/
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = encryptedData.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(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* java端私钥解密
*/
public static String decryptDataOnJava(String data, String PRIVATEKEY) {
String temp = "";
try {
byte[] rs = Base64.decodeBase64(data);
temp = new String(RSAUtils.decryptByPrivateKey(rs, PRIVATEKEY),"UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return temp;
}
/**
* 公钥加密
*/
public static String encryptByPublicKey(byte[] data, String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.decodeBase64(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes));
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = data.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, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return Base64.encodeBase64String(encryptedData);
}
}
二.加密测试
三.总结
RSA加密,基于大数因子分解难题,使用公钥加密、私钥解密,确保数据安全传输。密钥一对生成,非对称设计提升安全性,广泛应用于网络通信加密。参考网上的案例进行修改。