记一次接口数据签名设计(RSA非对称加密)
Posted 不吃肉的犟驴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次接口数据签名设计(RSA非对称加密)相关的知识,希望对你有一定的参考价值。
1.为什么要加密?
加密的原因:保证数据安全
加密必备要素:1、明文/密文 2、秘钥 3、算法
2.加密方式:对称加密和非对称加密(已下讲述)
(一)对称加密(Symmetric Cryptography)
(二)非对称加密(Asymmetric Cryptography)
package com.ym.auth.framework.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author wenbo
* @Date 2020/6/16 14:32
**/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceSign {
boolean isSign() default false;
}
(2)加解密算法封装
package com.ym.auth.common.utils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
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;
/**
* @Author wenbo
* @Date 2020/6/16 14:57
**/
public class RSACryptUtil {
/**
* 根据公钥字符串加载公钥
*
* @param publicKeyStr 公钥字符串
* @return
* @throws Exception
*/
public static RSAPublicKey loadPublicKey(String publicKeyStr) throws Exception {
try {
byte[] buffer = javax.xml.bind.DatatypeConverter.parseBase64Binary(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法", e);
} catch (InvalidKeySpecException e) {
throw new Exception("公钥非法", e);
} catch (NullPointerException e) {
throw new Exception("公钥数据为空", e);
}
}
/**
* 根据私钥字符串加载私钥
*
* @param privateKeyStr 私钥字符串
* @return
* @throws Exception
*/
public static RSAPrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
try {
byte[] buffer = javax.xml.bind.DatatypeConverter.parseBase64Binary(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法", e);
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法", e);
} catch (NullPointerException e) {
throw new Exception("私钥数据为空", e);
}
}
/**
* 公钥加密
*
* @param publicKey 公钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception {
if (publicKey == null) {
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(plainTextData);
return base64ToStr(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此加密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("加密公钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("明文长度非法");
} catch (BadPaddingException e) {
throw new Exception("明文数据已损坏");
}
}
/**
* 私钥解密
*
* @param privateKey 私钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception {
if (privateKey == null) {
throw new Exception("解密私钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(cipherData);
return new String(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此解密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("解密私钥非法,请检查");
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
throw new Exception("密文长度非法");
} catch (BadPaddingException e) {
e.printStackTrace();
throw new Exception("密文数据已损坏");
}
}
public static String base64ToStr(byte[] b) {
return javax.xml.bind.DatatypeConverter.printBase64Binary(b);
}
public static byte[] strToBase64(String str) {
return javax.xml.bind.DatatypeConverter.parseBase64Binary(str);
}
}
(3)拦截器拦截需要加密的接口
package com.ym.auth.framework.interceptor;
import com.ym.auth.common.utils.APIResponseBuilder;
import com.ym.auth.common.utils.RSACryptUtil;
import com.ym.auth.common.utils.ServletUtils;
import com.ym.auth.domain.entity.SysSign;
import com.ym.auth.framework.annotation.ServiceSign;
import com.ym.auth.framework.condition.Query;
import com.ym.auth.service.ISysSignSV;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
/**
* Created with IntelliJ IDEA.
* Description:
* Author: wenbo
* Date: 2019-04-03
* Time: 10:11
* 接口签名拦截器
*/
@Component
public class ServiceSignInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGE = LoggerFactory.getLogger(ServiceSignInterceptor.class);
@Autowired
private ISysSignSV iSysSignSV;
/**
*以下参数在yml中配置获取
*/
/**
* 签名开关
*/
@Value("${config-center.sign.enabled}")
private boolean enabled;
/**
* 签名超时时间
*/
@Value("${config-center.sign.timeout}")
private long timeout;
@Value("${config-center.sign.serverEnabled}")
private boolean serverEnabled;
/**
* 签名超时时间
*/
@Value("${config-center.sign.salt}")
private String salt;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 验签开关
if (!serverEnabled) {
return true;
}
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 签名注解判断
HandlerMethod handlerMethod = (HandlerMethod) handler;
ServiceSign methodApiSign = handlerMethod.getMethodAnnotation(ServiceSign.class);
if (methodApiSign != null && !methodApiSign.isSign()) {
//方法签名为false
return true;
} else if (methodApiSign == null ) {
//方法无签名,且类无签名或类签名为false
return true;
}
String appKey = request.getHeader(SignConstant.APPKEY);
String sign = request.getHeader(SignConstant.SIGN);
String timestampStr = request.getHeader(SignConstant.TIMESTAMP);
LOGGE.info("内网调用签名验证appKey:{}",appKey);
// 校验参数完整性
if (StringUtils.isBlank(appKey) || StringUtils.isBlank(timestampStr) || StringUtils.isBlank(sign)) {
LOGGE.debug("请求参数不完整[appKey:{}][timestamp:{}][sign:{}]", appKey, timestampStr, sign);
ServletUtils.write(response, APIResponseBuilder.failWithMsg("请求签名参数不完整"), MediaType.APPLICATION_JSON_UTF8_VALUE);
return false;
}
// 校验接口调用是否超时
long timestamp = Long.valueOf(timestampStr);
long currTimestamp = System.currentTimeMillis();
if (currTimestamp - timestamp > TimeUnit.MINUTES.toMillis(timeout)) {
LOGGE.debug("接口调用超时");
ServletUtils.write(response, APIResponseBuilder.failWithMsg("签名时间戳超时"), MediaType.APPLICATION_JSON_UTF8_VALUE);
return false;
}
//查询该标志下的私钥解密
SysSign sysSign = iSysSignSV.selectSingleByModel(
Query.init(SysSign.class).andEq("appKey",appKey).get());
String privateKey = sysSign.getAppSecret();
if (StringUtils.isBlank(privateKey)){
ServletUtils.write(response, APIResponseBuilder.failWithMsg("appKey错误"), MediaType.APPLICATION_JSON_UTF8_VALUE);
LOGGE.info("appKey 可能被更改,当前值:{}",appKey);
}
String signDb = sysSign.getSign();
String decryptData= RSACryptUtil.decrypt(RSACryptUtil.loadPrivateKey(privateKey),RSACryptUtil.strToBase64(sign));
String str = timestampStr + signDb;
if (!str.equals(decryptData)){
ServletUtils.write(response, APIResponseBuilder.failWithMsg("请求签名错误"), MediaType.APPLICATION_JSON_UTF8_VALUE);
LOGGE.debug("请求签名错误{}",decryptData);
return false;
}
LOGGE.debug("请求签名成功服务器:{}",signDb);
return true;
}
/**
* 签名常量
*/
public interface SignConstant {
String TIMESTAMP = "timestamp";
String SIGN = "sign";
String APPKEY = "appKey";
}
}
如果你喜欢本文
请长按二维码,关注 不吃肉的犟驴
以上是关于记一次接口数据签名设计(RSA非对称加密)的主要内容,如果未能解决你的问题,请参考以下文章
SpringCloud Gateway API接口安全设计(加密 签名)