加密机体系

笔者目前使用的jdk版本是1.6.0_29,Eclipse版本是Juno Release,Build id 20120614-1722。加密机使用卫士通SJL05型号金融数据加密机。如无特殊说明,本文所有的Java代码都是基于此。

修订记录

版本号 修订日期 修订说明
V0.1 2018/09/28 初稿
V1.0 2018/10/12 发布

参考资料

  1. Q/CUP 006.4-2015 中国银联股份有限公司企业标准 中国银联银行卡交换系统技术规范(国际卷) 第4部分 数据安全传输控制规范
  2. SJL05型金融数据加密机程序员手册

银联密钥体系

约定中国银联银行卡信息交换系统(CUPS)与每个入网机构之间的密钥,由三层构成:


第一层:MK

Master Key,主密钥。加密机最高层密钥,人工方式输入产生,由三部分构成,分别由三人掌管,存储于加密机中,受加密机硬件设备保护。

第二层:MMK

Member Master Key,成员主密钥。指在银行卡安全体系中,分配给成员机构的密钥加密密钥,用于加密下一层密钥,受主密钥(MK)加密保护。CUPS与入网机构各产生一半,在硬件设备中合成;或由CUPS在加密机中随机生成并散列出2个分量;或由双方商定该密钥的产生办法。

第三层:PIK、MAK

  • PIN Key,用于加密PIN的密钥。
  • MAC Key,用于生成交易报文合法性校验数据(MAC)的密钥。

统称为数据密钥,Data Key,受成员主密钥(MMK)加密保护。由硬件加密机中的随机发生器产生。CUPS的加密机产生数据密钥,入网机构接收和存储CUPS发来的数据密钥。当CUPS认为需要时,可以主动向入网机构发起重置密钥报文。当入网机构需要新密钥的时候,必须向CUPS发出申请重置密钥报文。

HSM

Hardware and security module,硬件加密机,对传输的数据进行加密的外围硬件设备,用于PIN的加密和解密、验证报文和文件来源的正确性以及存储密钥。

顾名思义,加密机就是一台机器,其优势在于硬件设备存储密钥的安全性以及加解密运算的高效性。

图片来源于卫士通官网,金融数据密码机产品介绍

排除行业要求必须使用加密机的场景,大家可以根据实际情况判断是否使用加密机,并不是使用加密机就一定好,符合自身需要的才是最好的。

卫士通

卫士通信息产业股份有限公司成立于1998年,由中国电子科技集团公司第30研究所发起成立。依托30所40年深厚的专业技术及人才资源积淀,凭借高效的现代企业运作机制和持续的战略创新,卫士通已发展成为我国最具主导地位的信息安全产业龙头企业,以此为核心拓展税务电子化、金融电子化、电子商务等安全IT化业务,实现企业规模化发展。并于2008年7月成功上市,成为“中国信息安全第一股”。

笔者就职公司使用加密机采购于卫士通,笔者也未曾接触过其他品牌加密机,因此本篇剩余部分仅涉及卫士通加密机,型号选择SJL05,因为SJL05型号加密机文档可以轻易从网上获取,不存在泄密问题,至于其他型号或其它品牌加密机使用,了解了原理,总归大同小异,举一反三不难。

SJL05

查阅《SJL05型金融数据加密机程序员手册》,虽然其中未明确SJL05加密机中密钥体系的定义,但是仍然可以根据其中接口推测出符合银联密钥体系规范的3层结构:


第一层:LMK

Local Master Key,本地主密钥。相当于银联密钥体系中的MK。

第二层:ZMK/BMK、TMK/ATK

  • ZMK,Zone Master Key,区域主密钥。
  • TMK,Terminal Master Key,终端主密钥。

相当于银联密钥体系中的MMK。

第三层:ZAK、ZPK、TAK、TPK

  • ZAK,Zone Authentication Key,区域认证密钥。
  • ZPK,Zone PIN Key,区域PIN密钥。
  • TAK,Terminal Administrative Key,终端认证密钥。
  • TPK,Terminal PIN encryption Key,终端PIN密钥。

