对接外部系统,为了安全,需要对请求和响应的报文进行加密和解密。步骤如下:
1. 客户端和服务端各自生成非对称密钥对,并交换公钥;再约定对称加密的key。
2. 客户端使用对称加密对报文加密成密文message后,再对密文提取摘要,然后用私钥对密文进行签名得到sign,再把message和sign通过请求传给服务端。
3. 服务端接收后用客户端的公钥验证签名,签名通过后,用对称加密的密钥key进行解密。
4. 服务端处理完业务后,将返回结果用对称加密的key加密完成,提取摘要,用服务端的私钥对摘要进行签名,然后返回客户端。
代码实现如下:
一、写一个过滤器,过滤方法如下:
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
if(httpRequest.getServletPath().startsWith(externalApiPath)) {
// 如果是系统间调用的请求
ServletInputStream in = servletRequest.getInputStream();
byte[] bytes = in.readAllBytes();
String encryptContent = new String(bytes, StandardCharsets.UTF_8);
MessageSign messageSign = JSON.parseObject(encryptContent,MessageSign.class);
// 取出各对接方的公钥和对称加密密钥
String clientPublicKey = null;
String clientKey = null;
if(messageSign.getName().equals(x1)) {
clientPublicKey = x2;
clientKey = x3;
}
if(ObjectUtil.isNotEmpty(messageSign)
&&ObjectUtil.isNotEmpty(messageSign.getSign())) {
// 验签
String sign = messageSign.getSign();
SM2 smVerify = new SM2(null, clientPublicKey);
String digestHex = SmUtil.sm3(messageSign.getMessage());
boolean verify = smVerify.verifyHex(digestHex, sign);
if(verify) {
// 如果签名验证通过,解密报文
SymmetricCrypto sm4 = SmUtil.sm4(HexUtil.decodeHex(clientKey));
String plainText = sm4.decryptStr(messageSign.getMessage(),
CharsetUtil.CHARSET_UTF_8);
// 生成明文后,执行后面方法
SmRequest smRequest = new SmRequest((HttpServletRequest)servletRequest, new ByteArrayInputStream(plainText.getBytes()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmResponse smResponse = new SmResponse((HttpServletResponse)response, out);
chain.doFilter(smRequest, smResponse);
// 加密返回报文
String responseMessage = sm4.encryptHex(out.toString());
SM2 serverVerify = new SM2(privateKeyServer, null);
String serverDigestHex = SmUtil.sm3(responseMessage);
String serverSign = serverVerify.signHex(serverDigestHex);
String serverEncryptContent = String.format("{\"sign\": \"%s\", \"message\":\"%s\"}", serverSign, responseMessage);
// 将加密后的报文返回给客户端
response.getWriter().write(serverEncryptContent);
response.getWriter().flush();
return;
}
}
// 如果签名验证不通过,返回错误信息给客户端。
response.getWriter().print(String.format("{\"msgCode\":%s, \"message\":%s}", CodeConstant._SUCCESSFUL_CODE, "报文格式不正确"));
} else {
// 如果是正常的请求。
chain.doFilter(httpRequest, response);
}
}
二、对应的请求类如下:
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;
/**
* 自定义请求。解密报文后,生成明文的新请求。
*/
public class SmRequest extends HttpServletRequestWrapper {
private ByteServletInputStream servletInputStream;
public SmRequest(HttpServletRequest request, ByteArrayInputStream byteArrayInputStream) {
super(request);
this.servletInputStream = new ByteServletInputStream(byteArrayInputStream);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return servletInputStream;
}
public static class ByteServletInputStream extends ServletInputStream {
private ByteArrayInputStream byteArrayInputStream;
public ByteServletInputStream(ByteArrayInputStream byteArrayInputStream) {
this.byteArrayInputStream = byteArrayInputStream;
}
@Override
public boolean isFinished() {
return byteArrayInputStream.available() <= 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
}
}
三、对应的响应类如下:
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;
public class SmResponse extends HttpServletResponseWrapper {
private SmResponse.ByteServletOutputStream servletOutputStream;
public SmResponse(HttpServletResponse response, ByteArrayOutputStream byteArrayOutputStream) {
super(response);
this.servletOutputStream = new SmResponse.ByteServletOutputStream(byteArrayOutputStream);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return servletOutputStream;
}
public static class ByteServletOutputStream extends ServletOutputStream {
private ByteArrayOutputStream byteArrayOutputStream;
public ByteServletOutputStream(ByteArrayOutputStream byteArrayOutputStream) {
this.byteArrayOutputStream = byteArrayOutputStream;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
byteArrayOutputStream.write(b);
}
}
}
附生成密钥的方法:
public static void main(String[] args) throws UnsupportedEncodingException {
byte[] key = KeyUtil.generateKey(SM4.ALGORITHM_NAME, 128).getEncoded();
String hexKey = HexUtil.encodeHexStr(key);
System.out.println("hexKey:" + hexKey);
SymmetricCrypto sm4 = SmUtil.sm4(HexUtil.decodeHex(hexKey));
SymmetricCrypto sm4 = SmUtil.sm4(HexUtil.decodeHex(hexKey));
String encryptContent = sm4.encryptHex("\"kevin test\"");
KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
System.out.println("privateKey:" + HexUtil.encodeHexStr(privateKey));
System.out.println("publicKey:" + HexUtil.encodeHexStr(publicKey));
SM2 smVerify = new SM2(privateKey, null);
String digestHex = SmUtil.sm3(encryptContent);
String sign = smVerify.signHex(digestHex);
System.out.println(String.format("{\"sign\": \"%s\", \"message\":\"%s\"}", sign, encryptContent));
}