接口规范 - 签名、加解密
## 参数加密
#### 第一步:
所有的参数转换为json字符串
```String paramsJson= {"key1":"value1","key2":"value2"};```
#### 第二步:
获取到发放平台给的 publicKey 进行RSA加密
```String encryptResult= RSAHelper.encryptByPublicKey(paramsJson, publicKey);```
>d RSA加密说明
◆ 加密长度为512
◆ 需要分段加密防止数据太长导致加密失败
◆ 获取到加密结果后通过base64转成字符串即为加密后的数据
◆ 如参数为空字符串无需加密
#### 加密代码示例(java)
```java
/**
* 使用公钥加密
*
* @param content 待加密内容
* @param publicKeyBase64 公钥 base64 编码(发放平台给的publicKey)
* @return 经过 base64 编码后的字符串
*/
public static String encryptByPublicKey(String content, String publicKeyBase64) throws Exception {
PublicKey publicKey = getPublicKey(publicKeyBase64);
return encrypt(content, publicKey);
}
/**
* 分段加密
*
* @param ciphertext 密文
* @param key 加密秘钥
* @return
*/
public static String encrypt(String ciphertext, Key key) {
try {
// 用公钥加密
byte[] srcBytes = ciphertext.getBytes();
// Cipher负责完成加密或解密工作,基于RSA
Cipher cipher = Cipher.getInstance("RSA");
// 根据公钥,对Cipher对象进行初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
//分段加密
byte[] resultBytes = encryptCipherDoFinal(cipher, srcBytes);
//base64编码
String base64Str = Base64Utils.encodeToString(resultBytes);
return base64Str;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 分段加密
*
* @param cipher
* @param srcBytes
* @return
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws IOException
*/
public static byte[] encryptCipherDoFinal(Cipher cipher, byte[] srcBytes)
throws IllegalBlockSizeException, BadPaddingException, IOException {
int inputLen = srcBytes.length;
int offLen = 0;//偏移量
int i = 0;
ByteArrayOutputStream bops = new ByteArrayOutputStream();
while (inputLen - offLen > 0) {
byte[] cache;
if (inputLen - offLen > 53) {
cache = cipher.doFinal(srcBytes, offLen, 53);
} else {
cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen);
}
bops.write(cache);
i++;
offLen = 53 * i;
}
bops.close();
byte[] encryptedData = bops.toByteArray();
return encryptedData;
}
```
## 结果解密
#### 第一步:
获取接口返回结果中的data字段,该值即为需要解密的数据
```
{
"resopnseType": 0,
"errorCode": "",
"errorMessage": "",
"status": 0,
"data": xxxxxxxxxxxxxxxxxxx,
"ext": null,
"extMessage": null,
"success": true
}
```
#### 第二步:
获取到发放平台提供的publicKey解密
```
String decryptResult= RSAHelper.decryptByPublicKey(data, publicKey);
```
>d RSA解密说明
◆ 需要分段解密密防止数据太长导致解密失败
◆ 获取到加密结果后直接转成字符串即为解密结果
#### 解密代码示例(java)
```java
/**
* 使用公钥解密
*
* @param contentBase64 待解密内容,base64 编码
* @param publicKeyBase64 私钥 base64 编码
* @return
*/
public static String decryptByPublicKey(String contentBase64, String publicKeyBase64) throws Exception {
PublicKey publicKey = getPublicKey(publicKeyBase64);
return decrypt(contentBase64, publicKey);
}
/**
* 分段解密
*
* @param contentBase64 密文
* @param key 解密秘钥
* @return
*/
public static String decrypt(String contentBase64, Key key) {
try {
// 用私钥解密
byte[] bytes = Base64Utils.decodeFromString(contentBase64);
// Cipher负责完成加密或解密工作,基于RSA
Cipher deCipher = Cipher.getInstance("RSA");
// 根据公钥,对Cipher对象进行初始化
deCipher.init(Cipher.DECRYPT_MODE, key);
// 分段解密
byte[] decBytes = decryptCipherDoFinal(deCipher, bytes);
// 结果转成字符串
String decrytStr = new String(decBytes);
return decrytStr;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 分段解密
*
* @param cipher
* @param srcBytes
* @return
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws IOException
*/
public static byte[] decryptCipherDoFinal(Cipher cipher, byte[] srcBytes)
throws IllegalBlockSizeException, BadPaddingException, IOException {
int inputLen = srcBytes.length;
int offLen = 0;
int i = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while (inputLen - offLen > 0) {
byte[] cache;
if (inputLen - offLen > 64) {
cache = cipher.doFinal(srcBytes, offLen, 64);
} else {
cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen);
}
byteArrayOutputStream.write(cache);
i++;
offLen = 64 * i;
}
byteArrayOutputStream.close();
byte[] data = byteArrayOutputStream.toByteArray();
return data;
}
```
## 公共参数说明
|参数名称|参数含义|是否必填|参数备注|
|-|-|-|-|
|AppKey|由发放平台提供的|必填|商户签约后发放提供的唯一识别号|
|Timestamp|时间戳|必填|请求时间戳精确到毫秒。需要将请求机器时间调整为北京时间,请求有效时间为两分钟|
|Version|版本号|必填|如果无特别说明则填写1.0即可|
|Sign|签名|必填|签名,具体来源请看下面的==签名算法==|
>d 以上公共参数放在header中进行传递
#### 代码示例(java)
```java
/**
*
* @param url 接口地址
* @param encryptResult 请求参数加密后的数据
* @param appKey
* @param secretKey
* @return
*/
public static String postRequest(String url, String encryptResult, String appKey, String secretKey) {
String timestamp = String.valueOf(System.currentTimeMillis());
//将参数加密结果放在body中传输
RequestBody body = setRequestBody(encryptResult);
String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0" + "&SecretKey" + secretKey;
//签名
String sigin = Md5Util.getMd5(localString);
Request request = new Request.Builder()
//公共参数
.addHeader("AppKey", appKey)
.addHeader("Timestamp", timestamp)
.addHeader("Version", "1.0")
.addHeader("Sign", sigin)
.url(url)
.post(body)
.build();
Call call = getOkHttpClient().newCall(request);
try {
Response response = call.execute();
String res = response.body().string();
log.info("res====" + res);
return res;
} catch (IOException e) {
log.error("HttpUtil.post网络请求失败:{}", e.getMessage());
return "网络请求失败";
}
}
```
## 签名算法
#### 第一步:
将加密结果跟公共字段拼接成新的字符串
```java
String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0" + "&SecretKey" + secretKey;
```
>d 备注:该处特别说明 &SecretKey 后面没有 ==等于==号
#### 第二步:
用MD5对新字符串加密即为签名
```java
//md5加密
String sigin = Md5Util.getMd5(localString);
/**
* md5 加密工具类
* 32小写英文字母
*/
public class Md5Util {
static String[] chars = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
/**
* md5 加密
* @param src 需的加密字符串
* @return
*/
public static String getMd5(String src){
MessageDigest md5 = null;
try {
// 参数代表的是算法名称
md5 = MessageDigest.getInstance("md5");
byte[] result = md5.digest(src.getBytes());
StringBuilder sb = new StringBuilder(32);
// 将结果转为16进制字符 0~9 A~F
for (int i = 0; i < result.length; i++) {
// 一个字节对应两个字符
byte x = result[i];
// 取得高位
int h = 0x0f & (x >>> 4);
// 取得低位
int l = 0x0f & x;
sb.append(chars[h]).append(chars[l]);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
```