前言
在生产活动中接口数据安全性是一个非常重要的模块。假如发起一个支付请求A向B转账100元,在支付请求传输过程中请求被劫持,将转账金额由100元修改为10000元。这种情况是不被允许的为了防止接口传输数据被篡改,我们就必须要对接口提供方调用方数据进行加签/验签操作用来验证数据的真实性;
数据交互格式
为方便接口的加签或验签,接口提供方或调用方需约定一种固定的请求或响应格式。以下格式为双方数据交互格式;
import lombok.Data;
/**
接口提供方或调用方统一数据交互格式
*/
@Data
public class PublicParameter {
/* 请流水号 */
String requestId;
/* 用于标识唯一一个接口调用方,可用于获取公私钥 */
String appid;
/* 签名 */
String sgin;
/*
明文JSON入参或响应数据(此处为不为String类型是因为data为String类型的JSON字符串响应给调用方
会带有"/转义符。服务提供方响应Obj在@RestController出去时自动转为JSON)
*/
Object data;
/* 请求时间 */
String requestTime;
/* 状态码(接口提供方设置) */
String code;
/* 异常信息(接口提供方设置) */
String message;
/* 接口响应时间(接口提供方设置) */
String responseTime;
}
流程
1. 双方各自生成一对RSA公私钥(私钥加密/公钥解密),双方互换公钥用于验签对方数据;
2. 以接口调用方A访问接口提供方B为例解释加签/验签流程;
2.1. 调用方data中存放本次请求的JSON明文字符串数据,sign中存放data字符串MD5后
再使用调用方私钥加签后的密文数据(我们把这个过程叫做“加签”);
2.2. 提供方收到调用方的请求进行“验签”,看数据传输过程中原始数据是否被篡改,验签
流程如下
2.2.1. 因为双方互换公钥所以提供方对接收到的sign数据使用调用方的公钥进行解
密得到MD5数据。提供方再对传输过来的data数据进行MD5。此时对两个
MD5数据进行比对,若MD5相同则代表验签通过。不同则验签失败;
备注:接口提供方响应调用方加签验签流程也同上
公私钥生成及加解密工具类
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class IRsa {
public final String algorithm = "RSA/ECB/PKCS1Padding";
public final String ALGORITHM_RSA = "RSA";
public String publicKey = null;
public String privateKey = null;
/**
* 生成密钥对
*/
public void generatorKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
byte[] keyBs = rsaPublicKey.getEncoded();
publicKey = encodeBase64(keyBs);
ILog.log.info("publicKey:" + publicKey + " " + publicKey.length());
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
keyBs = rsaPrivateKey.getEncoded();
privateKey = encodeBase64(keyBs);
ILog.log.info("privateKey:" + privateKey + " " + privateKey.length());
}
/**
* 获取私钥
*
* @param privatekey
* @return
* @throws Exception
*/
public PrivateKey getPrivateKey(String privatekey) throws Exception {
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decodeBase64(privatekey));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
return keyFactory.generatePrivate(privateKeySpec);
}
/**
* 获取公钥
*
* @param publickey
* @return
* @throws Exception
*/
public PublicKey getPublicKey(String publickey) throws Exception {
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decodeBase64(publickey));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
return keyFactory.generatePublic(publicKeySpec);
}
/**
* 公钥解密
*/
public String decryptionByPublicKey(String target, String publickey) throws Exception {
PublicKey publicKey = getPublicKey(publickey);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
cipher.update(decodeBase64(target));
String source = new String(cipher.doFinal(), "UTF-8");
return source;
}
/**
* 私钥加密
*/
public String encryptionByPrivateKey(String source, String privatekey) throws Exception {
PrivateKey privateKey = getPrivateKey(privatekey);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
cipher.update(source.getBytes("UTF-8"));
String target = encodeBase64(cipher.doFinal());
return target;
}
/**
* base64编码
*/
public String encodeBase64(byte[] source) throws Exception {
return new String(Base64.encodeBase64(source), "UTF-8");
}
/**
* Base64解码
*/
public byte[] decodeBase64(String target) throws Exception {
return Base64.decodeBase64(target.getBytes("UTF-8"));
}
}
补充说明
1. 数据交互格式中data为Obj类型避免了响应的JSON字符串不带转移符号"/。
2. 如上加密方式的核心是比对MD5数据。一定要确保调用方或提供方将data转为JSON字符串
后JSON字符串Key的顺序要与对方加签时JOSN字符串Key的顺序相同,且在使用某些工具
类将Obj转为JSON字符串一定要确保不要忽略null值(有些工具类将Obj转为JSON字符串时会
自动去掉null值对应的键值数据),因为Key的顺序改变或Obj转JSON字符串时忽略null值会
使MD5的数据发生变化,发生变化后最终会导致验签失败。
3. 解决上面第2点问题有两种方式:
3.1:将data类型更改为String,这样就无需Obj转JOSN字符串过程中造成的Key顺序变化
和忽略null值这两个问题,且可直接对data中的JSON字符串进行MD5后进行比对;
3.2:接口提供方额外提供一个工具类jar包,此jar用于发送请求,加解密数据;
4. 此方式不能阻止请求被劫持,但能解决数据被篡改问题;