概述
有些安全级别比较高的接口,需要做严格的加密,则可以用AES结合RSA做一个加密处理。RSA是非对称过加密,需要准备一对匹配的公钥私钥文件。
(待完善。。。)
RSA加密算法简介
RSA是最流行的非对称加密算法之一。也被称为公钥加密。它是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
RSA是非对称的,也就是用来加密的密钥和用来解密的密钥不是同一个。
和DES一样的是,RSA也是分组加密算法,不同的是分组大小可以根据密钥的大小而改变。如果加密的数据不是分组大小的整数倍,则会根据具体的应用方式增加额外的填充位。
RSA作为一种非对称的加密算法,其中很重要的一特点是当数据在网络中传输时,用来加密数据的密钥并不需要也和数据一起传送。因此,这就减少了密钥泄露的可能性。RSA在不允许加密方解密数据时也很有用,加密的一方使用一个密钥,称为公钥,解密的一方使用另一个密钥,称为私钥,私钥需要保持其私有性。
RSA被认为是非常安全的,不过计算速度要比DES慢很多。同DES一样,其安全性也从未被证明过,但想攻破RSA算法涉及的大数(至少200位的大数)的因子分解是一个极其困难的问题。所以,由于缺乏解决大数的因子分解的有效方法,因此,可以推测出目前没有有效的办法可以破解RSA。
代码
这里我分了AesUtil、KeyEncryptUtil和SignEncryptUtil三个工具类来处理。
AesUtil
AesUtil 工具类详见我前面写的文章,戳这里
KeyEncryptUtil
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* 秘钥工具类
*
* @author weizhenhui
*/
public class KeyEncryptUtil {
private KeyEncryptUtil() {
}
/**
* 根据密钥对中的公钥字符串,生成公钥
*/
public static PublicKey getPublicKey(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] bytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
return keyFactory.generatePublic(keySpec);
}
/**
* 根据秘钥对中的私钥字符串,生成私钥
*/
public static PrivateKey getPrivateKey(String privateKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] bytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
return keyFactory.generatePrivate(keySpec);
}
/**
* 从流中生成公钥(Pem格式的私钥)
*/
public static PublicKey getKeyPairOfPemPublicKey(InputStream inputStream) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
BufferedReader bufReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String temp;
while ((temp = bufReader.readLine()) != null) {
if (temp.startsWith("--") && temp.endsWith("--")) {
continue;
}
sb.append(temp);
}
return getPublicKey(sb.toString());
}
/**
* 从流中生成私钥
*/
public static PrivateKey getKeyPairOfPemPrivateKey(InputStream inputStream) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
BufferedReader bufReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String temp;
while ((temp = bufReader.readLine()) != null) {
if (temp.startsWith("--") && temp.endsWith("--")) {
continue;
}
sb.append(temp);
}
return getPrivateKey(sb.toString());
}
}
SignEncryptUtil
import com.alibaba.fastjson.JSONObject;
import javax.crypto.*;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.util.*;
/**
* RSA加密工具类
*
* @author weizhenhui
*/
public class SignEncryptUtil {
private SignEncryptUtil() {
}
public static final String KEY_ALGORITHM = "AES";
public static final String KEY_ENCRYPT_ALGORITHM = "RSA";
public static final String CHARSET_NAME = "UTF-8";
/**
* RSA私钥解密
*
* @param encyrptString 密文数据
* @param privateKey 私钥
* @return
*/
public static byte[] decryptKeyByPrivateKey(String encyrptString, PrivateKey privateKey)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(KEY_ENCRYPT_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(Base64.getDecoder().decode(encyrptString));
}
/**
* 生成加密key
*/
public static byte[] getEncryptKey() {
KeyGenerator keyGenerator = null;
// 实例化一个用AES加密算法的密钥生成器
try {
keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("NoSuchAlgorithmException=>" + e.getMessage());
}
// bitSize
keyGenerator.init(128);
// 生成一个密钥。
SecretKey key = keyGenerator.generateKey();
return key.getEncoded();
}
/**
* 通过公钥加密
*
* @param keyBytes AES 秘钥
* @param publicKey 公钥
*/
public static String encryptKeyByPublicKey(byte[] keyBytes, PublicKey publicKey) throws Exception {
// 创建密码器
Cipher cipher = Cipher.getInstance(KEY_ENCRYPT_ALGORITHM);
// 初始化为加密模式的密码器
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 加密
byte[] enBytes = cipher.doFinal(keyBytes);
// 通过Base64转码返回
return Base64.getEncoder().encodeToString(enBytes);
}
/**
* AES加密
*/
public static String encryptData(String data, byte[] keyBytes) {
return AesUtil.encrypt(data, keyBytes);
}
/**
* AES解密
*/
public static String decryptData(String encryptData, byte[] keyBytes) {
return AesUtil.decrypt(encryptData, keyBytes);
}
/**
* 检查签名是否正确
*
* @param content 签名内容
* @param sign 签名比较对象
* @param publicKey 公钥
* @param signType 签名类型
* @return 成功:true,失败:false
*/
public static boolean checkSignByPublicKey(String content, String sign, PublicKey publicKey, String signType) throws Exception {
try {
Signature signature = Signature.getInstance(signType);
signature.initVerify(publicKey);
signature.update(content.getBytes(CHARSET_NAME));
return signature.verify(Base64.getDecoder().decode(sign.getBytes(CHARSET_NAME)));
} catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException | UnsupportedEncodingException e) {
throw new IllegalArgumentException(e.getClass().getSimpleName() + "=>" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
throw new Exception("checkSignByPublicKey() => " + e.getMessage());
}
}
/**
* RSA私钥签名
*/
public static String signByPrivateKey(String content, PrivateKey privateKey, String signType) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
Signature signature = Signature.getInstance(signType);
signature.initSign(privateKey);
signature.update(content.getBytes(CHARSET_NAME));
return new String(Base64.getEncoder().encode(signature.sign()), CHARSET_NAME);
}
/**
* 对多节点的Map对象生成签名字符串(参数名 ASCII 码从小到大排序(字典序)) 全部节点都排序
*
* @param params
* @return
*/
public static final String toString(Map<String, Object> params) {
Iterator<Map.Entry<String, Object>> iterator = params.entrySet().iterator();
Map<String, Object> signMap = newSortedMap(true);
toStringLoop(signMap, iterator);
return new JSONObject(signMap).toJSONString();
}
private static final void toStringLoop(Map<String, Object> jsonObject, Iterator<Map.Entry<String, Object>> iterator) {
while (iterator.hasNext()) {
Map.Entry<String, Object> next = iterator.next();
String key = next.getKey();
Object value = next.getValue();
// 如果参数的值为空不参与签名
if (key == null || "".equals(key) || "sign".equals(key) || value == null || "".equals(value)) {
continue;
}
if (value instanceof Map) {
Map<String, Object> map = (Map<String, Object>) value;
Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
Map<String, Object> child = newSortedMap(true);
toStringLoop(child, iter);
jsonObject.put(key, child);
} else if (value instanceof List) {
List<Object> newList = new ArrayList<>();
for (Object obj : (List<Object>) value) {
if (obj instanceof Map) {
Map<String, Object> map = (Map<String, Object>) obj;
Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
Map<String, Object> child = newSortedMap(true);
toStringLoop(child, iter);
newList.add(child);
}
}
jsonObject.put(key, newList);
} else if (value != null && value.getClass().isArray()) {
List<Object> newList = new ArrayList<>();
for (Object obj : (Object[]) value) {
if (obj instanceof Map) {
Map<String, Object> map = (Map<String, Object>) obj;
Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
Map<String, Object> child = newSortedMap(true);
toStringLoop(child, iter);
newList.add(child);
}
}
jsonObject.put(key, newList);
} else {
jsonObject.put(next.getKey(), next.getValue());
}
}
}
/**
* 实例化一个排序的Map.
*
* @param isAsc 是否升序
* @return
*/
public static Map<String, Object> newSortedMap(final boolean isAsc) {
return new TreeMap<>((o1, o2) -> {
int ret = o1.compareTo(o2);
return isAsc ? ret : -ret;
});
}
}
使用案例
这里写个加密请求第三方接口的案例。这里A是我方,B是第三方。在这之前需要双方交换公钥。
思路:
用第三方的公钥B_public_key_dev.pem
加签、加密请求他接口,他们用自己的私钥B_private_key_dev.pem
验签并解密数据,然后响应;响应数据用我方公钥A_public_key_dev.pem
加签加密返回,我这边得到的响应数据就需要用自己的私钥A_private_key_dev.pem
验签、解密数据。总之,要成对的验签、加密解密数据。
这里RSA是用来加签验签,以防在请求过程中数据被篡改,实际加密请求参数的是用的AES加密,然后AES秘钥是用公钥加密后传输的,这里接收到参数后也是需要用配对的私钥解密得到AES秘钥,方能解密。案例中的请求头参数中reqKey就是加密后的秘钥。
实现代码:
package com.wzh.test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wzh.utils.HttpUtil;
import com.wzh.utils.KeyEncryptUtil;
import com.wzh.utils.SignEncryptUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;
/**
* RSA 加密请求 Test
* @author weizhenhui
* @date 2021/4/30 16:49
*/
public class SignEncryptUtilTest {
public static final Logger logger = LoggerFactory.getLogger(SignEncryptUtilTest.class);
public static final String SUCCESS_CODE = "200";
public static void main(String[] args) throws Exception {
// 请求地址
String requestUrl = "localhost:8080/testSign";
// 公共请求头参数
Map<String, Object> reqMap= new HashMap<>(7);
reqMap.put("requestNo", "20210430173828001");
reqMap.put("requestTimestamp", "2021-04-30 17:38:28");
reqMap.put("version", "1.0");
reqMap.put("sign", "");
reqMap.put("signType", "");
reqMap.put("reqKey", "");
reqMap.put("reqDate", "");
// 接口请求参数
Map<String, Object> reqDataMap= new HashMap<>(3);
reqDataMap.put("username", "weizhenhui");
reqDataMap.put("age", 18);
reqDataMap.put("sex", 1);
reqDataMap.put("occupation", "程序员");
Map<String, Object> returnMap = requestData(requestUrl, reqMap, reqDataMap);
System.out.println(reqDataMap);
}
/**
* @param reqMap 公共请求头参数map
* @param reqDataMap 真正的请求参数map
* @return
* @throws Exception
*/
public static Map<String, Object> requestData(String requestUrl, Map<String, Object> reqMap, Map<String, Object> reqDataMap) throws Exception {
Map<String, Object> returnMap;
String signType = "SHA256WithRSA";
InputStream publicKeyIn = null;
InputStream privateKeyIn = null;
try {
// 生成AES秘钥
byte[] keyBytes = SignEncryptUtil.getEncryptKey();
// 根据.pem格式的秘钥文件获取公钥、私钥
String publicKeyPath = "/key/B_public_key_dev.pem";
String privateKeyPath = "/key/A_private_key_dev.pem";
publicKeyIn = new FileInputStream(new File(publicKeyPath));
privateKeyIn = new FileInputStream(new File(privateKeyPath));
PublicKey publicKey = KeyEncryptUtil.getKeyPairOfPemPublicKey(publicKeyIn);
PrivateKey privateKey = KeyEncryptUtil.getKeyPairOfPemPrivateKey(privateKeyIn);
// 根据公钥得到加密aesKey
String encryptAesKey = SignEncryptUtil.encryptKeyByPublicKey(keyBytes, publicKey);
reqMap.put("reqKey", encryptAesKey);
// 用AES秘钥加密真正的请求参数reqDataMap<>,并添加到公共请求头reqMap<>中
logger.info("请求数据:加密前 =>> " + reqDataMap.toString());
String encryptReqDataString = SignEncryptUtil.encryptData(reqDataMap.toString(), keyBytes);
logger.info("请求数据:加密后 =>> " + encryptReqDataString);
reqMap.put("reqData", encryptReqDataString);
// 生成签名,用于验证请求参数是否被篡改。
// 这里toString()是带排序功能的,请求前后的参数排序要相同,否则验签不通过
String signString = SignEncryptUtil.toString(reqMap);
logger.info("待签报文 =>> " + signString);
String sign = SignEncryptUtil.signByPrivateKey(signString, privateKey, signType);
reqMap.put("sign", sign);
logger.info("请求报文 =>> " + reqMap);
// 请求接口
Map<String, Object> httpResultMap = HttpUtil.httpPost(requestUrl, JSONObject.toJSONString(reqMap));
logger.info("响应报文 =>> " + httpResultMap);
String returnCode = (String) httpResultMap.get("retCode");
String returnMessage = (String) httpResultMap.get("retMessage");
String returnSign = httpResultMap.get("sign").toString();
// 根据返回的code判断是否返回成功
if (!SUCCESS_CODE.equals(returnCode) || StringUtils.isEmpty(returnSign)) {
throw new Exception("接口返回数据有误,retCode=" + returnCode + ", returnMessage= " + returnMessage);
}
// ============ 验证签名 Start ============
// 1、得到返回的签名串sign和签名的类型
String resultSign = (String) httpResultMap.get("sign");
String resultSignType = (String) httpResultMap.get("signType");
// 2、在把返回结果map排序toString()一次,跟前面的保持一致
String resultJsonString = SignEncryptUtil.toString(httpResultMap);
logger.info("响应待签报文 =>> " + resultJsonString);
boolean resultSignResult;
// 3、验证签名
try {
resultSignResult = SignEncryptUtil.checkSignByPublicKey(resultJsonString, resultSign, publicKey, resultSignType);
} catch (Exception e) {
throw new SignatureException("签名错误:" + e.getMessage());
}
logger.info("响应验签结果 =>> " + resultSignResult);
if (!resultSignResult) {
throw new SignatureException("签名不匹配");
}
// ============ 验证签名 End ============
// 验证签名通过后,方可解密数据,操作跟加密对称处理即可
// ============ 解密数据 Start ============
String resKey = (String) httpResultMap.get("respKey");
String resData = (String) httpResultMap.get("respData");
// 发送者的数据用公钥加密,接受者就要用与其一对的秘钥解密数据
// 1、先解密得到AES秘钥
byte[] resKeyBytes = SignEncryptUtil.decryptKeyByPrivateKey(resKey, privateKey);
// 2、再用AES秘钥解密数据
logger.info("响应数据:解密前 =>> " + resData);
String decryptData = SignEncryptUtil.decryptData(resData, resKeyBytes);
logger.info("响应数据:解密后 =>> " + decryptData);
// ============ 解密数据 End ============
// 数据解密完成,得到响应数据
returnMap = JSON.parseObject(decryptData);
} catch (Exception e) {
logger.error("请求接口异常 =>> " + e.getMessage());
e.printStackTrace();
throw new Exception("请求接口异常 =>> " + e.getMessage());
} finally {
try {
if (null != privateKeyIn) {
privateKeyIn.close();
}
if (null != publicKeyIn) {
publicKeyIn.close();
}
} catch (IOException e) {
logger.error("关闭流异常");
}
}
return returnMap;
}
}
编写有误的地方还请指出,共勉~