对称加密:加密解密使用相同的密钥串来完成
非对称加密:分为公钥(publicKey)与私钥(privateKey)
大致步骤
1)服务端使用RSA算法生成密钥对,其中公钥给调用方,私钥保存到服务器或者服务器数据库中,防止泄露
2)请求前,对约定的参数使用公钥进行加密,并将加密结果作为参数传递给服务端
3)服务端接收到参数,对签名部分使用私钥解密,得到明文数据,并验证,再对数据进行相应处理
此处以同步接口一为例说明:
{
param1 : xxx, // 业务参数1
param2 : xxx, // 业务参数2
param3 : xxx, // 业务参数2
nonce: uuidStr, // 取32位uuid,用于不可重放校验
timestamp: yyyyMMddHHmmss, // 时间戳
sign : sign("nonce=uuidStr×tamp=yyyyMMddHHmmss¶m1=xxx¶m2=xxx¶m3=xxx")
}
param1、param2、param3是业务参数入参示例;
nonce由客户端自己uuid()生成,去除横杠,小写32位,用于服务端做 不可重放校验
sign是签名串,用于保护业务入参及nonce不被篡改,sign()指的顶象的签名函数伪代码.
1、签名原文内容中的参数使用&分隔,参数顺序在每个接口文档自行定义好;
2、签名内容中的参数param2的值如果为空或者不存在或null等,无需该参数param2,例如:sign : sign("nonce=uuidStr¶m1=xxx¶m3=xxx");
3、参与签名的参数为入参中排除signature列表外的所有参数,字符串顺序按照参数名ASCII码从小到大排序
以下为调用端加密实例:
package com.yuntai.ssm.manager.util;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;
public class RSAUtil {
public static void main(String[] args) throws Exception {
String publicKey = "<您的公钥>";
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//验证签名
Map<String, String> signMap = new HashMap<>();
signMap.put("timestamp", df.format(new Date()));
signMap.put("nonce", UUID.randomUUID().toString().replaceAll("-",""));
signMap.put("orderId", "123");
signMap.put("orderCode", "orderCode123456");
signMap.put("orderTime", df.format(new Date()));
signMap.put("payTime", df.format(new Date()));
signMap.put("payOrderNo", df.format(new Date()));
signMap.put("customerPhone", "13333333333");
signMap.put("totalPrice", "0.01");
signMap.put("openId", "1234567");
signMap.put("discountCost", "0.01");
signMap.put("discountType", "满减");
signMap.put("payType", "13333333333");
signMap.put("orderSource", "0.01");
signMap.put("receiveName", "xxxx");
signMap.put("receivePhone","13333333333");
signMap.put("receiveAddress", "门诊一楼");
signMap.put("patientDept", "耳鼻喉科");
signMap.put("refundAmount", null);
signMap.put("refundApplyTime", null);
signMap.put("refundStatus", null);
signMap.put("refundTime", null);
// map排序后拼接
List<String> keys= new ArrayList<>(signMap.keySet());
Collections.sort(keys);
StringBuffer content = new StringBuffer();
for (int i=0; i<keys.size();i++) {
String key = keys.get(i);
String value = signMap.get(key);
if(value != null && value.length() != 0) {
content.append((i == 0 ? "" : "&") + key + "=" + value);
}
}
String encryptData = RSAUtil.encrypt(RSAUtil.loadPublicKey(publicKey),
content.toString().getBytes());
System.out.println("加密后签名encryptData:" + encryptData);
}
/**
* 根据公钥字符串加载公钥
*
* @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 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);
InputStream ins = new ByteArrayInputStream(plainTextData);
ByteArrayOutputStream writer = new ByteArrayOutputStream();
// rsa加密的字节大小最多是117,将需要解密的内容,按117位拆开解密
byte[] buf = new byte[117];
int bufl;
while ((bufl = ins.read(buf)) != -1) {
byte[] block = null;
if (buf.length == bufl) {
block = buf;
} else {
block = new byte[bufl];
for (int i = 0; i < bufl; i++) {
block[i] = buf[i];
}
}
writer.write(cipher.doFinal(block));
}
return Base64.encode(writer.toByteArray());
} 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("明文数据已损坏");
}
}
}
服务端接收到参数后,解密并验证
@PostMapping("/model1/syncOrderInfo")
public SmartCabinetResponse syncOrderDataFromSmartCabinet(@Validated(SmartCabinetOrder.SyncOrderInfo.class) @RequestBody SmartCabinetOrder order) throws Exception {
if (StringUtils.isEmpty(order.getSignature())) {
return SmartCabinetResponse.builder().message("签名为空").code(-1).build();
}
SecretKey cabinetSecretKey = SecretKey.builder().privateKey("取的私钥").build();
// 验证签名
Map<String, String> signMap = new HashMap<>();
signMap.put("timestamp", order.getTimestamp());
signMap.put("nonce", order.getNonce());
signMap.put("orderId", order.getOrderId().toString());
signMap.put("orderCode", order.getOrderCode());
signMap.put("orderTime", order.getOrderTime());
signMap.put("payTime", order.getPayTime());
signMap.put("payOrderNo", order.getPayOrderNo());
signMap.put("customerPhone", order.getCustomerPhone());
signMap.put("totalPrice", order.getTotalPrice().toString());
signMap.put("openId", order.getOpenId());
signMap.put("discountCost", order.getDiscountCost());
signMap.put("discountType", order.getDiscountType());
signMap.put("payType", order.getPayType());
signMap.put("orderSource", order.getOrderSource());
signMap.put("receiveName", order.getReceiveName());
signMap.put("receivePhone",order.getReceivePhone());
signMap.put("receiveAddress", order.getReceiveAddress());
signMap.put("patientDept", order.getPatientDept());
signMap.put("refundAmount", order.getRefundAmount());
signMap.put("refundApplyTime", order.getRefundApplyTime());
signMap.put("refundStatus", order.getRefundStatus());
signMap.put("refundTime", order.getRefundTime());
List<String> keys= new ArrayList<>(signMap.keySet());
Collections.sort(keys);
StringBuffer content = new StringBuffer();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = signMap.get(key);
if(value != null && value.length() != 0) {
content.append((i == 0 ? "" : "&") + key + "=" + value);
}
}
String decryptStr = RSAUtil.decrypt(RSAUtil.loadPrivateKey(cabinetSecretKey.getPrivateKey()), Base64.getDecoder().decode(order.getSignature()));
if (!StringUtils.equals(decryptStr, content.toString())) {
return SmartCabinetResponse.builder().message("签名信息不正确").code(-1).build();
}
return 《相应数据处理接口》;
}
附RSAUtil工具
package com.lecshop.util;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.*;
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;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class RSAUtil {
/**
* 生成RAS公钥与私钥字符串,直接返回
*
* @return
*/
public static HashMap<String, String> getKeys() {
HashMap<String, String> map = new HashMap<String, String>();
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到公钥字符串
String publicKey = base64ToStr(keyPair.getPublic().getEncoded());
// 得到私钥字符串
String privateKey = base64ToStr(keyPair.getPrivate().getEncoded());
map.put("publicKey", publicKey);
map.put("privateKey", privateKey);
return map;
}
public static String base64ToStr(byte[] b) {
return javax.xml.bind.DatatypeConverter.printBase64Binary(b);
}
/**
* 根据公钥字符串加载公钥
*
* @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);
InputStream ins = new ByteArrayInputStream(plainTextData);
ByteArrayOutputStream writer = new ByteArrayOutputStream();
// rsa加密的字节大小最多是117,将需要解密的内容,按117位拆开解密
byte[] buf = new byte[117];
int bufl;
while ((bufl = ins.read(buf)) != -1) {
byte[] block = null;
if (buf.length == bufl) {
block = buf;
} else {
block = new byte[bufl];
for (int i = 0; i < bufl; i++) {
block[i] = buf[i];
}
}
writer.write(cipher.doFinal(block));
}
return Base64.encode(writer.toByteArray());
} 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);
InputStream ins = new ByteArrayInputStream(cipherData);
ByteArrayOutputStream writer = new ByteArrayOutputStream();
//rsa解密的字节大小最多是128,将需要解密的内容,按128位拆开解密
byte[] buf = new byte[128];
int bufl;
while ((bufl = ins.read(buf)) != -1) {
byte[] block = null;
if (buf.length == bufl) {
block = buf;
} else {
block = new byte[bufl];
for(int i = 0; i < bufl; i++) {
block[i] = buf[i];
}
}
writer.write(cipher.doFinal(block));
}
return new String(writer.toByteArray(), "utf-8");
} 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("密文数据已损坏");
}
}
}