簽名規則:
1:簽名中出現&,為連接符,非運算符,簽名規則:body體中signature不參與簽名,但header中的timestamp參與body簽名
2:其餘以參數名的字典升序排序(ascii碼升序),且值不為空,並拼接"key=value"格式,多參數之間以&鏈接,得到A
- 值為空,不參與簽名
- 只有數字類型以及字符串類型,參與簽名
- 特別注意:請求參數體中需要增加unitId,用來確定目前客戶識別資訊
3:timestamp=時間戳,得到B,然後B&A得到C
4:將C使用MD5加密並全部字母變為大寫,得到最終值D,即為signature值
5:body中需要增加signature,然後對body中數據,轉為json後,進行encode轉碼,得到E
6:用公司公鑰將E進行加密,得到F,其請求key值為data
- F長度以100長度分割,進行分段加密,加密後的值,需以用","進行拼接
7:最終請求參數結構如下:
//Request header
{
"timestamp": 11111131331,
"trace": "xxxxxx"
}
//Request body
{
"data": F
}
簽名示例:
1:a=1&b=2&c=3×tamp=11111131331,即為A
2:timestamp=11111131331,即為B
3: B&A 即 timestamp=11111131331&a=1&b=2&c=3×tamp=11111131331 為最終C取值
4:將C進行MD5加密並全部字母變為大寫,得到最終簽名密鑰
5:對所有body參數,轉為json後,encode轉碼,得到E
6:RSA加密
- 用公司公鑰將E加密,得到F
- 若E長度>100,則RSA(E[0-100])=E1,RSA[E[剩余長度]]=E2,最終F=E1,E2
//Request header
{
"timestamp": 11111131331,
"trace": "xxxxxx"
}
//Request body
{
"a": 1,
"b": 2,
"c": "3",
"signature": "44b3a042-dd5d-4796-92e1-651927b6ada9"
}
JAVA版加密方式如下,请参考encryptByPublicKey
JAVA版解密方式如下,请参考decrypt4LongTextByPublicKey
package com.cats.common.core.util;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cats.common.core.base.enums.CommonLogType;
import com.cats.common.core.log.LogUtil;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.util.StringUtils;
import javax.crypto.Cipher;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
@Slf4j
public class RSAUtil {
public static final String RSA_ALGORITHM = "RSA";
public static final String UTF8_STR = "UTF-8";
public static final Integer KEY_SIZE = 1024;
public static final String TIME_STAMP = "timestamp";
public static final String JOIN_CHAR_1 = "=";
public static final String JOIN_CHAR_2 = "&";
public static final String JOIN_CHAR_3 = ",";
/**
* 随机生成密钥对
*
* @throws NoSuchAlgorithmException
*/
public static List<String> genKeyPair() {
List<String> result = new ArrayList<>();
try {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到
result.add(publicKeyString);
result.add(privateKeyString);
} catch (NoSuchAlgorithmException e) {
log.error("RSAUtil genKeyPair ", e);
}
return result;
}
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(String str, String publicKey) throws Exception {
String outStr = null;
try {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(UTF8_STR)));
} catch (Exception e) {
log.error("RSAUtil encrypt ", e);
}
return outStr;
}
/**
* RSA加密(私钥)
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encryptByPrivateKey(String str, String privateKey) throws Exception {
String outStr = null;
try {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, priKey);
outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(UTF8_STR)));
} catch (Exception e) {
log.error("RSAUtil encryptByPrivateKey ", e);
}
return outStr;
}
/**
* RSA解密(公钥)
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
public static String decryptByPublicKey(String str, String publicKey) {
String outStr = null;
try {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, pubKey);
outStr = new String(cipher.doFinal(inputByte));
} catch (Exception e) {
log.error("RSAUtil decryptByPublicKey ", e);
}
return outStr;
}
/**
* RSA私钥解密(支持超长文本)
*
* @param str 加密字符串
* @param publicKey 私钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt4LongTextByPublicKey(String str, String publicKey) {
try {
if (StringUtils.isEmpty(str)) {
return null;
}
StringBuilder sb = new StringBuilder();
String[] tempStr = str.split(JOIN_CHAR_3);
for (String s : tempStr) {
if (StringUtils.isEmpty(s)) {
return s;
}
String temp = decryptByPublicKey(s, publicKey);
if (StringUtils.isEmpty(temp)) {
return temp;
}
sb.append(temp);
}
return URLDecoder.decode(sb.toString(), "UTF-8");
} catch (Exception e) {
LogUtil.error(CommonLogType.CODE_ERROR, "decrypt4LongTextByPublicKey解密出现异常", e.getMessage(), e);
return null;
}
}
/**
* 数据加密+签名(私钥)
*
* @param params params
* @param timestamp timestamp
* @param privateKey privateKey
* @return String
* @throws Exception
*/
public static String encryptByPrivateKey(Map<String, Object> params, String timestamp, String privateKey, Map<String, Object> noSignatureMap) throws Exception {
StringBuilder sb = new StringBuilder();
Map<String, Object> sortParams = new TreeMap<>(params);
sortParams.forEach((key, value) -> {
if (U.isNotBlank(value)) {
sb.append(key).append("=").append(value).append("&");
}
});
String sortedBody = sb.substring(0, sb.length() - 1),
salt = "timestamp=" + timestamp,
signatureBeforeMD5 = salt + "&" + sortedBody;
String signature = DigestUtils.md5Hex(signatureBeforeMD5).toUpperCase();
sortParams.put("signature", signature);
if (Objects.nonNull(noSignatureMap)) {
sortParams.putAll(noSignatureMap);
}
String toJson = JSONUtil.toJsonStr(sortParams);
log.info("sortedBody={},salt={},signatureBeforeMD5={},signature={},toJson={}", sortedBody, salt, signatureBeforeMD5, signature, toJson);
Iterable<String> chunks = Splitter.fixedLength(100).split(URLEncoder.encode(toJson, "UTF-8"));
Iterator<String> iterator = chunks.iterator();
StringBuilder sbEncryption = new StringBuilder();
while (iterator.hasNext()) {
String encrypt = encryptByPrivateKey(iterator.next(), privateKey);
sbEncryption.append(encrypt).append(",");
}
Map<String, String> bodyData = Maps.newHashMap();
bodyData.put("data", sbEncryption.substring(0, sbEncryption.length() - 1));
return JSONUtil.toJsonStr(bodyData);
}
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey) {
String outStr = null;
try {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(UTF8_STR));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, priKey);
outStr = new String(cipher.doFinal(inputByte));
} catch (Exception e) {
log.error("RSAUtil decrypt ", e);
}
return outStr;
}
/**
* RSA私钥解密(支持超长文本)
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt4LongText(String str, String privateKey) {
try {
if (StringUtils.isEmpty(str)) {
return null;
}
StringBuilder sb = new StringBuilder();
String[] tempStr = str.split(JOIN_CHAR_3);
for (String s : tempStr) {
if (StringUtils.isEmpty(s)) {
return s;
}
String temp = decrypt(s, privateKey);
//LogUtil.info(CommonLogType.CODE_COMMON, "decrypt4LongText分段解密,原始密文>>>", s, "解密结果>>>", temp);
if (StringUtils.isEmpty(temp)) {
return temp;
}
sb.append(temp);
}
return URLDecoder.decode(sb.toString(), "UTF-8");
} catch (Exception e) {
LogUtil.error(CommonLogType.CODE_ERROR, "decrypt4LongText解密出现异常", e.getMessage(), e);
return null;
}
}
/**
* 验证签名信息
*
* @param params 参数信息
* @param signKey 加密之后key
* @return 延签是否正常
*/
public static boolean verifySign(Map<String, Object> params, String signKey) {
try {
if (StringUtils.isEmpty(params) || StringUtils.isEmpty(signKey)) {
return Boolean.FALSE;
}
Object signValue = "";
if (params.containsKey(signKey)) {
signValue = params.remove(signKey);
}
if (StringUtils.isEmpty(signValue)) {
return Boolean.FALSE;
}
StringBuilder sb = new StringBuilder().append(TIME_STAMP).append(JOIN_CHAR_1).append(params.get(TIME_STAMP));
// 将参数以参数名的字典升序排序
Map<String, Object> sortParams = new TreeMap<>(params);
// 遍历排序的字典,并拼接"key=value"格式
for (Map.Entry<String, Object> entry : sortParams.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (!StringUtils.isEmpty(value) && (value instanceof Number || value instanceof String))
sb.append(JOIN_CHAR_2).append(key).append(JOIN_CHAR_1).append(value);
}
String md5 = DigestUtils.md5Hex(sb.toString()).toUpperCase();
LogUtil.info(CommonLogType.CODE_COMMON, "verifySign验签参数", sb.toString(), md5);
return signValue.equals(md5);
} catch (Exception e) {
LogUtil.error(CommonLogType.CODE_ERROR, "verifySign验签出现问题", e.getMessage(), e);
return false;
}
}
/**
* 数据加密+签名(公钥)
*
* @param params params
* @param timestamp timestamp
* @param publicKey publicKey
* @return String
* @throws Exception
*/
public static String encryptByPublicKey(Map<String, Object> params, String timestamp, String publicKey, Map<String, Object> noSignatureMap) throws Exception {
StringBuilder sb = new StringBuilder();
Map<String, Object> sortParams = new TreeMap<>(params);
sortParams.forEach((key, value) -> {
if (U.isNotBlank(value)) {
sb.append(key).append("=").append(value).append("&");
}
});
String sortedBody = sb.substring(0, sb.length() - 1),
salt = "timestamp=" + timestamp,
signatureBeforeMD5 = salt + "&" + sortedBody;
String signature = DigestUtils.md5Hex(signatureBeforeMD5).toUpperCase();
sortParams.put("signature", signature);
if (Objects.nonNull(noSignatureMap)) {
sortParams.putAll(noSignatureMap);
}
String toJson = JSONUtil.toJsonStr(sortParams);
log.info("sortedBody={},salt={},signatureBeforeMD5={},signature={},toJson={}", sortedBody, salt, signatureBeforeMD5, signature, toJson);
Iterable<String> chunks = Splitter.fixedLength(100).split(URLEncoder.encode(toJson, "UTF-8"));
Iterator<String> iterator = chunks.iterator();
StringBuilder sbEncryption = new StringBuilder();
while (iterator.hasNext()) {
String encrypt = encrypt(iterator.next(), publicKey);
sbEncryption.append(encrypt).append(",");
}
Map<String, String> bodyData = Maps.newHashMap();
bodyData.put("data", sbEncryption.substring(0, sbEncryption.length() - 1));
return JSONUtil.toJsonStr(bodyData);
}
public static void main(String[] args) throws Exception {
//生成公钥和私钥
List<String> list = genKeyPair();
//加密字符串
System.out.println("随机生成的公钥为:" + list.get(0));
System.out.println("随机生成的私钥为:" + list.get(1));
String timestamp = String.valueOf(System.currentTimeMillis());
Map<String, Object> map = Maps.newTreeMap();
map.put("userName", "abc");
map.put("currency", "CNY");
map.put("timestamp", timestamp);
String encryptStr = encryptByPublicKey(map, timestamp, list.get(0), null);
System.out.println("公钥加密后信息," + encryptStr);
JSONObject jsonObject = JSONUtil.parseObj(encryptStr);
String decryptStr = decrypt4LongText(jsonObject.getStr("data"), list.get(1));
System.out.println("【私钥】解密密后的字符串为:" + decryptStr);
}
}