银联POS工作密钥生成

来源:互联网 发布:unity3d游戏架构教程 编辑:程序博客网 时间:2024/05/12 20:43

根据银联POS终端应用规范,我们了解到POS交易中保函两类密钥:
密钥加密密钥 key encryption key; KEK和工作密钥 working key; WK
而工作密钥有包括:PIN加密密钥、 MAC计算的密钥和磁道数据加密密钥

关于以上密钥的定义,银联规范中是这样解释的:

密钥加密密钥 key encryption key; KEK
POS终端工作时对工作密钥进行加密的密钥,由专门人员设置并直接保存在系统硬件中,只能使用,
不能读取,该密钥必须与加密算法放在同一加密芯片里,又称终端主密钥。

工作密钥 working key; WK
也称为数据密钥,通常指PIN加密密钥、 MAC计算的密钥和磁道数据加密密钥。 工作密钥必须经常
更新。在联机更新的报文中对工作密钥必须用密钥加密密钥( KEK)加密,形成密文后进行传输。

密钥加密密钥,我们也称之为主密钥,为32位的16进制字符串

在规范里,讲到第62域,用法一:终端密钥,域描述中写到:

*POS中心向POS终端约定的新工作密钥,若长度域不为024或040或056或060或84,将认为该域格式有误。
在POS终端签到的应答消息中,本域的长度应为24或40或56或60或84个字节,存放由POS中心产生的POS终端新工作密钥。
对于单倍长密钥算法,前12个字节为PIN的工作密钥的密文, 后12个字节为MAC的工作密钥的密文。(其中,前8个字节是密文,后4个字节是checkvalue;前8个字节解出明文后,对8个数值0做单倍长密钥算法,取结果的前四位与checkvalue 的值比较应该是一致的)。
对于双倍长密钥算法,前20个字节为PIN的工作密钥的密文,后20个字节为MAC的工作密钥的密文。(其中,“ PIN工作密钥”前16个字节是密文,后4个字节是checkvalue;前16个字节解出明文后,对8个数值0做双倍长密钥算法,取结果的前四位与checkvalue 的值比较应该是一致的;“ MAC工作密钥”前8个字节是密文,再8个字节是二进制零,后4个字节是checkvalue;前8个字节解出明文后,对8个数值0做单倍长密钥算法,取结果的前四位与checkvalue 的值比较应该是一致的)。
对于支持磁道加密的签到报文本域长度为60字节,采用双倍长密钥算法,前20个字节为PIN的工作密钥的密文,中间20个字节为MAC的工作密钥,后面20个字节为TDK的工作密钥的密文。(其中,“ PIN工作密钥”前16个字节是密文,后4个字节是checkvalue;前16个字节解出明文后,对8个数值0做双倍长密钥算法, 取结果的前四位与checkvalue 的值比较应该是一致的;“ MAC工作密钥”前8个字节是密文,再8个字节是二进制零,后4个字节是checkvalue;前8个字节解出明文后,对8个数值0做单倍长密钥算法,取结果的前四位与checkvalue 的值比较应该是一致的;“ TDK工作密钥”前16个字节是密文,后4个字节是checkvalue;前16个字节解出明文后,对8个数值0做双倍长密钥算法,取结果的前四位与checkvalue的值比较应该是一致的)*

在这里我就按最复杂的第三种来实现(java):
第一步:获取终端的主密钥,我们采用随机生成的方式来生成,生成的主密钥需要手动设置到POS机中

public static String randomHexString(int len)  {    try {            StringBuffer result = new StringBuffer();            for(int i=0;i<len;i++) {                result.append(Integer.toHexString(new Random().nextInt(16)));            }            return result.toString().toUpperCase();    } catch (Exception e) {        // TODO: handle exception        e.printStackTrace();    }    return null;}

第二步,POS终端签到,获取工作密钥
1、生成PIN的工作密钥:

String pik = KeyUtil.randomHexString(32);

2、拿主密钥对PIN工作密钥进行双倍长加密,生成前16位的密文

String pikEnc = DesUtil.doubleDesEncrypt(minKey, pik);

3、拿PIN工作密钥对8个数值0做双倍长加密,生成checkValue

String checkValue = DesUtil.doubleDesEncrypt(pik, "0000000000000000").substring(0,8); 

4、拼接,生成最终要返回的结果

String pik = pikEnc + checkValue;

TDK工作密钥和PIN工作密钥生成规则一样

MAC工作密钥生成:

String mak = KeyUtil.randomHexString(16);String makEnc = DesUtil.doubleDesEncrypt(minKey, mak);String checkValue = DesUtil.desEncrypt(mak, "0000000000000000").substring(0,8); //单倍长加密String mak = makEnc + "0000000000000000"  + checkValue;

附:双倍长加密算法:

public static String doubleDesEncrypt(String key,String data) throws Exception{    return bytesToHexString(doubleDesEncrypt(hexStringToByte(key), hexStringToByte(data)));}public static byte[] doubleDesEncrypt(byte[] key,byte[] data) throws Exception{    byte[] result = null;    if(key.length != 16) {        throw new Exception("expected length of des key is 16! [" + key.length +"]");    }    // 拆分密钥    byte[] keyLeft = new byte[8];    byte[] keyRight = new byte[8];    System.arraycopy(key, 0, keyLeft, 0, 8);    System.arraycopy(key, 8, keyRight, 0, 8);    // 使用Left进行加密    byte[] tmp = desEncrypt(keyLeft, data);    tmp = desDecrypt(keyRight, tmp);    result = desEncrypt(keyLeft, tmp);    return result;}

单倍长加密算法:

public static String desEncrypt(String key,String data) throws Exception{    return bytesToHexString(desEncrypt(hexStringToByte(key), hexStringToByte(data)));}public static byte[] desDecrypt(byte[] key,byte[] data) throws Exception{    return des(key, data, Cipher.DECRYPT_MODE);}private static byte[] des(byte[] key,byte[] data,int opmode) throws Exception{        byte[] result = null;        try {            // 数据长度必须为8的倍数            if(data.length%8 != 0) {                throw new Exception("expected length of des data must multiple of 8! [" + data.length +"]");            }            SecretKeyFactory keyFactory;            DESKeySpec dks = new DESKeySpec(key);            keyFactory = SecretKeyFactory.getInstance("DES");            SecretKey secretkey = keyFactory.generateSecret(dks);            //创建Cipher对象            Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");            //初始化Cipher对象            cipher.init(opmode, secretkey);            result = new byte[data.length];            // 如果数据超过8位,循环每8位进行加解密,然后进行拼接            int offset = 0;            for (int i = 0; i < data.length/8; i++) {                // 需要处理的数据逐8位取出                byte[] tmp = new byte[8];                System.arraycopy(data, offset, tmp, 0, 8);                // 进行加解密计算                byte[] tmpResult = cipher.doFinal(tmp);                // 放入返回结果中                System.arraycopy(tmpResult, 0, result, offset, 8);                offset += 8;            }        } catch (Exception e) {            e.printStackTrace();            throw new SeaException(e);        }        return result;    }
0 0
原创粉丝点击