银联卡8583协议小额免密免签交易总结

简介: 银联卡8583协议小额免密免签交易总结

之前做过金融支付这块儿。到过北京石景山区银行卡检测中心过检PBOC的level2认证,去过上海银联总部和湖南银联对接银联卡和扫码支付。对金融支付和卡交易这块儿熟悉。现在这块儿知识用不上了总结下留作备忘,同时分享给有需要的人。


关于免密免密交易


免密支付指的是在支付一定金额时,不用输入密码,即可完成交易。小额免密免签,也叫小额双免。是中国银联提供的一种小额快速支付服务。银联闪付就是免密支付的应用,通过联机方式,使用具有“闪付”标识的银行银联芯信用卡,或承载个人信用卡信息的移动支付产品,将银联芯片卡靠近终端“闪付”感应区即可完成支付。


这是大概在2015年10月银联为了紧跟时代潮流和方便用户推出的新业务。我们知道原来的银行卡用起来很不方便,随后它推出的有一个"电子现金"功能,但还是需要先到ATM机上往卡里圈存一部分钱才能用,这部分圈存到卡片上的钱俗称电子现金,消费通过带有闪付标识的银联pos机使用,只需挥卡不用输入密码和签名即可消费,用在一些小额支付场景如公交,地铁和商超。


现在这种小额支付免密交易很流行,各大平台和支付结构都有一定小额的免密交易。银联小额免密免签不需要密码也能保障资金安全,因为小额免密免签使用高安全性的金融ic卡,同时银联联合银行为持卡人提供风险控制和赔付机制。


银联8583协议


8583协议在金融系统中很常用,报文简短,定义清晰规范。是基于ISO8583报文国际标准的包格式的通讯协议,8583包最多由128个字段域组成,每个域都有统一的规定,并有定长与变长之分。8583包前面一段为位图,它是打包解包确定字段域的关键代替。


曾写过一个java版的8583解析协议,全互联网最简单好用的,地址在这里:


java版银联8583协议解析,超简单超直观的实现及示例(全互联网最简单)_特立独行的猫a的博客-CSDN博客_银联8583demo






银联公网接入规范


银联提供了公网https接入规范。HTTP交易报文由http头加传统交易报文数据组成。http头中主要定义了交易报文类型及长度,同时需符合http协议规范。



POST /mjc/webtrans/XX HTTP/1.1
HOST: 145.4.206.XX:XX
User-Agent: XXXX
Cache-Control: no-cache
Content-Type:x-ISO-TPDU/x-auth
Accept: */*
Content-Length: length


https握手流程


HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL。在发送HTTP报文之前需要建立SSL连接,以此加密HTTP数据。关系如下图所示:



SSL连接分为两个阶段,即握手和数据传输阶段;传输任何应用数据之前必须先完成握手。

握手协议:


  1. 对服务器进行认证;


  1. 确立用于保护数据传输的加密密钥;


  1. 记录协议:


  1. 传输数据;



银联通信MAC算法


8583通信有个MAC校验,相关算法参照博主博文:


ANSI-X99MAC算法和PBOC的3DES MAC算法_特立独行的猫a的博客-CSDN博客


Java的HTTP接口封装


没有使用第三方的jar包,使用java自带的net.HttpURLConnection包的封装,简单易用。支持http和https,可以让https不验证本地证书。


package com.yang.utils;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
 * https请求工具类
 *
 * @author yang
 * @date 2018/6/6 19:12
 */