ZAK、TAK相当于银联密钥体系中的MAK;ZPK、TPK相当于银联密钥体系中的PIK。

在特定应用场景,还可能衍生出更加复杂的密钥体系,比如4层结构:

但是,不管是哪种密钥体系,密钥明文不会暴露在外,上层密钥保护下层密钥,最上层密钥由加密机硬件保护的原理是不变的。

加密机实际应用

本章节介绍POS终端业务场景下对SJL05型号加密机具体接口的调用。各金融机构调用逻辑不尽相同,笔者仅提出一种基本思路,大家在实际应用时应当因地制宜切忌生搬硬套,如有不妥之处请大家批评指正。

首先,看一下基本的数据流图,实际可能更加复杂。

  • POS终端以P表示
  • 收单机构以A表示
  • 转接机构以B表示
  • 发卡机构以C表示

POS终端密钥体系:

收单机构密钥体系:

转接机构密钥体系:

发卡机构密钥体系:


涉及加密机接口列表

在整个交易流程中,笔者提出SJL05型号加密机接口的调用逻辑,涉及如下接口。

命令码 名称 说明
0X08 产生并存储一个指定长度的主密钥,并打印明文到密码信封 产生一个指定长度的区域主密钥或终端主密钥或本地主密钥,存储在加密机指定位置,同时将明文密钥通过串口打印机打印到密码信封上,如果密钥类型为区域主密钥或终端主密钥,将同时返回用LMK加密的密文。
0XD108 加密明文密钥 输入指定长度的明文密钥,并用加密机本地主密钥加密后返回其密文密钥和校验码
0X0510 用本地主密钥加密终端主密钥 输入一个TMK明文,用本地主密钥加密后输出,并存放于主机系统中
0X0512 产生终端数据密钥(用TMK和LMK加密) 加密机产生一个数据密钥,分别用终端主密钥TMK和本地主密钥(LMK)加密后输出
0X0410 请求产生MAC(变长MAK) 请求产生MAC
0X0411 请求验证MAC(变长MAK) 请求验证MAC
0X0402 PINBLOCK转换(任意长度PIK) PINBLOCK转换
0X76 用输入密钥对数据加/解密,再对数据进行MD5运算 利用输入的16字节密钥对数据做3DES/DES的CBC或ECB加密或解密,最后对加解密结果进行MD5摘要运算

密钥初始化

  1. 转接机构初始化本地主密钥B_LMK(0X08),生成分发给收单机构的区域主密钥AB_ZMK(0X08),生成分发给发卡机构的区域主密钥BC_ZMK(0X08)。在转接机构加密机中,B_LMK明文由加密机硬件保护,AB_ZMKBC_ZMKB_LMK加密保护;
  2. 收单机构初始化本地主密钥A_LMK(0X08),录入转接机构分发的区域主密钥AB_ZMK(0XD108),录入与POS终端约定的终端主密钥PA_TMK(0X0510)。收单机构加密机中,A_LMK明文由加密机硬件保护,AB_ZMKPA_TMKA_LMK加密保护;
  3. 发卡机构初始化本地主密钥C_LMK(0X08),录入转接机构分发的区域主密钥BC_ZMK(0XD108)。发卡机构加密机中,C_LMK明文由加密机硬件保护,BC_ZMKC_LMK加密保护;
  4. POS终端录入终端主密钥PA_TMKPA_TMK明文由POS终端安全模块保护。

签到

  1. 收单机构转接机构请求数据密钥,包括:区域认证密钥AB_ZAK,区域PIN密钥AB_ZPK。在收单机构转接机构加密机中,AB_ZAKAB_ZPK由区域主密钥AB_ZMK保护;
  2. 发卡机构转接机构请求数据密钥,包括:区域认证密钥BC_ZAK,区域PIN密钥BC_ZPK。在发卡机构转接机构加密机中,BC_ZAKBC_ZPK由区域主密钥BC_ZMK保护;
  3. POS终端收单机构请求数据密钥(0X0512),包括:终端认证密钥PA_TAK,终端PIN密钥PA_TPK。在POS终端安全模块和收单机构加密机中,PA_TAKPA_TPK由终端主密钥PA_TMK保护。

