要了解支付系统加解密是怎么处理的,可以先看看支付行业的龙头:支付宝、微信等。
因为公司内部刚好需要和支付宝对接,就参考支付宝的加解密流程来实现公司内部系统的加解密。
以下是支付宝转账接口的示例代码:
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do"); //gateway:支付宝网关(固定)https://openapi.alipay.com/gateway.do
certAlipayRequest.setAppId(app_id); //APPID 即创建应用后生成,详情见创建应用并获取 APPID
certAlipayRequest.setPrivateKey(app_privateKey); //开发者应用私钥,由开发者自己生成
certAlipayRequest.setFormat("json"); //参数返回格式,只支持 json 格式
certAlipayRequest.setCharset(charset); //请求和签名使用的字符编码格式,支持 GBK和 UTF-8
certAlipayRequest.setSignType(sign_type); //商户生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA,推荐商家使用 RSA2。
certAlipayRequest.setCertPath(app_cert_path); //应用公钥证书路径(app_cert_path 文件绝对路径)
certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path); //支付宝公钥证书文件路径(alipay_cert_path 文件绝对路径)
certAlipayRequest.setRootCertPath(alipay_root_cert_path); //支付宝CA根证书文件路径(alipay_root_cert_path 文件绝对路径)
AlipayClient alipayClient = new DefaultAlipayClient(certAlipayRequest);
AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
request.setBizContent("{" +
"\"out_biz_no\":\"201806300001\"," +
"\"trans_amount\":1.68," +
"\"product_code\":\"TRANS_ACCOUNT_NO_PWD\"," +
"\"biz_scene\":\"DIRECT_TRANSFER\"," +
"\"order_title\":\"201905代发\"," +
"\"payee_info\":{" +
"\"identity\":\"2088123412341234\"," +
"\"identity_type\":\"ALIPAY_USER_ID\"," +
"\"name\":\"黄龙国际有限公司\"," +
"}," +
"\"remark\":\"201905代发\"," +
"\"business_params\":\"{\\\"payer_show_name\\\":\\\"服务代理\\\"}\"" +
"}");
AlipayFundTransUniTransferResponse response = alipayClient.certificateExecute(request);
if(response.isSuccess()){
System.out.println("调用成功");
} else {
System.out.println("调用失败");
}
复制代码
从上面的示例代码我们就能简单了解到支付宝转账的加解密流程核心的依赖:
- 开发者应用私钥
- 应用公钥证书
- 支付宝公钥证书
- 支付宝CA根证书
为什么一定要这四样东西?
可以先回忆一下加解密中,证书、公钥、私钥、CA证书认证流程都是啥来的。
公钥:公钥是对称加密算法密钥对的非秘密一半。公钥通常用于加密会话密钥、验证数字签名,或加密可以用相应的私钥解密的数据。
私钥:私钥是对称加密算法密钥对的秘密一半。私钥通常用于解密会话密钥、加签。
证书:证书是一个凭证,用来证明消息是谁发的,比如说支付宝公钥证书包含一个公钥,证明这个公钥是支付宝颁发的,而不是中间人颁发的。
CA证书认证流程:从根证书开始向下认证,每次认证通过获取当前证书的公钥以认证下级证书。
根据以上描述,猜测一下:
- 开发者应用私钥作用:对商家请求报文加签
- 应用公钥证书作用:包含应用公钥,支付宝对商家请求/响应报文的签名进行验签,以确保数据在传输过程没有被篡改
- 支付宝公钥证书:包含支付宝公钥,商家对支付宝的请求/响应报文里的签名进行验签,以确保数据在传输过程没有被篡改
- 支付宝CA根证书:用于验证支付宝公钥证书是否是合法的
咦,有问题了。
怎么好像缺少了对称加密算法的密钥,那岂不是传输过程是明文了。
查看了一下支付宝SDK发现确实是在实例代码的配置下,是不会对传输报文进行加密。
// 只有新接口和设置密钥才能支持加密
if (request.isNeedEncrypt()) {
if (StringUtils.isEmpty(appParams.get(AlipayConstants.BIZ_CONTENT_KEY))) {
throw new AlipayApiException("当前API不支持加密请求");
}
// 需要加密必须设置密钥和加密算法
if (StringUtils.isEmpty(this.encryptType) || getEncryptor() == null) {
throw new AlipayApiException("API请求要求加密,则必须设置密钥类型和加密器");
}
String encryptContent = getEncryptor().encrypt(
appParams.get(AlipayConstants.BIZ_CONTENT_KEY), this.encryptType, this.charset);
appParams.put(AlipayConstants.BIZ_CONTENT_KEY, encryptContent);
}
复制代码
也可以实际看看上面四样东西在代码里是否如猜测那样。
以下是SDK中的加签方法,使用了开发者应用私钥。
public String sign(String sourceContent, String signType, String charset) {
String sign = null;
try {
// this.privateKey就是开发者应用密钥
sign = AlipaySignature.rsaSign(sourceContent, this.privateKey, charset, signType);
} catch (AlipayApiException e) {
throw new RuntimeException(e);
}
return sign;
}
复制代码
那对于支付宝来说就得对商家发送的报文进行验签,验签就必须先知道与开发者应用私钥相匹配的公钥,但是如果商家在每次请求的时候后带上公钥证书,那性能消耗可不是一点点,所以支付宝的SDK也对发送公钥这个逻辑做了优化:把发送应用证书替换为应用证书的唯一序列号,只需要在报文中指定证书序列号,在支付宝端就会根据序列号查询相应的证书,以降低网络传输的消耗。
//appCertSN为最终发送给网关的应用证书序列号
this.appCertSN = AntCertificationUtil.getCertSN(cert);
if (StringUtils.isEmpty(this.appCertSN)) {
throw new AlipayApiException("AppCert Is Invalid");
}
-----------------------------------------------------------------
// 如果证书过期,支付宝会通过响应返回证书信息给商家,商家需要根据根证书对返回的证书进行校验
String alipayPublicKey = null;
if (certItem.getCert() != null) {
if (!alipayPublicCertMap.containsKey(certItem.getCert())) {
//如果返回的支付宝公钥证书序列号与本地支付宝公钥证书序列号不匹配,通过返回的支付宝公钥证书序列号去网关拉取新的支付宝公钥证书
AlipayOpenAppAlipaycertDownloadRequest alipayRequest = new AlipayOpenAppAlipaycertDownloadRequest();
alipayRequest.setBizContent("{" +
"\"alipay_cert_sn\":\"" + certItem.getCert() + "\"" +
" }");
Map<String, Object> rt = doPost(alipayRequest, null, null, this.appCertSN, null);
// 解析实际串
String respContent = rt.get("rsp").toString();
AlipayParser<AlipayOpenAppAlipaycertDownloadResponse> parserCert = null;
parserCert = new ObjectJsonParser<AlipayOpenAppAlipaycertDownloadResponse>(alipayRequest.getResponseClass());
AlipayOpenAppAlipaycertDownloadResponse alipayResponse = parserCert.parse(respContent);
if (!alipayResponse.isSuccess()) {
throw new AlipayApiException("支付宝公钥证书校验失败,请确认是否为支付宝签发的有效公钥证书");
}
String alipayCertContent = alipayResponse.getAlipayCertContent();
InputStream inputStream = null;
try {
byte[] alipayCert = Base64.decodeBase64String(alipayCertContent);
String alipayPublicCertStr = new String(alipayCert);
if (!verifyCert(alipayPublicCertStr)) {
throw new AlipayApiException("支付宝公钥证书校验失败,请确认是否为支付宝签发的有效公钥证书");
}
inputStream = new ByteArrayInputStream(alipayCert);
CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
X509Certificate alipayPublicCertNew = (X509Certificate) cf.generateCertificate(inputStream);
//获取新证书和公钥添加到map中
String alipayCertSNNew = AntCertificationUtil.getCertSN(alipayPublicCertNew);
this.alipayPublicCertMap.put(alipayCertSNNew, alipayPublicCertNew);
PublicKey publicKey = alipayPublicCertNew.getPublicKey();
String alipayPublicKeyNew = Base64.encodeBase64String(publicKey.getEncoded());
this.alipayPublicKeyMap.put(alipayCertSNNew, alipayPublicKeyNew);
} catch (NoSuchProviderException e) {
throw new AlipayApiException(e);
} catch (CertificateException e) {
throw new AlipayApiException(e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
throw new AlipayApiException(e);
}
}
} else if (!alipayPublicKeyMap.containsKey(certItem.getCert())) {
PublicKey publicKey = alipayPublicCertMap.get(certItem.getCert()).getPublicKey();
this.alipayPublicKeyMap.put(certItem.getCert(), Base64.encodeBase64String(publicKey.getEncoded()));
}
// 针对成功结果且有支付宝公钥的进行验签
if (alipayPublicCertMap.containsKey(certItem.getCert())) {
alipayPublicKey = this.alipayPublicKeyMap.get(certItem.getCert());
if (responseIsSuccess
|| (!responseIsSuccess && !StringUtils.isEmpty(certItem.getSign()))) {
signChecker = new DefaultSignChecker(alipayPublicKey);
boolean rsaCheckContent = signChecker.checkCert(certItem.getSignSourceDate(),
certItem.getSign(), this.signType, this.charset, alipayPublicKey);
if (!rsaCheckContent) {
// 针对JSON \/问题,替换/后再尝试做一次验证
if (!StringUtils.isEmpty(certItem.getSignSourceDate())
&& certItem.getSignSourceDate().contains("\\/")) {
String srouceData = certItem.getSignSourceDate().replace("\\/", "/");
boolean jsonCheck = getSignChecker().check(srouceData, certItem.getSign(),
this.signType, this.charset);
if (!jsonCheck) {
throw new AlipayApiException(
"cert check fail: check Cert and Data Fail!JSON also!");
}
} else {
throw new AlipayApiException("cert check fail: check Cert and Data Fail!");
}
}
}
} else {
throw new AlipayApiException("cert check fail: check Cert and Data Fail! CertSN non-existent");
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END