title: 实例-第三方接口签名加密
date: 2019-08-06 19:32:21
categories:
最近和第三方接口做对接,接口涉及到了签名和加密,因此在这里总结一下。签名和加密涉及对称加密、非对称加密、SSL等概念,可以在其他文章中参考。这里只做具体项目中实践的示例。
总体思路是用固定的key进行SHA1摘要签名,用对称加密的secret对数据进行aes加密。由于签名key和对称加密的secret是双方预先沟通好写死在代码里的,不像SSL需要在网络中传输,因此可以保证安全。
具体如下:
数据报文结构:
{
"appid":"1a6dfe4b3a7740d1a37fa1615a3f6bd9",
"nonce":"YSBuLCgvmdSdB3oS",
"timestamp":1552900602924,
"signature":"296a25e1d61b10d359b2893befc3a417d89b4708",
"data":"sdfjasIOISADFLASlsdaf12142rsakdfsafjsawKJL34343"
}
发送过程:
接收过程:
由于appid在报文里是明文的,因此很容易根据appid获得验签和解密所需的签名key,以及秘钥和初始化向量。
下面进行具体解释。
签名规则如下:
其中签名key由平台提供。
签名key:Odgs!^36M$aAXrpp
请求体字段 | 值 |
---|---|
appid | “1a6dfe4b3a7740d1a37fa1615a3f6bd9” |
nonce | “YSBuLCgvmdSdB3oS” |
timestamp | 1552900602924 |
data | null |
将appid、timestamp、nonce、key及data不为null的字段值按ASCII码从小到大排序(字典序),因为data为null不加入签名计算,结果如下:
请求体字段值 |
---|
1552900602924 |
1a6dfe4b3a7740d1a37fa1615a3f6bd9 |
Odgs!^36M$aAXrpp |
YSBuLCgvmdSdB3oS |
将排序后的字段值拼接成字符串,结果如下:
15529006029241a6dfe4b3a7740d1a37fa1615a3f6bd9Odgs!^36M$aAXrppYSBuLCgvmdSdB3oS
对拼接后的字符串计算SHA1摘要,签名如下:
296a25e1d61b10d359b2893befc3a417d89b4708
签名后的请求体:
{
"appid":"1a6dfe4b3a7740d1a37fa1615a3f6bd9",
"nonce":"YSBuLCgvmdSdB3oS",
"timestamp":1552900602924,
"signature":"296a25e1d61b10d359b2893befc3a417d89b4708"
}
其中加*/解密的密钥和初始化向量由平台提供,字符串均采用UTF-8*编码格式
项目实际使用的签名、加密/解密代码如下:
import java.security.MessageDigest;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.stream.Collectors;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import lombok.SneakyThrows;
public class SecurityUtil {
public static final String CHARSET = "UTF-8";
private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f' };
static {
Security.addProvider(new BouncyCastleProvider());
}
private static String hex(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
for (int j = 0; j < len; j++) {
buf.append(HEX_CHARS[(bytes[j] >> 4) & 0x0f]);
buf.append(HEX_CHARS[bytes[j] & 0x0f]);
}
return buf.toString();
}
@SneakyThrows
private static byte[] sha1(byte[] input) {
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(input);
return messageDigest.digest();
}
@SneakyThrows
private static String encodeBase64(byte[] data) {
return new String(Base64.getEncoder().encode(data), CHARSET);
}
@SneakyThrows
private static byte[] decodeBase64(String data) {
return Base64.getDecoder().decode(data.getBytes(CHARSET));
}
@SneakyThrows
private static byte[] aes(byte[] data, byte[] key, byte[] iv, boolean encrypt) {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
new IvParameterSpec(iv));
return cipher.doFinal(data);
}
/**
* 生成签名
*
* @param fieldValues 参与签名的字段
* @return 签名
*/
@SneakyThrows
public static String generateSign(Object... fieldValues) {
String joinedStr = Arrays.asList(fieldValues).stream()
.map(fieldValue -> fieldValue != null ? fieldValue.toString() : "").sorted()
.collect(Collectors.joining(""));
return hex(sha1(joinedStr.getBytes(CHARSET)));
}
/**
* 加密业务数据
*
* @param rawData 业务数据
* @param key 密钥
* @param iv 初始化向量
* @return 加密业务数据
*/
@SneakyThrows
public static String encryptData(String rawData, String key, String iv) {
return encodeBase64(aes(rawData.getBytes(CHARSET), decodeBase64(key), decodeBase64(iv), true));
}
/**
* 解密业务数据
*
* @param encryptedData 加密业务数据
* @param key 密钥
* @param iv 初始化向量
* @return 业务数据
*/
@SneakyThrows
public static String decryptData(String encryptedData, String key, String iv) {
return new String(aes(decodeBase64(encryptedData), decodeBase64(key), decodeBase64(iv), false), CHARSET);
}
// 测试Demo:签名
public static void testGenerateSign() {
String appid = "fs8342f8542c13e9ba0400342d015716";
long timestamp = 1554189997590l;
String nonce = "YDBuFQgbmladR3oK";
String data = "{\"count\":1,\"productId\":\"c3e8fd65544f11e9ba0400155d015705\"}";
String key = "4jSuQA0HNraqPjfq";
System.out.println("[签名测试]");
System.out.println("appid:" + appid);
System.out.println("timestamp:" + timestamp);
System.out.println("nonce:" + nonce);
System.out.println("data:" + data);
System.out.println("key:" + key);
String sign = generateSign(appid, timestamp, nonce, data, key);
System.out.println("签名:" + sign);
System.out.println("----------------");
}
// 测试Demo:加密解密
public static void testEncryptData() {
String data = "{\"key\":\"value\"}";
String key = "D3QE5sMzHlNCpxQ5+GMOzK==";
String iv = "Qb6HMJIDlico8/9lWyKpLf==";
System.out.println("[加解密测试]");
System.out.println("原始数据:" + data);
String encryptedData = encryptData(data, key, iv);
System.out.println("加密数据:" + encryptedData);
System.out.println("解密数据:" + decryptData(encryptedData, key, iv));
System.out.println("----------------");
}
@SneakyThrows
public static void main(String[] args) {
testGenerateSign();
testEncryptData();
}
}