交易请求

  1. POS终端刷卡,使用PA_TPK加密密码(PIN block),PA_TAK生成交易报文MAC,上送交易报文至收单机构
  2. 收单机构验证MAC(0X0411),使用AB_ZPK转加密PIN block(0X0402),AB_ZAK生成交易报文MAC(0X0410),上送交易报文至转接机构;转加密过程是PA_TMK解密PA_ZPK,PA_ZPK解密PIN block,AB_ZMK解密AB_ZPK,AB_ZPK加密PIN block
  3. 转接机构验证MAC(0X0411),使用BC_ZPK转加密PIN block(0X0402),BC_ZAK生成交易报文MAC(0X0410),上送交易报文至发卡机构;转加密过程是AB_ZMK解密AB_ZPK,AB_ZPK解密PIN block,BC_ZMK解密BC_ZPK,BC_ZPK加密PIN block
  4. 发卡机构验证MAC(0X0411),使用BC_ZPK转加密PIN block(0X76),校验PIN block的哈希值;
  5. 交易完成后原路返回;转加密过程是BC_ZMK解密BC_ZPK,BC_ZPK解密PIN block,PIN block计算哈希值

以<0X76>接口为例编写代码

消息格式

输入域 长度 类型 备注
命令 1 H 0x76
银行索引号 2 H 区域主密钥索引号
密钥长度 1 H 0x08 ; 0x10
密钥KEY 16 H 加/解数据的密钥(zmk加密)
初始向量 8 H CBC加密的初始向量
加/解密标识 1 H 1为加密,0为解密
算法标识 1 H 第0位:ECB=0,CBC=1
第1位:3DES=0,DES=1
LEN 2 H 处理数据的长度
数据 N H N=LEN
输出域 长度 类型 备注
应答码 1 A “A”
摘要数据 16 H
应答码 1 A “E”
错误码 1 H 0x0C:非法银行主密钥索引号
0x59:输入的数据长度错误
0x2C:非法银行主密钥
0x10:无效的算法模式

SJL05处理过程

  1. 取出指定的区域主密钥ZMK;
  2. 用ZMK对输入的密钥KEY做3DES解密,得到密钥明文PKEY;
  3. 检查输入数据长度是否为8的倍数,不是,补0x00使之长度为8的倍数;
  4. 根据加/解密标识和算法标识,用PKEY对DATA做3DES/DES的CBC/ECB加密或解密;
  5. 对加密或解密的结果进行MD5摘要运算;
  6. 如果成功,返回16字节的MD5摘要结果。

代码示例

创建加密机命令父类

package com.godson.util.hsm;

import java.io.InputStream;
import java.io.OutputStream;

/**
 * 加密机命令父类
 */
abstract public class HSMCommand {

    // 错误码
    protected int errorCode;

    // 封装消息报文
    abstract public void packetInputField(OutputStream os) throws Exception;

    // 解析响应报文
    public boolean parseOutputField(InputStream is) throws Exception {
        // 应答码
        int retCode = is.read();
        if ('E' == (char) retCode) {
            errorCode = is.read();
            return false;
        }
        return true;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }
}

继承父类,实现0x76指令类

package com.godson.util.hsm;

import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;

/**
 * 0X76 用输入密钥对数据加/解密,再对数据进行MD5运算
 * 
 * <pre>
 * 利用输入的16字节密钥对数据做3DES/DES的CBC或ECB加密或解密,最后对加解密结果进行MD5摘要运算。
 * </pre>
 */
public class Cmd0X76 extends HSMCommand {
    private short zmkIndex;
    private byte keyLen;
    private byte[] key;
    private byte[] initVect;
    private byte enOrDe;
    private byte algorithm;
    private short dataLen;
    private byte[] data;
    private byte[] md;

