RSA签名工具类
public static void getM() throws Exception {
RSAPublicKey loadPublicKeyByStr = RSAEncrypt.loadPublicKeyByStr(
"MIGfQAB");
RSAPrivateKey loadPrivateKeyByStr;
loadPrivateKeyByStr = RSAEncrypt.loadPrivateKeyByStr(
"MIIC");
String mobile = "15858236349";
String encrypt = RSAEncrypt.encrypt(loadPublicKeyByStr, mobile, "UTF-8");
System.out.println(encrypt);
String mobiles = RSAEncrypt.decrypt(loadPrivateKeyByStr, encrypt, "UTF-8");
System.out.println(mobiles);
TreeMap<String, String> paramMap = new TreeMap<>();
paramMap.put("mobile_tel", URLDecoder.decode(encrypt, "UTF-8"));
paramMap.put("timestamp", "1559636223216");
paramMap.put("partner_id", "20190604");
String createLinkString = createLinkString(paramMap);
System.out.println("createLinkString: " + createLinkString);
String sign = RSASignature.sign(createLinkString, loadPrivateKeyByStr, "UTF-8");
boolean doCheck = RSASignature.doCheck(createLinkString(paramMap), sign, loadPublicKeyByStr);
System.out.println("doCheck: " + doCheck);
}
public static void threadLogin() throws Exception {
String mobile = "18806513872";
String ciphertext = RSAEncrypt.encrypt(RSAEncrypt.loadPublicKeyByStr(partnerPublicKey), mobile, "UTF-8");
System.out.println("手机号加密: " + ciphertext);
String phone = RSAEncrypt.decrypt(RSAEncrypt.loadPrivateKeyByStr(privateKey), ciphertext, "UTF-8");
System.out.println("手机号解密: " + phone);
TreeMap<String, String> paramMap = new TreeMap<>();
paramMap.put("partner_id", "20190634");
paramMap.put("timestamp", DateTime.now().toString("yyyyMMddHHmmss"));
paramMap.put("mobile_tel", ciphertext);
paramMap.put("car_num", "浙A641MY");
// 拼接
String urlParams = RSAEncrypt.createLinkString(paramMap);
System.out.println("代签名字符串: " + urlParams);
// 平台私钥获取
RSAPrivateKey loadPrivateKeyByStr = RSAEncrypt.loadPrivateKeyByStr(privateKey);
// 平台私钥签名
String sign = RSASignature.sign(urlParams, loadPrivateKeyByStr,
"UTF-8");
boolean doCheck = RSASignature.doCheck(urlParams, sign, RSAEncrypt.loadPublicKeyByStr(partnerPublicKey));
LogKit.info("验签状态: " + doCheck);
paramMap.put("sign", sign);
urlParams = RSAEncrypt.createLinkStringTo(paramMap);
System.out.println("签名后: " + urlParams);
// String url = "http://127.0.0.1:9094/user/thirdLogin?" + urlParams;
String url = "http://192.168.2.92:4009?" + urlParams;
System.out.println("url: " + url);
String res = HttpUtil.get(url);
LogKit.info(res);
System.out.println("========结束=======");
}
pc
package cn.inpark.consumer.app.io;
import cn.hutool.core.date.DateTime;
import cn.hutool.http.HttpUtil;
import cn.inpark.core.sign.RSAEncrypt;
import cn.inpark.core.sign.RSASignature;
import com.alibaba.fastjson.JSON;
import com.jfinal.kit.LogKit;
import java.net.URLEncoder;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.TreeMap;
/**
* @author Wgs (ˇˍˇ)
* @version 1.0
* @create:2020/05/22
*/
public class IoPaySuccess {
private static String privateKey = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANb90wVhDLg5RONfbjHK7mUyVcD6aEH6lrF0PNyhKc9AOaKdkCPT8YEyKcJFSHCD3zjr6N/zVyV7d+Haycfmf7NIR+eLP5hUYh5JfCcbgF5SOFZGU9y7hI5r1BXMaLisTTvc0IHLutIuiw/e7XgkNZsmZG2C3mCKXGjvWFZ2GyDzAgMBAAECgYAETdCLqwW2Km9WnfJ2zLk5DWjqFBlCOOILa2hMgm1z2LVOrBjWnseW7ooneXuZxQoS+aYP1a9I8M/Vlau0dMjZpJNuD0FNHFXS22jSsYsJ9ZpDz5yBrox9iQRjfNBzgDcXU7PazDENAPKTsg2GWVcxYrYzWm+QANwcArS6M1S4AQJBAPBW/27w05OeTibALrKPClHF2yVte17lBOx0OUNyYHhsPhSwDQqE/qcXgzq3yJ3i4z/c9hymntIiU4O2DOY7GOECQQDlAACP8dGztFxiBCU3bxVwA2A50EY6o2xTSrCXzbKb9V+T/mhCBTPrb4V+ghhU2f3F1FTvA8TCWenxu93X5BBTAkEAkKwt0oBRO/j9HkJtWbrVAWhShHMk4niFwDguCDWfcJ3nuNhI4V4xVC/JJN1ge/oJxXWjCJgb3m4fS5eYqDZGwQJBAJsjsD3UlrvHEds5cbknYX+cWeFw71Mkieu3qwF80mcECfdqIFmxyqXSzL6TBkbd1CXlHpE6nV8JqQsiuj1bfY8CQQCbm0d3GguFQM1iY5J0aaN/kvi+CMoArZNfdpdbB4NhR2d6C1jl14K+SDrcES6df+g/Vm7k9vJYnIZAnegamXqt";
// 商户号
private static String partnerPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDW/dMFYQy4OUTjX24xyu5lMlXA+mhB+paxdDzcoSnPQDminZAj0/GBMinCRUhwg9846+jf81cle3fh2snH5n+zSEfniz+YVGIeSXwnG4BeUjhWRlPcu4SOa9QVzGi4rE073NCBy7rSLosP3u14JDWbJmRtgt5gilxo71hWdhsg8wIDAQAB";
private static String localhost_url = "127.0.0.1:9095/io/paySuccess";
public static void main(String[] args) throws Exception {
ioPay();
}
private static void ioPay() throws Exception {
RSAPublicKey loadPublicKeyByStr = RSAEncrypt.loadPublicKeyByStr(partnerPublicKey);
RSAPrivateKey loadPrivateKeyByStr = RSAEncrypt.loadPrivateKeyByStr(privateKey);
TreeMap<String, String> paramMap = initParam();
paramMap.put("parkId", "1");
paramMap.put("carNo", "浙F12347");
paramMap.put("ioType", "2");
paramMap.put("ioTime", "2020-05-25 16:00:04");
paramMap.put("passCode", "20181010162830");
paramMap.put("passName", "南一");
paramMap.put("recordId", "");
paramMap.put("outTradeNo", "2020052122001428411445935438");
paramMap.put("payType", "免密支付");
paramMap.put("payCharge", "15.00");
paramMap.put("realCharge", "15.00");
String urlParams = RSAEncrypt.createLinkStringFrom(paramMap);
String sign = RSASignature.sign(urlParams, loadPrivateKeyByStr, "UTF-8");
paramMap.put("sign", sign);
boolean doCheck = RSASignature.doCheck(urlParams, sign, loadPublicKeyByStr);
LogKit.info("doCheck: " + doCheck);
String url = localhost_url;
// 通过表单提交需要sign Encode ; json提交不要sign不需要encode
String param = RSAEncrypt.createLinkStringFrom(paramMap) +"&sign="+ URLEncoder.encode(sign.toString(), "utf-8");
String realUrl = url +"?" +param;
System.out.println(url + "?" + param);
System.out.println("json: " + JSON.toJSONString(paramMap));
String res = HttpUtil.get(realUrl);
LogKit.info(res);
System.out.println("========结束=======");
// ============== json ===============
String urlParams = RSAEncrypt.createLinkStringFrom(paramMap);
String sign = RSASignature.sign(urlParams, loadPrivateKeyByStr, "UTF-8");
paramMap.put("sign", sign);
boolean doCheck = RSASignature.doCheck(urlParams, sign, loadPublicKeyByStr);
LogKit.info("doCheck: " + doCheck);
String url = localhost_url;
String param = RSAEncrypt.createLinkStringFrom(paramMap);
String res = HttpUtil.post(url ,JSON.toJSONString(paramMap));
LogKit.info(res);
}
private static TreeMap<String, String> initParam() {
TreeMap<String, String> paramMap = new TreeMap<>();
paramMap.put("timestamp", DateTime.now().toString("yyyyMMddHHmmss"));
paramMap.put("partnerId", "20200402");
return paramMap;
}
}
签名验证
签名验证涉及到客户端(比如一个 Web 应用)和服务器端,每个客户端在服务器上有一个对应的 app_id 和 app_key,大致步骤如下:
客户端使用 app_id + app_key + 其他参数生成签名字符串 sign
把 app_id、其他参数 和 sign 一起发送给服务器(app_key 不发送)
服务器接收到请求后,根据参数中的 app_id 查找到对应的 app_key,然后根据签名算法生成签名字符串 sign2
客户端和服务器端使用同样的签名算法生成签名字符串。
- 字符串比较参数中的 sign 和服务器生成的 sign2,如果相等则签名没问题,放行访问,否则签名无效,拒绝访问
签名算法
- 设要参与计算签名的数据为集合 M,将集合 M 内非空参数值的参数按照参数名 ASCII 码从小到大排序(字典序),使用 URL 键值对的格式(即key1=value1&key2=value2…)拼接成字符串 signTemp。
特别注意以下重要规则:
参数名 ASCII 码从小到大排序(字典序)
如果参数的值为空不参与签名
参数名区分大小写
验证调用返回或主动通知签名时,传送的 sign 参数不参与签名,将生成的签名与该 sign 值作校验
接口可能增加字段,验证签名时必须支持增加的扩展字段
- 对 signTemp 进行 MD5 运算,再将得到的字符串所有字符转换为大写,得到 sign 值 signValue。
例子
假设传送的参数如下:
app_id: 015B512C873648578FB2C32BD5677BD4
username: alice
productId: 1001
signedTime: 1499914521231
并且 app_key 为 927170905ECA42FC9813DD7EED21A5AF
第一步:对参数按照 key=value 的格式,并按照参数名 ASCII 字典序排序:
signTemp = "app_id=015B512C873648578FB2C32BD5677BD4&app_key=927170905ECA42FC9813DD7EED21A5AF&productId=1001&signedTime=1499914521231&username=alice";
第二步:计算 signTemp 的签名字符串:
sign = md5(signTemp).toUpperCase(); // 281879C9007C3698D1106F9CF6A097A3
第三步:发送参数(没有 app_key)
app_id=015B512C873648578FB2C32BD5677BD4&productId=1001&signedTime=1499914521231&username=alice&sign=33A62BBCEF9D4AF675ADC6BAEA468B99
Java 实现
import org.springframework.util.DigestUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Sign {
public static void main(String[] args) throws IOException {
String appId = "015B512C873648578FB2C32BD5677BD4";
String appKey = "927170905ECA42FC9813DD7EED21A5AF";
// 参与签名的参数
Map<String, String> params = new HashMap<>();
params.put("app_id", appId);
params.put("app_key", appKey);
params.put("username", "alice");
params.put("productId", "1001");
params.put("signedTime", "1499914521231");
String signValue = sign(params);
System.out.println(signValue); // 281879C9007C3698D1106F9CF6A097A3
}
public static String sign(Map<String, String> params) {
Map<String, String> temp = new TreeMap<>(params); // 对参数进行排序
// 拼接参数
StringBuilder sb = new StringBuilder();
for (String key : temp.keySet()) {
sb.append(key).append("=").append(temp.get(key)).append("&");
}
String signTemp = sb.deleteCharAt(sb.length() - 1).toString(); // 去掉最后一个 &
String signValue = DigestUtils.md5DigestAsHex(signTemp.getBytes()).toUpperCase(); // 使用 MD5 计算签名字符串
return signValue;
}
}
签名有效期
如果不限制签名的使用时间,则生成的签名永远有效,如果别人拿到生成的链接后就能一直使用了,这是比较危险的,所以限制签名时长很有必要,有 2 种方式可以限制签名的有效时间:
- 客户端加签名生成时间:
- 生成签名时的时间加入计算签名
如果客户端的时间或则服务器端的时间不准,就有可能签名会无效,例如客户端的时间是 2016 年的,而服务器端的是 2017 年的,签名的过期时长为 5 分钟,则计算的签名就会是无效的。反过来签名的有效时间就变长了。
还有个办法,使用服务器时间进行签名:在计算签名前先向服务器请求一下服务器的时间,然后用此时间进行签名即可。服务器接收到请求时判断一下这个时间一定要比服务器当前时间早才行。
服务器记录签名时间:
- 客户端计算签名(不使用时间)
- 服务器收到请求后:
- 如果签名有效且是第一次使用,则保存签名和时间 T1 到数据库或则 Redis 等,放行访问
- 如果签名不是第一次访问(能从数据库或则 Redis 中查找到),并且 服务器当前时间 <= T1 + 过期时长 则放行访问,否则拒绝访问
优点:此种方式是最安全的。
缺点:服务器端需要一直保存签名,否则删除后再次使用时就变成第一次使用了,不过 Redis 之类的存储几亿条记录访问还是很快的。
代码实例
package com.demo.type;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SignTest {
public static void main(String[] args) throws Exception {
Map<String, String[]> params = new HashMap<>();
params.put("name", new String[]{"tom"});
params.put("type", new String[]{"3"});
params.put("age", new String[]{"12"});
params.put("a", new String[]{"a"});
System.out.println(createLinkString(params));
// a="a"&age="12"&name="tom"&type="3"
System.out.println("------------加密--------------");
String privvateKey = "purp3489u&*&";
System.out.println(createSign(createLinkString(params),privvateKey));
// 06cd7206aa20010f1b67f4dc17ec79c4
// 判断请求时间有效性
Long nowTimePoint = System.currentTimeMillis();
if (nowTimePoint - Long.valueOf(timePoint) > 15 * 60) {
}
}
public static String createSign(String params, String privateKey) throws UnsupportedEncodingException {
return MD5Util.MD5Encode(params + privateKey, "utf-8");
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkString(Map<String, String[]> params) throws Exception {
Map<String, String> paramsMap = new HashMap<>();
for (String name : params.keySet()) {
String[] value = params.get(name);
for (int i = 0; i < value.length; i++) {
paramsMap.put(name, value[i]);
}
}
List<String> keys = new ArrayList<String>(paramsMap.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = paramsMap.get(key);
if (!"sign".equals(key)) {
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
prestr = prestr + key + "=\"" + URLEncoder.encode(value, "utf-8") + "\"";
} else {
prestr = prestr + key + "=\"" + URLEncoder.encode(value, "utf-8") + "\"" + "&";
}
}
}
return prestr;
}
}
工具类
package com.demo.type;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SignUtils {
public static String createSign(String params, String privateKey) throws UnsupportedEncodingException {
return MD5Util.MD5Encode(params + privateKey, "utf-8");
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params
* 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkString(Map<String, String[]> params) throws Exception {
Map<String, String> paramsMap = new HashMap<>();
for (String name : params.keySet()) {
String[] value = params.get(name);
for (int i = 0; i < value.length; i++) {
paramsMap.put(name, value[i]);
}
}
List<String> keys = new ArrayList<String>(paramsMap.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = paramsMap.get(key);
if (!"sign".equals(key)) {
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
prestr = prestr + key + "=\"" + URLEncoder.encode(value, "utf-8") + "\"";
} else {
prestr = prestr + key + "=\"" + URLEncoder.encode(value, "utf-8") + "\"" + "&";
}
}
}
return prestr;
}
}
三个类
package cn.inpark.common.util.sign;
public final class Base64 {
static private final int BASELENGTH = 128;
static private final int LOOKUPLENGTH = 64;
static private final int TWENTYFOURBITGROUP = 24;
static private final int EIGHTBIT = 8;
static private final int SIXTEENBIT = 16;
static private final int FOURBYTE = 4;
static private final int SIGN = -128;
static private final char PAD = '=';
static private final boolean fDebug = false;
static final private byte[] base64Alphabet = new byte[BASELENGTH];
static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
static {
for (int i = 0; i < BASELENGTH; ++i) {
base64Alphabet[i] = -1;
}
for (int i = 'Z'; i >= 'A'; i--) {
base64Alphabet[i] = (byte) (i - 'A');
}
for (int i = 'z'; i >= 'a'; i--) {
base64Alphabet[i] = (byte) (i - 'a' + 26);
}
for (int i = '9'; i >= '0'; i--) {
base64Alphabet[i] = (byte) (i - '0' + 52);
}
base64Alphabet['+'] = 62;
base64Alphabet['/'] = 63;
for (int i = 0; i <= 25; i++) {
lookUpBase64Alphabet[i] = (char) ('A' + i);
}
for (int i = 26, j = 0; i <= 51; i++, j++) {
lookUpBase64Alphabet[i] = (char) ('a' + j);
}
for (int i = 52, j = 0; i <= 61; i++, j++) {
lookUpBase64Alphabet[i] = (char) ('0' + j);
}
lookUpBase64Alphabet[62] = '+';
lookUpBase64Alphabet[63] = '/';
}
private static boolean isWhiteSpace(char octect) {
return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
}
private static boolean isPad(char octect) {
return (octect == PAD);
}
private static boolean isData(char octect) {
return (octect < BASELENGTH && base64Alphabet[octect] != -1);
}
/**
* Encodes hex octects into Base64
*
* @param binaryData Array containing binaryData
* @return Encoded Base64 array
*/
public static String encode(byte[] binaryData) {
if (binaryData == null) {
return null;
}
int lengthDataBits = binaryData.length * EIGHTBIT;
if (lengthDataBits == 0) {
return "";
}
int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
char encodedData[] = null;
encodedData = new char[numberQuartet * 4];
byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
int encodedIndex = 0;
int dataIndex = 0;
if (fDebug) {
System.out.println("number of triplets = " + numberTriplets);
}
for (int i = 0; i < numberTriplets; i++) {
b1 = binaryData[dataIndex++];
b2 = binaryData[dataIndex++];
b3 = binaryData[dataIndex++];
if (fDebug) {
System.out.println("b1= " + b1 + ", b2= " + b2 + ", b3= " + b3);
}
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
if (fDebug) {
System.out.println("val2 = " + val2);
System.out.println("k4 = " + (k << 4));
System.out.println("vak = " + (val2 | (k << 4)));
}
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
}
// form integral number of 6-bit groups
if (fewerThan24bits == EIGHTBIT) {
b1 = binaryData[dataIndex];
k = (byte) (b1 & 0x03);
if (fDebug) {
System.out.println("b1=" + b1);
System.out.println("b1<<2 = " + (b1 >> 2));
}
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
encodedData[encodedIndex++] = PAD;
encodedData[encodedIndex++] = PAD;
} else if (fewerThan24bits == SIXTEENBIT) {
b1 = binaryData[dataIndex];
b2 = binaryData[dataIndex + 1];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
encodedData[encodedIndex++] = PAD;
}
return new String(encodedData);
}
/**
* Decodes Base64 data into octects
*
* @param encoded string containing Base64 data
* @return Array containind decoded data.
*/
public static byte[] decode(String encoded) {
if (encoded == null) {
return null;
}
char[] base64Data = encoded.toCharArray();
// remove white spaces
int len = removeWhiteSpace(base64Data);
if (len % FOURBYTE != 0) {
return null;// should be divisible by four
}
int numberQuadruple = (len / FOURBYTE);
if (numberQuadruple == 0) {
return new byte[0];
}
byte decodedData[] = null;
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
int i = 0;
int encodedIndex = 0;
int dataIndex = 0;
decodedData = new byte[(numberQuadruple) * 3];
for (; i < numberQuadruple - 1; i++) {
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
|| !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) {
return null;
} // if found "no data" just return null
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
}
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
return null;// if found "no data" just return null
}
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
d3 = base64Data[dataIndex++];
d4 = base64Data[dataIndex++];
if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters
if (isPad(d3) && isPad(d4)) {
if ((b2 & 0xf) != 0)// last 4 bits should be zero
{
return null;
}
byte[] tmp = new byte[i * 3 + 1];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
return tmp;
} else if (!isPad(d3) && isPad(d4)) {
b3 = base64Alphabet[d3];
if ((b3 & 0x3) != 0)// last 2 bits should be zero
{
return null;
}
byte[] tmp = new byte[i * 3 + 2];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
return tmp;
} else {
return null;
}
} else { // No PAD e.g 3cQl
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
}
return decodedData;
}
/**
* remove WhiteSpace from MIME containing encoded Base64 data.
*
* @param data the byte array of base64 data (with WS)
* @return the new length
*/
private static int removeWhiteSpace(char[] data) {
if (data == null) {
return 0;
}
// count characters that's not whitespace
int newSize = 0;
int len = data.length;
for (int i = 0; i < len; i++) {
if (!isWhiteSpace(data[i])) {
data[newSize++] = data[i];
}
}
return newSize;
}
}
package cn.inpark.common.util.sign;
import org.springframework.util.CollectionUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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.Map;
import java.util.TreeMap;
public class RSAEncrypt {
/**
* 字节数据转字符串专用集合
*/
private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f'};
/**
* 随机生成密钥对
*/
public static void genKeyPair(String filePath) {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
try {
// 得到公钥字符串
String publicKeyString = Base64.encode(publicKey.getEncoded());
// 得到私钥字符串
String privateKeyString = Base64.encode(privateKey.getEncoded());
checkOrCreateFile(filePath + "/publicKey.keystore");
checkOrCreateFile(filePath + "/privateKey.keystore");
// 将密钥对写入到文件
FileWriter pubfw = new FileWriter(filePath + "/publicKey.keystore");
FileWriter prifw = new FileWriter(filePath + "/privateKey.keystore");
BufferedWriter pubbw = new BufferedWriter(pubfw);
BufferedWriter pribw = new BufferedWriter(prifw);
pubbw.write(publicKeyString);
pribw.write(privateKeyString);
pubbw.flush();
pubbw.close();
pubfw.close();
pribw.flush();
pribw.close();
prifw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void checkOrCreateFile(String fileName) throws IOException {
File file = new File(fileName);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (!file.exists()) {
file.createNewFile();
}
}
/**
* 从文件中输入流中加载公钥
*
* @param path 公钥输入流
* @throws Exception 加载公钥时产生的异常
*/
public static String loadPublicKeyByFile(String path) throws Exception {
try {
BufferedReader br = new BufferedReader(new FileReader(path + "/publicKey.keystore"));
String readLine = null;
StringBuilder sb = new StringBuilder();
while ((readLine = br.readLine()) != null) {
sb.append(readLine);
}
br.close();
return sb.toString();
} catch (IOException e) {
throw new Exception("公钥数据流读取错误");
} catch (NullPointerException e) {
throw new Exception("公钥输入流为空");
}
}
/**
* 从字符串中加载公钥
*
* @param publicKeyStr 公钥数据字符串
* @throws Exception 加载公钥时产生的异常
*/
public static RSAPublicKey loadPublicKeyByStr(String publicKeyStr) throws Exception {
try {
byte[] buffer = Base64.decode(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("公钥非法");
} catch (NullPointerException e) {
throw new Exception("公钥数据为空");
}
}
/**
* 从文件中加载私钥
*
* @param path 私钥文件名
* @return 是否成功
* @throws Exception
*/
public static String loadPrivateKeyByFile(String path) throws Exception {
try {
BufferedReader br = new BufferedReader(new FileReader(path + "/privateKey.keystore"));
String readLine = null;
StringBuilder sb = new StringBuilder();
while ((readLine = br.readLine()) != null) {
sb.append(readLine);
}
br.close();
return sb.toString();
} catch (IOException e) {
throw new Exception("私钥数据读取错误");
} catch (NullPointerException e) {
throw new Exception("私钥输入流为空");
}
}
public static RSAPrivateKey loadPrivateKeyByStr(String privateKeyStr) throws Exception {
try {
byte[] buffer = Base64.decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法");
} catch (NullPointerException e) {
throw new Exception("私钥数据为空");
}
}
/**
* 公钥加密过程
*
* @param publicKey 公钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(RSAPublicKey publicKey, String plainTextData, String encode) throws Exception {
if (publicKey == null) {
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
// cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(plainTextData.getBytes(encode));
return Base64.encode(output);
} 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 key 私钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public static byte[] encrypt(Key key, byte[] plainTextData) throws Exception {
if (key == null) {
throw new Exception("加密私钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] output = cipher.doFinal(plainTextData);
return output;
} 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 key 私钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(Key key, String cipherData, String encode) throws Exception {
// 64位解码加密后的字符串
byte[] inputByte = Base64.decode(cipherData);
if (key == null) {
throw new Exception("解密私钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
// cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] output = cipher.doFinal(inputByte);
return new String(output, encode);
} 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 publicKey 公钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static byte[] decrypt(RSAPublicKey publicKey, byte[] cipherData) throws Exception {
if (publicKey == null) {
throw new Exception("解密公钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
// cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(cipherData);
return output;
} 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 data 输入数据
* @return 十六进制内容
*/
public static String byteArrayToString(byte[] data) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < data.length; i++) {
// 取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移
stringBuilder.append(HEX_CHAR[(data[i] & 0xf0) >>> 4]);
// 取出字节的低四位 作为索引得到相应的十六进制标识符
stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);
if (i < data.length - 1) {
stringBuilder.append(' ');
}
}
return stringBuilder.toString();
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkStringFrom(Map<String, String[]> params) throws Exception {
TreeMap<String, String> paramsMap = new TreeMap<>();
for (String name : params.keySet()) {
String[] value = params.get(name);
for (int i = 0; i < value.length; i++) {
paramsMap.put(name, value[i]);
}
}
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
if (!"sign".equals(entry.getKey())) {
sb.append(entry.getKey()).append("=");
if (entry.getValue() != null) {
sb.append(URLEncoder.encode(entry.getValue().toString(), "utf-8"));
}
sb.append("&");
}
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkStringFrom(Map<String, String[]> params, boolean isSign) {
StringBuffer sb = new StringBuffer();
if (!CollectionUtils.isEmpty(params)){
TreeMap<String, String> paramsMap = new TreeMap<>();
for (String name : params.keySet()) {
String[] value = params.get(name);
for (int i = 0; i < value.length; i++) {
paramsMap.put(name, value[i]);
}
}
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
sb.append(entry.getKey()).append("=");
if (entry.getValue() != null) {
sb.append(URLEncoder.encode(entry.getValue()));
}
sb.append("&");
}
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkStringFromCheck(Map<String, String[]> params) throws Exception {
TreeMap<String, String> paramsMap = new TreeMap<>();
for (String name : params.keySet()) {
String[] value = params.get(name);
for (int i = 0; i < value.length; i++) {
paramsMap.put(name, value[i]);
}
}
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
if (!"sign".equals(entry.getKey())) {
sb.append(entry.getKey()).append("=");
if (entry.getValue() != null) {
if ("fromCouponId".equals(entry.getKey())) {
sb.append(URLEncoder.encode(entry.getValue(), "utf-8"));
} else {
sb.append(URLDecoder.decode(entry.getValue().toString(), "utf-8"));
}
}
sb.append("&");
}
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkStringFrom(TreeMap<String, String> params) throws Exception {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (!"sign".equals(entry.getKey())) {
sb.append(entry.getKey()).append("=");
if (entry.getValue() != null) {
sb.append(URLDecoder.decode(entry.getValue().toString(), "utf-8"));
// sb.append(entry.getValue().toString());
}
sb.append("&");
}
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkString(TreeMap<String, String> params) throws Exception {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (!"sign".equals(entry.getKey())) {
sb.append(entry.getKey()).append("=").append(entry.getValue().toString()).append("&");
}
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
* @throws Exception
*/
public static String createLinkStringTo(TreeMap<String, String> params) throws Exception {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : params.entrySet()) {
// if (entry.getValue() == null) {
// throw new IllegalArgumentException("待签名值不能为空");
// }
sb.append(entry.getKey()).append("=");
if (entry.getValue() != null) {
sb.append(URLEncoder.encode(entry.getValue().toString(), "utf-8"));
// sb.append(entry.getValue().toString());
}
sb.append("&");
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}
package cn.inpark.common.util.sign;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* RSA签名验签类
*/
public class RSASignature {
/**
* 签名算法
*/
public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
public static String sign(String content, PrivateKey privateKey, String encode) {
try {
java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
signature.initSign(privateKey);
signature.update(content.getBytes(encode));
byte[] signed = signature.sign();
return Base64.encode(signed);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* RSA签名
*
* @param content 待签名数据
* @param privateKey 商户私钥
* @return 签名值
*/
public static String sign(String content, PrivateKey privateKey) {
return sign(content, privateKey, "utf-8");
}
public static boolean doCheck(String content, String sign, PublicKey publicKey, String encode) {
try {
java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
signature.initVerify(publicKey);
signature.update(content.getBytes(encode));
boolean bverify = signature.verify(Base64.decode(sign));
return bverify;
} catch (Exception e) {
return false;
}
}
/**
* RSA验签名检查
*
* @param content 待签名数据
* @param sign 签名值
* @param publicKey 分配给开发商公钥
* @return 布尔值
*/
public static boolean doCheck(String content, String sign, PublicKey publicKey) {
return doCheck(content, sign, publicKey, "utf-8");
}
}