最近的工作是用户需要提现操作,所以需要对接第三方打款接口,但是交互过程需要加解密这个复杂的过程,然后就梳理学习了一番,那就把RSA+AES混合加密形式并对数据进行加签讲一下,也会贴上相应的代码。
1.RSA密钥
RSA密钥很重要,开始加密的起源第一步。
1.首先我们通过一些工具可以生成RSA密钥对,Java也有生成密钥对的代码,密钥对顾名思义就是一对钥匙,我自己平台生成RSA时会有公钥和私钥,我们会把自己平台的公钥交给第三方,私钥自己保存,同样第三方按照规则生成RSA公私钥,给我们平台公钥,自己保留私钥, 这样更安全,后续加签加密都是需要RSA操作的。
2.第三方会给相应的加密规范,我们生成RSA密钥长度为2048位,填充模式为RSA/ECB/PKCS1Padding。
2.AES密钥
AES主要是对明文数据进行加密的功能,我们加密最多的就是单用AES进行加密,但是这样安全性不高。
1.第三方给我们平台规范,AES秘钥长度为128,填充模式为AES/ECB/PKCS5Padding,而且每次会话的AES秘钥随机生成
3.SIGN(签名)
这里签名是对明文数据进行加数字签名,变成像密文一样的字符串,RSA是保证数据传输安全,AES是对请求数据加密,签名那是为什么呢???签名是为了对数据检验,如果传输过程中对数据更改,那么第三方进行验签的时候就会不通过。
第三方签名的规则是“SHA256withRSA”对加密前的明文数据进行签名。
4.RSA+AES+签名流程
上面三个名词说完大概有一个印象,但是他们怎么互相合作生成最终的加密数据呢?先上个流程图
合作方就是我们平台,佳薪棠就是第三方。
可能看了这个流程图还是蒙蔽,没错我刚开始也是,需要缕清怎么回事还是很费事的,
我平台请求要做的事情
1.因为我们要请求操作给第三方,所以第三方规定了请求的参数必须是哪些,所以aes密钥包含在所说的参数里,并且加签明文数据也需要AES密钥,所以流程图第一个AES密钥生成就应该在前面。
2.合作方RSA私钥加签?我们有规则加签之前获取所有请求参数,不包含字节类型参数,如文件、字节流,剔除sign,剔除值为空的参数,并按照第一字符的键值ASCII码递增排序(字符升序排序),如果遇到相同字符则按照第二字符的键值ASCII码递增排序,以此类推,然后就可以用我自己平台的RSA私钥进行加签了。
3.AES对请求体进行加密?用1这个步骤的AES密钥进行AES明文加密。
4.第三方公钥加密AES密钥?这里就是用的第三方RSA公钥对1这个步骤的AES密钥进行加密。
代码如下:
/**
* 对请求的报文进行加密处理
* 除了异步通知需要调用的统一加密数据
*
* @param myReqData app端提现要传的数据
* @return
* @throws Exception
* @Author df
*/
public JSONObject createRequestJson(JSONObject myReqData) throws Exception {
JSONObject jxtReqData = new JSONObject();
//组装第三方规定的请求的数据
jxtReqData.put("signType", "RSA");
jxtReqData.put("version", VERSION);
jxtReqData.put("mchId", myReqData.get("mchId"));
myReqData.remove("mchId");
myReqData.remove("version");
jxtReqData.put("reqId", System.currentTimeMillis());
jxtReqData.put("reqTime", TimeUtils.getTimeNowFormat("yyyyMMddHHmmss"));
jxtReqData.put("reqData", myReqData.toString());
jxtReqData.put("aes", AESUtil.generateAES());
// 调用加密操作
Map<String, String> map = encryptionService(jxtReqData, myReqData);
jxtReqData.put("reqData", map.get("reqData"));
jxtReqData.put("sign", map.get("sign"));
jxtReqData.put("aes", map.get("aes"));
return jxtReqData;
}
上面文字叙述的加密过程代码
/**
* 拆开加密逻辑操作
* 1.加薪棠通知我们接口情况需要
*
* @param waitSignData 构造待验签字符串
* @param paramData 请求或响应报文体
* @Author df
* @Date 2020/8/12 13:10
*/
public Map<String, String> encryptionService(JSONObject waitSignData, JSONObject paramData) throws Exception {
Map<String, String> data = new HashMap<>();
String dataForm = buildDataForm(waitSignData);
// 获取aes密钥
String aesKey = waitSignData.get("aes").toString();
// RSA私钥加签
String sign = RSAUtils.sign(dataForm,"MY_RSA_PRIVATE");
// 佳薪棠RSA公钥加密AES公钥
String encAes = RSAUtils.encrypt(aesKey,"JXT_RSA_PUBLIC");
// AES对报文体进行加密
String encString = AESUtil.encrypt(paramData.toString(), aesKey);
data.put("sign", sign);
data.put("aes", encAes);
data.put("reqData", encString);
return data;
}
// 获取当前时间并格式化
public static String getTimeNowFormat(String format) {
if (format == null || "".equals(format)) {
format = "yyyy-MM-dd HH:mm:ss";
}
String now = DateTimeFormatter.ofPattern(format).format(LocalDateTime.now());
return now;
}
加签前对数据进行规范
/**
* 获取待签名字符串
*
* @return
*/
public static String buildDataForm(Map params) {
if (params.containsKey(SIGN)) {
params.remove(SIGN);
}
StringBuilder sb = new StringBuilder();
//使用TreeMap对key按照字典升序排序
TreeMap<String, Object> sortedMap = new TreeMap(params);
//拼接为key=value&key2=value2形式
for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
if (entry.getValue() == null) {
//空值不参与签名
continue;
}
sb.append(entry.getKey()).append("=");
sb.append(entry.getValue()).append("&");
}
sb.deleteCharAt(sb.lastIndexOf("&"));
return sb.toString();
}
别着急哈,后面再上RSA和AES工具类,咱们在这里继续讲下我请求完第三方,第三方操作完后也会把返回结果进行同样操作传输给我,所以返回的数据是加密的,我需要解析。返回的格式与字段都是第三方规定好的,我需要根据规定进行相应解析。
解密一定要按顺序进行
1.先用自己RSA私钥解密AES密钥,获得响应报文AES密钥
2.经过第1步骤就直接获取到了AES密钥,用这个AES的密钥进行AES解密操作获取明文数据
3.用第三方的RSA公钥对返回并解密的数据进行验签操作。验签通过整个流程结束
代码如下:
/**
* 对返回的数据进行解析
*
* @param jxtResult 返回结果体
* @Author df
* @Date 2020/8/8 11:24
*/
public Map<String, String> responseParsing(JSONObject jxtResult) throws Exception {
// 第三方返回的通用数据字段
Map<String, Object> rtMap = new HashMap<>();
rtMap.put("respData", jxtResult.get("respData"));
rtMap.put("sign", jxtResult.get("sign"));
rtMap.put("signType", "RSA");
rtMap.put("version", jxtResult.get("version"));
rtMap.put("mchId", jxtResult.get("mchId"));
rtMap.put("reqId", jxtResult.get("reqId"));
rtMap.put("respTime", jxtResult.get("respTime"));
rtMap.put("aes", jxtResult.get("aes"));
// 调用解密
Map<String, String> map = decryptService(jxtResult, keyFilePre);
return map;
}
解密操作
/**
* 拆开解密逻辑操作
* 1.加薪棠通知我们接口情况需要
* 2.一定遵守解析流程
* 2.1 先RSA私钥解密AES密钥
* 2.2 AES解密响应报文
* 2.3 构造验签数据,并验签
*
* @param paramData 请求或响应报文体
* @Author df
* @Date 2020/8/12 11:19
*/
public Map<String, String> decryptService(JSONObject paramData) throws Exception {
Map<String, String> resultMap = new HashMap<>();
// 自己RSA私钥解密AES密钥,获得响应报文AES密钥
String decAES = RSAUtils.decrypt(paramData.get("aes").toString(), super.readRsaPrivateKey(RSA_PRIVATE_KEY));
if (decAES != null) {
// AES解密响应 业务报文respData
String decString = AESUtil.decrypt((String) paramData.get("respData"), decAES);
paramData.put("respData", decString);
paramData.put("aes", decAES);
String sign = paramData.get("sign").toString();
paramData.remove("code");
paramData.remove("msg");
// 构造待验签字符串
String dataForm = buildDataForm(paramData);
// 验签结果
boolean isverifyPass = RSAUtils.verifySign(dataForm, sign, super.readRsaPrivateKey(JXT_RSA_PUBLIC_KEY));
System.out.println("验签结果: " + isverifyPass);
if (isverifyPass) {
// 返回结果
resultMap.put("code", String.valueOf(ReqAnswerCodeEnum.SUCCESS.getCode()));
resultMap.put("respData", decString);
} else {
// 返回结果
resultMap.put("code", ReqAnswerCodeEnum.SIGN_CHECK_ERROR.getCode() + "");
resultMap.put("msg", ReqAnswerCodeEnum.SIGN_CHECK_ERROR.getMessage());
}
return resultMap;
} else {
resultMap.put("code", "0001");
resultMap.put("msg", "获取业务报文失败");
return resultMap;
}
}
AES工具类代码
package com.xiyin.finance.jiaxintang.util.encryption;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.tomcat.util.codec.binary.Base64;
/**
* 随机生成AES
*
* @author zhangbj
* @author df
* @version 1.0
* @date 2020/7/31 16:07
*/
public abstract class AESUtil {
public static final String ALG_AES = "AES";
public static final String CHARSET_UTF8 = "utf-8";
public static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";
public static String generateAES() {
try {
KeyGenerator keygen = KeyGenerator.getInstance(ALG_AES);
SecretKey desKey = keygen.generateKey();
return Base64.encodeBase64String(desKey.getEncoded());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* AES对报文体进行加密并Base64位编码
*
* @param content
* @param password
* @return
* @throws Exception
*/
public static String encrypt(String content, String password) throws Exception {
SecretKeySpec key = new SecretKeySpec(password.getBytes(CHARSET_UTF8), ALG_AES);// 转换为AES专用密钥
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);// 创建密码器
byte[] byteContent = content.getBytes(CHARSET_UTF8);
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化为加密模式的密码器
byte[] result = cipher.doFinal(byteContent);// 加密
return Base64.encodeBase64String(result);
}
/**
* AES解密报文
*
* @param content AES加密过过的内容
* @param password 加密时的密码
* @return 明文
*/
public static String decrypt(String content, String password) throws Exception {
SecretKeySpec key = new SecretKeySpec(password.getBytes(CHARSET_UTF8), ALG_AES);// 转换为AES专用密钥
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化为解密模式的密码器
byte[] result = cipher.doFinal(Base64.decodeBase64(content));
return new String(result); // 明文
}
}
RSA工具类代码
package com.xiyin.finance.jiaxintang.util.encryption;
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.*;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author zhangbj
* @author df
* @version 1.0
* @date 2020/8/3 9:42
*/
public abstract class RSAUtils {
private static final String ALGORITHM_RSA = "RSA";
private static final String SHA256WithRSA = "SHA256WithRSA";
/**
* 私钥对报文签名并Base64编码(加密前报文)
*
* @param content 加密前报文
* @param priKey RSA私钥
* @return
*/
public static String sign(String content, String priKey) throws Exception {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(priKey));
KeyFactory fac = KeyFactory.getInstance(ALGORITHM_RSA);
RSAPrivateKey privateKey = (RSAPrivateKey) fac.generatePrivate(keySpec);
Signature sigEng = Signature.getInstance(SHA256WithRSA);
sigEng.initSign(privateKey);
sigEng.update(content.getBytes());
byte[] signature = sigEng.sign();
return Base64.encodeBase64String(signature);
}
/**
* 私钥数字签名验签(对加密前的数据)
*
* @param content 加密前报文
* @param sign 签名
* @param pubKey 公钥
* @return
*/
public static boolean verifySign(String content, String sign, String pubKey) {
try {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(pubKey));
KeyFactory fac = KeyFactory.getInstance(ALGORITHM_RSA);
RSAPublicKey rsaPubKey = (RSAPublicKey) fac.generatePublic(keySpec);
Signature sigEng = Signature.getInstance(SHA256WithRSA);
sigEng.initVerify(rsaPubKey);
sigEng.update(content.getBytes());
return sigEng.verify(Base64.decodeBase64(sign));
} catch (Exception e) {
return false;
}
}
/**
* 私钥解密
*
* @param content 已加密数据
* @param pubKey 私钥
* @return
* @throws Exception
*/
public static String encrypt(String content, String pubKey)
throws Exception {
PublicKey publicKey = getPublicKeyFromX509(pubKey);
Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeBase64String(cipher.doFinal(content.getBytes()));
}
private static PublicKey getPublicKeyFromX509(String pubKey) throws Exception {
InputStream ins = new ByteArrayInputStream(pubKey.getBytes());
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
byte[] bytes = readAsString(ins).getBytes();
return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(bytes)));
}
/**
* 内部私钥解密AES报文
*
* @param content 已加密数据
* @param priKey 内部私钥
* @return
* @throws Exception
*/
public static String decrypt(String content, String priKey)
throws Exception {
PrivateKey privateKey = getPrivateKeyFromPKCS8(priKey);
Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(Base64.decodeBase64(content)));
}
private static PrivateKey getPrivateKeyFromPKCS8(String priKey) throws Exception {
InputStream ins = new ByteArrayInputStream(priKey.getBytes());
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
byte[] bytes = readAsString(ins).getBytes();
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(bytes)));
}
private static String readAsString(InputStream ins) throws IOException {
Reader reader = new InputStreamReader(ins);
StringWriter writer = new StringWriter();
char[] chars = new char[2048];
int length;
while ((length = reader.read(chars)) >= 0) {
writer.write(chars, 0, length);
}
return writer.toString();
}
}
以上就是加解密全部过程!