    @Override
    public void packetInputField(OutputStream os) throws Exception {
        int len = 32 + dataLen;
        ByteBuffer buf = ByteBuffer.allocate(len);
        buf.put((byte) 0x76); // 命令,0x76
        buf.putShort(zmkIndex); // 银行索引号,区域主密钥索引号
        buf.put(keyLen); // 密钥长度
        buf.put(key); // 加/解数据的密钥(zmk加密)
        buf.put(initVect); // CBC加密的初始向量
        buf.put(enOrDe); // 加/解密标识,1为加密,0为解密
        buf.put(algorithm); // 算法标识,第0位:ECB=0,CBC=1,第1位:3DES=0,DES=1
        buf.putShort(this.dataLen); // 处理数据的长度
        buf.put(data); // 数据
        os.write(buf.array());
    }

    @Override
    public boolean parseOutputField(InputStream is) throws Exception {
        if (!super.parseOutputField(is)) {
            return false;
        }
        DataInputStream dis = new DataInputStream(is);
        md = new byte[16];
        dis.read(md); // 摘要数据
        return true;
    }

    public short getDataLen() {
        return dataLen;
    }

    public void setDataLen(short dataLen) {
        this.dataLen = dataLen;
    }

    public short getZmkIndex() {
        return zmkIndex;
    }

    public void setZmkIndex(short zmkIndex) {
        this.zmkIndex = zmkIndex;
    }

    public byte getKeyLen() {
        return keyLen;
    }

    public void setKeyLen(byte keyLen) {
        this.keyLen = keyLen;
    }

    public byte[] getKey() {
        return key;
    }

    public void setKey(byte[] key) {
        this.key = key;
    }

    public byte[] getInitVect() {
        return initVect;
    }

    public void setInitVect(byte[] initVect) {
        this.initVect = initVect;
    }

    public byte getEnOrDe() {
        return enOrDe;
    }

    public void setEnOrDe(byte enOrDe) {
        this.enOrDe = enOrDe;
    }

    public byte getAlgorithm() {
        return algorithm;
    }

    public void setAlgorithm(byte algorithm) {
        this.algorithm = algorithm;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }

    public byte[] getMd() {
        return md;
    }

    public void setMd(byte[] md) {
        this.md = md;
    }
}

加密机调用模块

package com.godson.util.hsm;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 加密机模块
 */
public class HSMUtil {

    private String host; // 加密机IP地址
    private int port; // 加密机端口

    public HSMUtil(String host, int port) {
        this.host = host;
        this.port = port;
    }

    // 执行加密机指令
    public boolean execute(HSMCommand command) throws Exception {
        Socket sock = null;
        OutputStream os = null;
        InputStream is = null;

        try {
            // 建立Socket连接
            sock = new Socket(host, port);
            sock.setSoTimeout(5000);
            // 封装消息报文,并送给加密机
            os = sock.getOutputStream();
            command.packetInputField(os);
            os.flush();
            // 从加密机接受响应报文
            is = sock.getInputStream();
            return command.parseOutputField(is);
        } finally {
            // 关闭加密机读写流和连接
            try {
                if (os != null) {
                    os.close();
                    os = null;
                }
                if (is != null) {
                    is.close();
                    is = null;
                }
                if (sock != null) {
                    sock.close();
                    sock = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

这里的加密机调用方式是有缺陷的,短连接在并发量较高时,容易产生大量的TIME_WAIT状态,产生TIME_WAIT的原因是:TCP在四次挥手关闭连接时,(假设客户端主动关闭连接)客户端在发送ACK后需要等待一段时间确保服务器端已成功接收ACK或未接收ACK可正常重发FIN,以避免发生服务器错误。

网上多数提出的解决方案是修改服务器参数:

修改/etc/sysctl.conf :

net.ipv4.tcp_syncookies = 1 #表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1 #表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1 #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。>net.ipv4.tcp_timestamps开启时,net.ipv4.tcp_tw_recycle开启才能生效
net.ipv4.tcp_timestamps = 1 #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout = 30 #用来设置保持在FIN_WAIT_2状态的时间

保存后执行/sbin/sysctl -p生效。

笔者认为不可轻易修改服务器参数,应尽量从程序本身寻找解决问题的突破口,以长连接代替短连接是比较好的方式,但是从加密机接口文档中发现其并不支持长连接,不过仍然可以采用连接池等方案来减少socket的close,留给大家思考实现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容