public class HttpsUtils {
    /*
     * https请求是在http请求的基础上加上一个ssl层
     */
    public static String doPost(String requestUrl, String bodyStr, Map<String, String> header, String charset, String contentType) throws Exception {
        System.out.printf("https post 请求地址:%s 内容:%s", requestUrl, bodyStr);
        charset = null == charset ? "utf-8" : charset;
        // 创建SSLContext
        SSLContext sslContext = SSLContext.getInstance("SSL");
        TrustManager[] trustManagers = {new X509TrustManager() {
            /*
             * 实例化一个信任连接管理器
             * 空实现是所有的连接都能访问
             */
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }};
        // 初始化
        sslContext.init(null, trustManagers, new SecureRandom());
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        URL url = new URL(requestUrl);
        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
        httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
        // 以下参照http请求
        httpsURLConnection.setDoOutput(true);
        httpsURLConnection.setDoInput(true);
        httpsURLConnection.setUseCaches(false);
        httpsURLConnection.setRequestMethod("POST");
        httpsURLConnection.setRequestProperty("Accept-Charset", charset);
        if (null != bodyStr) {
            httpsURLConnection.setRequestProperty("Content-Length", String.valueOf(bodyStr.length()));
        }
        if (contentType == null) {
            httpsURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        } else {
            httpsURLConnection.setRequestProperty("Content-Type", contentType);
        }
        if (!header.isEmpty()) {
            for (Map.Entry<String, String> entry : header.entrySet()) {
                httpsURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
            }
        }
        httpsURLConnection.connect();
        // 读写内容
        OutputStream outputStream = null;
        InputStream inputStream = null;
        InputStreamReader streamReader = null;
        BufferedReader bufferedReader = null;
        StringBuffer stringBuffer;
        try {
            if (null != bodyStr) {
                outputStream = httpsURLConnection.getOutputStream();
                outputStream.write(bodyStr.getBytes(charset));
                outputStream.close();
            }
            if (httpsURLConnection.getResponseCode() >= 300) {
                throw new Exception("https post failed, response code " + httpsURLConnection.getResponseCode());
            }
            inputStream = httpsURLConnection.getInputStream();
            streamReader = new InputStreamReader(inputStream, charset);
            bufferedReader = new BufferedReader(streamReader);
            stringBuffer = new StringBuffer();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuffer.append(line);
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
            if (streamReader != null) {
                streamReader.close();
            }
            if (bufferedReader != null) {
                bufferedReader.close();
            }
        }
        System.out.printf("--- https post 返回内容:%s", stringBuffer.toString());
        return stringBuffer.toString();
    }
    public static byte[] doUpHttpsPost(String requestUrl, byte[] bodyHex) throws Exception {
        System.out.printf("请求地址:%s\n", requestUrl);
        // 创建SSLContext
        SSLContext sslContext = SSLContext.getInstance("SSL");
        TrustManager[] trustManagers = {new X509TrustManager() {
            /*
             * 实例化一个信任连接管理器
             * 空实现是所有的连接都能访问
             */
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                //return new X509Certificate[0];
                return new X509Certificate[]{};
            }
        }};
        // 初始化
        sslContext.init(null, trustManagers, new SecureRandom());
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        URL url = new URL(requestUrl);
        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
        httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
        HostnameVerifier hv =  new HostnameVerifier(){
            @Override
            public boolean verify(String string, SSLSession ssls) {
                return true;//To change body of generated methods, choose Tools | Templates.
            }
        };
        httpsURLConnection.setHostnameVerifier(hv);
        // 以下参照http请求
        httpsURLConnection.setDoOutput(true);
        httpsURLConnection.setDoInput(true);
        httpsURLConnection.setUseCaches(false);
        httpsURLConnection.setRequestMethod("POST");
        String strLen = String.valueOf(bodyHex.length);
        httpsURLConnection.setRequestProperty("User-Agent", "Donjin Http 0.1");
        httpsURLConnection.setRequestProperty("Content-Length", strLen);
        httpsURLConnection.setRequestProperty("Accept", "*/*");
        httpsURLConnection.setRequestProperty("Content-Type", "x-ISO-TPDU/x-auth");
        httpsURLConnection.setRequestProperty("Cache-Control", "no-cache");
        httpsURLConnection.connect();
        // 读写内容
        OutputStream outputStream = null;
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            outputStream = httpsURLConnection.getOutputStream();
            outputStream.write(bodyHex);
            outputStream.flush();
            outputStream.close();
            System.out.printf("\n--- https ResponseCode:%d\n", httpsURLConnection.getResponseCode());
            if (httpsURLConnection.getResponseCode() >= 300) {
                throw new Exception("https post failed, response code " + httpsURLConnection.getResponseCode());
            }
            inputStream = httpsURLConnection.getInputStream();
            byteArrayOutputStream = new ByteArrayOutputStream();
            int len = 0;
            byte[] bytes = new byte[inputStream.available()];
            while ((len = inputStream.read(bytes)) != -1) {
                byteArrayOutputStream.write(bytes, 0, len);
            }//写入数据
            return byteArrayOutputStream.toByteArray();
             //byte[] nbytes = bytes.c
        } catch (Exception e) {
            throw e;
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
        }
    }
}


免密免签交易流程


测试源码在这里:


https://download.csdn.net/download/qq8864/87280073


报文示例


