Rsa加签验签工具
为了方便对外接口加签验签,写了个通用的加签验签工具。
一、上代码
1.1 RsaUtils 代码
PRIVATE_KEY、PUBLIC_KEY 可以在线生成或者通过支付宝工具生成。
在线生成公钥私钥对,RSA公私钥生成
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.meiyuan.food.research.common.exception.AppException;
import com.meiyuan.food.research.common.utils.JsonUtils;
import com.meiyuan.food.research.module.order.dto.OrderSaveDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import java.math.BigDecimal;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
/**
* @author zjson
*/
@Slf4j
public final class RsaUtils {
/**
* 签名字段
*/
public static final String SIGN_KEY = "sign";
private static final String KEY_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final String PRIVATE_KEY = "";
private static final String PUBLIC_KEY = "";
/**
* 获取RSA公钥 根据钥匙字段
*
* @param pubKey 公钥串
* @return 公钥
*/
public static PublicKey getPublicKey(String pubKey) {
if (StringUtils.isBlank(pubKey)) {
return null;
}
pubKey = pubKey.replace("-----BEGIN PUBLIC KEY-----", "");
pubKey = pubKey.replace("-----END PUBLIC KEY-----", "");
pubKey = pubKey.replace("\n", "");
byte[] byteKey = Base64.decodeBase64(pubKey);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(byteKey);
try {
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePublic(x509EncodedKeySpec);
} catch (Exception e) {
log.error("获取公钥出错", e);
throw new AppException("获取公钥出错");
}
}
/**
* 获取RSA私钥(PKCS8)
*
* @param priKey 私钥字符串
* @return 私钥
*/
private static PrivateKey getPrivateKey(String priKey) {
if (StringUtils.isBlank(priKey)) {
return null;
}
priKey = priKey.replace("-----BEGIN PRIVATE KEY-----", "");
priKey = priKey.replace("-----END PRIVATE KEY-----", "");
priKey = priKey.replace("\n", "");
byte[] byteKey = Base64.decodeBase64(priKey);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(byteKey);
try {
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (Exception e) {
log.error("获取私钥出错", e);
throw new AppException("获取私钥出错");
}
}
/**
* 根据 baseSign 对参数进行签名
* @param baseSign 基本签名参数
* @param privateKey 私钥
* @return 签名结果
*/
public static String sign(BaseSign baseSign, String privateKey) {
return sign(objectToTreeMap(baseSign), privateKey);
}
/**
* 对参数进行签名
*
* @param params 有序的TreeMap参数
* @param privateKey 私钥
* @return 签名结果
*/
public static String sign(TreeMap<String, Object> params, String privateKey) {
return sign(Objects.requireNonNull(getSignContent(params)), privateKey);
}
/**
* 私钥签名
*
* @param source 源数据:似 sign=xxx&name=yyy
* @param privateKey 私钥
* @return 签名
*/
public static String sign(String source, String privateKey) {
byte[] sourceData = source.getBytes();
String result = null;
try {
PrivateKey priKey = getPrivateKey(privateKey);
Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
sign.initSign(priKey);
sign.update(sourceData);
byte[] resultData = sign.sign();
result = Base64.encodeBase64String(resultData);
} catch (Exception e) {
log.error("生成签名时发生异常", e);
}
return result;
}
/**
* 将map转换为请求参数
* @param baseSign 基本签名参数
* @return 请求参数 类似 sign=xxx&name=yyyy
*/
public static String getSignContent(BaseSign baseSign) {
return getSignContent(objectToTreeMap(baseSign));
}
/**
* 将map参数转化为请求参数
* @param params treeMap参数
* @return 请求参数 类似 sign=xxx&name=yyyy
*/
public static String getSignContent(TreeMap<String, Object> params) {
if (MapUtils.isEmpty(params)) {
return null;
}
params.remove(SIGN_KEY);
StringBuilder sb = new StringBuilder();
params.forEach((k,v) -> sb.append(k).append("=").append(v).append("&"));
// 删除最后一个&
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
/**
* 公钥验签
*
* @param sign 签名
* @param source 源数据
* @param publicKey 公钥
* @return 通过/不通过
*/
public static boolean validSign(String sign, String source, String publicKey) {
byte[] sourceData = source.getBytes();
boolean result = false;
try {
PublicKey key = getPublicKey(publicKey);
Signature verify = Signature.getInstance(SIGNATURE_ALGORITHM);
verify.initVerify(key);
verify.update(sourceData);
byte[] decoded = Base64.decodeBase64(sign);
result = verify.verify(decoded);
} catch (Exception e) {
log.error("验证签名时发生异常", e);
}
return result;
}
/**
* object 转化为 treeMap
* @param o 参数
* @return map
*/
public static TreeMap<String, Object> objectToTreeMap(Object o) {
if (Objects.isNull(o)) {
return null;
}
return new TreeMap<>(objectToMap(o));
}
/**
* object 转化为 map
* @param o 参数
* @return map
*/
public static Map<String, Object> objectToMap(Object o) {
if (Objects.isNull(o)) {
return null;
}
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Map map = null;
try {
String json = objectMapper.writeValueAsString(o);
map = objectMapper.readValue(json, Map.class);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return map;
}
/**
* 通过参数验证签名是否正确
* @param param 加密参数
* @param publicKey 公钥
*/
public static void validSignByParam(BaseSign param, String publicKey) {
if (!validSign(param.getSign(), getSignContent(param), publicKey)) {
throw new AppException("签名验证失败");
}
}
public static void main(String[] args) {
// 客户端加签
OrderSaveDTO dto = OrderSaveDTO.builder()
.storeId("006007")
.outOrderNumber("MY202107281745001")
.reserveName("张杰")
.reservePhone("15298541236")
.orderPackage("下单套餐")
.unitPrice(new BigDecimal("1000"))
.peopleNumber(10)
.orderAmount(new BigDecimal("10000"))
.orderNickName("zj")
.orderTime(LocalDateTime.now())
.build();
String sign = sign(dto, PRIVATE_KEY);
dto.setSign(sign);
log.info("请求参数:{}", dto);
// 服务端验签
log.info("接受参数:{}", dto);
log.info("验签成功:{}", validSign(dto.getSign(), getSignContent(dto), PUBLIC_KEY));
log.info("验签加密参数:{}", getSignContent(dto));
}
}
1.2 基本签名参数
直接用你的实体继承BaseSign 。
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 基本签名参数
*
* @author Zhang Jie
* @version 1.0.0
* @since 2021/7/21 15:16
*/
@Data
public class BaseSign {
@NotBlank(message = "sign不能为空")
@ApiModelProperty("加密字符串")
private String sign;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "OrderSaveDTO",description = "保存的订单信息")
public class OrderSaveDTO extends BaseSign {
@NotBlank(message = "门店ID不能为空")
@ApiModelProperty("门店ID")
private String storeId;
@NotBlank(message = "订单号不能为空")
@ApiModelProperty("美域甄品订单号")
private String outOrderNumber;
}
// Controller调用
@PostMapping("/save")
@ApiOperation("同步保存订单")
public AppResult save(@Valid @RequestBody OrderSaveDTO param) {
ordersService.save(param);
return AppResult.ok();
}