test...
2022/12/13 18:57:16 Server:XXX.XX.XX.XXX
2022/12/13 18:57:16 Port:0
2022/12/13 18:57:16 Url:https://202.xx.25.xx:xx/mjc/webtrans/ABC
pack 8583 fields
Print fields...
==========================================
Len:    0057
Tpdu:   6002100000
Head:   613100311108
Msge:   0800
Bitmap: 0020000000c00016
==========================================
[field:11] [000001]
------------------------------
[field:41] [3134313030303033]
------------------------------
[field:42] [313431343230313431313130303033]
------------------------------
[field:60] [len:0011] [000000000030]
------------------------------
[field:62] [len:0025] [53657175656e6365204e6f3132333036303134313030303033]
------------------------------
[field:63] [len:0003] [303031]
------------------------------
2022/12/13 18:57:16 connect:server=https://2xx.101.25.18x:2x141/mjc/webtrans/ABC
send:0057600210000061310031110808000020000000c0001600000131343130303030333134313432303134313131303030330011000000000030002553657175656e6365204e6f31323330363031343130303030330003303031
ReadFile err: open UP.pem: no such file or directory
2022/12/13 18:57:16 begin post...
User-Agent : Donjin Http 0.1
Content-Type :  x-ISO-TPDU/x-auth
Cache-Control : no-cache
2022/12/13 18:57:16 recv ok!len=123
recv:007960000002106131003111080810003800010ac00014000001185716121308000952103138353731363035373232373030313431303030303331343134323031343131313030303300110000001700300040b31544e6cf8a5d091450ad5970c2143e7e960636afd9761ce25f41f70000000000000000c7e1867b
ans 8583 fields
解析成功
Print fields...
==========================================
Len:    0079
Tpdu:   6002100000
Head:   613100311108
Msge:   0810
Bitmap: 003800010ac00014
==========================================
[field:11] [000001]
------------------------------
[field:12] [185716]
------------------------------
[field:13] [1213]
------------------------------
[field:32] [len:08] [00095210]
------------------------------
[field:37] [313835373136303537323237]
------------------------------
[field:39] [3030]
------------------------------
[field:41] [3134313030303033]
------------------------------
[field:42] [313431343230313431313130303033]
------------------------------
[field:60] [len:0011] [000000170030]
------------------------------
[field:62] [len:0040] [b31544e6cf8a5d091450ad5970c2143e7e960636afd9761ce25f41f70000000000000000c7e1867b]
------------------------------
mackey:e5986862d3efefae
2022/12/13 18:57:16 签到成功


相关文章
支付系统03------支付指引,开启支付的前置条件,公户,如果是企业要准备营业执照,组织机构代码证,对公银行账户,法人身份证
支付系统03------支付指引,开启支付的前置条件,公户,如果是企业要准备营业执照,组织机构代码证,对公银行账户,法人身份证
|
存储 安全
币安交易所对接staking接口质押赎回ETH的合约代码开发详情
// 质押ETH function stakeETH(uint256 _stakeAmount) public payable returns (bool) { require(_stakeAmount > 0); require(msg.value == _stakeAmount);
|
4月前
支付设计白皮书:详解!《境外信用卡支付》收单完整过程
支付设计白皮书:详解!《境外信用卡支付》收单完整过程
135 0
|
区块链
区块链交易所的返佣(推荐、持仓)机制系统合约开发部署
区块链交易所的返佣(推荐、持仓)机制系统合约开发部署
GoDaddy用支付宝付款时出现我们无法处理这笔交易,请查看您的付款信息并重试。...
GoDaddy用支付宝付款时出现我们无法处理这笔交易,请查看您的付款信息并重试。...
270 0
|
API
微信开发之普通直连分账(境内普通商户)
单次分账请求按照传入的分账接收方账号和资金进行分账,同时会将订单剩余的待分账金额解冻给本商户。故操作成功后,订单不能再进行分账,也不能进行分账完结。
681 0
微信开发之普通直连分账(境内普通商户)
|
算法
支付宝支付配置公私钥
支付宝支付配置公私钥
351 0
|
测试技术 定位技术 开发工具
支付交易风险控制小知识:iOS设备限制境外交易
支付交易风险控制小知识:iOS设备限制境外交易
290 0
支付交易风险控制小知识:iOS设备限制境外交易
|
开发工具 前端开发 Java
芝麻信用授权接入
芝麻信用授权 背景介绍 芝麻信用分在金融风控方面可以提供,参考和应用,官方也给出了相应的开放平台,可以申请用户授权,查询。这里是官方接入文档页面授权,这个是H5版接入文档,通过这种方式接入可以跨平台应用在各个客户端。
8923 0
|
Linux Shell 区块链
LTC 主网钱包linux安装(付款的加密货币 基于区块链技术)
Litecoin是一种点对点Internet货币,可以向世界上任何人即时支付接近零的费用。莱特币是一个开源的全球支付网络,无需任何中央机构就可以完全分散化。数学可以保护网络安全,并可以使个人控制自己的财务状况。
651 0