Android DUKPT - 3DES

发布于:2025-03-12 ⋅ 阅读:(29) ⋅ 点赞:(0)

 一、DUKPT概述

DUKPT 即Derived Unique Key Per Transaction(每个事务的派生唯一密钥)。ANSI X9.24规范定义的密钥管理体系,主要用于对称密钥加密场景(如MAC、PIN等敏感数据保护)。通过动态生成唯一交易密钥,解决传统固定密钥易被破解的安全风险。

二、组成结构

DUKPT由BDK及KSN组成。

  1. 基础主密钥(BDK)

    • 作为根密钥(Base Derivation Key),通过加密模块生成初始密钥

    • 通常为双倍长3DES密钥(如128位或192位

  2. 密钥序列号(KSN)

    • 包含三部分:
      • 密钥标识(10位:9位基础派生标识 + 1位子密钥标识)

      • 设备标识(5位,含二进制位扩展)

      • 交易计数器(5位,记录交易次数)

    • 确保终端密钥唯一性,防止重复

三、秘钥衍生过程

  1. 根密钥准备
    收单机构通过HSM生成双倍长3DES的BDK(如0123456789ABCDEFFEDCBA9876543210),该密钥需满足FIPS 140-2 Level 3以上安全标准。

  2. KSN结构解析
    KSN由三部分构成:

    • 设备标识:10位十六进制(如2900080124)包含厂商代码和设备序列号

    • 交易计数器:21位二进制(如00021E00001)记录交易次数

    • 扩展位:1位二进制用于标识密钥用途

  3. IPEK生成算法
    通过3DES算法对BDK和设备标识进行加密运算

  4.  动态密钥派生
    使用IPEK及KSN进行系列异或运算最终获得当前加密PIN的PEK。

四、代码实现   

        代码作者 Antoine Averlant

// 基于BDK及KSN生成当前PIN秘钥。
public static byte[] computeKeyFromBDK(byte[] baseDerivationKey, byte[] keySerialNumber) throws Exception {
        BitSet ksn = toBitSet(keySerialNumber);
        BitSet bdk = toBitSet(baseDerivationKey);
        BitSet ipek = getIpek(bdk, ksn);

        // convert key for returning
        BitSet key = _getCurrentKey(ipek, ksn);
        byte[] rkey = toByteArray(key);

        // secure memory
        obliviate(ksn);
        obliviate(bdk);
        obliviate(ipek);
        obliviate(key);

        return rkey;
}

// 基于BDK及KSN生成Ipek
public static BitSet getIpek(BitSet key, BitSet ksn) throws Exception {
        byte[][] ipek = new byte[2][];
        BitSet keyRegister = key.get(0, key.length());
        BitSet data = ksn.get(0, ksn.length());
        data.clear(59, 80);

        ipek[0] = encryptTripleDes(toByteArray(keyRegister), toByteArray(data.get(0, 64)));

        keyRegister.xor(toBitSet(toByteArray("C0C0C0C000000000C0C0C0C000000000")));
        ipek[1] = encryptTripleDes(toByteArray(keyRegister), toByteArray(data.get(0, 64)));

        byte[] bipek = concat(ipek[0], ipek[1]);
        BitSet bsipek = toBitSet(bipek);

        // secure memory
        obliviate(ipek[0]);
        obliviate(ipek[1]);
        obliviate(bipek);
        obliviate(keyRegister);
        obliviate(data);

        return bsipek;
}

// 基于IPEK及KSN生成PEK
private static BitSet _getCurrentKey(BitSet ipek, BitSet ksn) throws Exception {
        BitSet key = ipek.get(0, ipek.length());
        BitSet counter = ksn.get(0, ksn.length());
        counter.clear(59, ksn.length());

        for (int i = 59; i < ksn.length(); i++) {
            if (ksn.get(i)) {
                counter.set(i);
                BitSet tmp = _nonReversibleKeyGenerationProcess(key, counter.get(16, 80));
                // secure memory
                obliviate(key);
                key = tmp;
            }
        }
        key.xor(toBitSet(toByteArray("00000000000000FF00000000000000FF"))); // data encryption variant (To PIN)
        // key.xor(toBitSet(toByteArray("F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0"))); // data encryption variant
        // key.xor(toBitSet(toByteArray("3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C"))); // data encryption variant

        // secure memory
        obliviate(counter);

        return key;
}

private static BitSet _nonReversibleKeyGenerationProcess(BitSet p_key, BitSet data) throws Exception {
        BitSet keyreg = p_key.get(0, p_key.length());
        BitSet reg1 = data.get(0, data.length());
        // step 1: Crypto Register-1 XORed with the right half of the Key Register goes to Crypto Register-2.
        BitSet reg2 = reg1.get(0, 64); // reg2 is being used like a temp here
        reg2.xor(keyreg.get(64, 128));   // and here, too, kind of
        // step 2: Crypto Register-2 DEA-encrypted using, as the key, the left half of the Key Register goes to Crypto Register-2
        reg2 = toBitSet(encryptDes(toByteArray(keyreg.get(0, 64)), toByteArray(reg2)));
        // step 3: Crypto Register-2 XORed with the right half of the Key Register goes to Crypto Register-2
        reg2.xor(keyreg.get(64, 128));
        // done messing with reg2

        // step 4: XOR the Key Register with hexadecimal C0C0 C0C0 0000 0000 C0C0 C0C0 0000 0000
        keyreg.xor(toBitSet(toByteArray("C0C0C0C000000000C0C0C0C000000000")));
        // step 5: Crypto Register-1 XORed with the right half of the Key Register goes to Crypto Register-1
        reg1.xor(keyreg.get(64, 128));
        // step 6: Crypto Register-1 DEA-encrypted using, as the key, the left half of the Key Register goes to Crypto Register-1
        reg1 = toBitSet(encryptDes(toByteArray(keyreg.get(0, 64)), toByteArray(reg1)));
        // step 7: Crypto Register-1 XORed with the right half of the Key Register goes to Crypto Register-1
        reg1.xor(keyreg.get(64, 128));
        // done

        byte[] reg1b = toByteArray(reg1), reg2b = toByteArray(reg2);
        byte[] key = concat(reg1b, reg2b);
        BitSet rkey = toBitSet(key);

        // secure memory
        obliviate(reg1);
        obliviate(reg2);
        obliviate(reg1b);
        obliviate(reg2b);
        obliviate(key);
        obliviate(keyreg);

        return rkey;
}

public static byte[] encryptTripleDes(byte[] key, byte[] data) throws Exception {
        return encryptTripleDes(key, data, true);
}

public static byte[] encryptTripleDes(byte[] key, byte[] data, boolean padding) throws Exception {
        BitSet bskey = toBitSet(key);
        BitSet k1, k2, k3;
        if (bskey.length() == 64) {
            // single length
            k1 = bskey.get(0, 64);
            k2 = k1;
            k3 = k1;
        } else if (bskey.length() == 128) {
            // double length
            k1 = bskey.get(0, 64);
            k2 = bskey.get(64, 128);
            k3 = k1;
        } else {
            // triple length
            if (bskey.length() != 192) {
                throw new InvalidParameterException("Key is not 8/16/24 bytes long.");
            }
            k1 = bskey.get(0, 64);
            k2 = bskey.get(64, 128);
            k3 = bskey.get(128, 192);
        }
        byte[] kb1 = toByteArray(k1), kb2 = toByteArray(k2), kb3 = toByteArray(k3);
        byte[] key16 = concat(kb1, kb2);
        byte[] key24 = concat(key16, kb3);

        IvParameterSpec iv = new IvParameterSpec(new byte[8]);
        SecretKey encryptKey = SecretKeyFactory.getInstance("DESede").generateSecret(new DESedeKeySpec(key24));
        Cipher encryptor;
        if (padding)
            encryptor = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        else
            encryptor = Cipher.getInstance("DESede/CBC/NoPadding");
        encryptor.init(Cipher.ENCRYPT_MODE, encryptKey, iv);
        byte[] bytes = encryptor.doFinal(data);

        // secure memory
        obliviate(k1);
        obliviate(k2);
        obliviate(k3);
        obliviate(kb1);
        obliviate(kb2);
        obliviate(kb3);
        obliviate(key16);
        obliviate(key24);
        obliviate(bskey);

        return bytes;
}

public static byte[] MEKQ(byte[] bPEK) throws Exception {
        BitSet pek = toBitSet(bPEK);

        pek.xor(toBitSet(toByteArray("000000000000FFFF000000000000FFFF")));
        byte[] rkey = toByteArray(pek);
        // secure memory
        obliviate(pek);

        return rkey;
}

// 核心MAC计算逻辑 ISO-9797-PART1
    public static byte[] calculateMac(byte[] keyData, byte[] inputData) throws Exception {
        // 1. 处理双倍长密钥(16字节 -> 24字节)
        byte[] fullKey = Arrays.copyOf(keyData, 24);
        byte[] key1 = new byte[16];
        System.arraycopy(fullKey, 0, fullKey, 16, 8);
        System.arraycopy(keyData, 0, key1, 0, 8);
        System.arraycopy(keyData, 0, key1, 8, 8);

        // 2. 初始化加密器(CBC模式 + 零向量)
        Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key1, "DESede"), new IvParameterSpec(new byte[8]));

        // 3. 数据填充(补零至8字节倍数)
        byte[] paddedData;
        if (inputData.length % 8 != 0) {
            paddedData = Arrays.copyOf(inputData, inputData.length + (8 - inputData.length % 8));
        } else {
            paddedData = Arrays.copyOf(inputData, inputData.length);
        }
        int count = paddedData.length/8;
        byte[] paddedBlock = new byte[8];
        byte[] encryptData = new byte[8];
        //拆分数据--加密--异或
        for (int i = 0; i < count; i++) {
            if (i == 0) {
                System.arraycopy(paddedData, 0, paddedBlock, 0, 8);
            }
            encryptData = cipher.doFinal(paddedBlock);
            if (i + 2 < count) {
                System.arraycopy(paddedData, (i+1) * 8, paddedBlock, 0, 8);
                myXor(paddedBlock, encryptData);
            } else {
                System.arraycopy(paddedData, (i+1) * 8, paddedBlock, 0, 8);
                myXor(paddedBlock, encryptData);
                break;
            }
        }

        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(fullKey, "DESede"), new IvParameterSpec(new byte[8]));
        encryptData = cipher.doFinal(paddedBlock);

        // 4. 执行加密并取前8字节
        return Arrays.copyOfRange(encryptData, 0, encryptData.length);
}

    public static void myXor(byte[] a, byte[] b) {
        if (a.length != b.length)
            return;

        for (int i = 0; i < a.length; i++)
            a[i] ^= b[i];
}

/**
     * Converts a byte array to an extended BitSet.
     */
    public static BitSet toBitSet(byte[] b) {
        BitSet bs = new BitSet(8 * b.length);
        for (int i = 0; i < b.length; i++) {
            for (int j = 0; j < 8; j++) {
                if ((b[i] & (1L << j)) > 0) {
                    bs.set(8 * i + (7 - j));
                }
            }
        }
        return bs;
    }

    /**
     * Converts an extended BitSet into a byte.
     * <p>
     * Requires that the BitSet be exactly 8 bits long.
     */
    public static byte toByte(BitSet b) {
        byte value = 0;
        for (int i = 0; i < b.length(); i++) {
            if (b.get(i))
                value = (byte) (value | (1L << 7 - i));
        }
        return value;
    }

    /**
     * Converts a BitSet into a byte array.
     * <p>
     * Pads to the left with zeroes.
     */
    public static byte[] toByteArray(BitSet b) {
        int size = (int) Math.ceil(b.length() / 8.0d);
        byte[] value = new byte[size];
        for (int i = 0; i < size; i++) {
            value[i] = toByte(b.get(i * 8, Math.min(b.length(), (i + 1) * 8)));
        }
        return value;
    }

    /**
     * Converts a hexadecimal String into a byte array (Big-Endian).
     *
     * @param s A representation of a hexadecimal number without any leading qualifiers such as "0x" or "x".
     */
    public static byte[] toByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    /**
     * Converts a byte array into a hexadecimal string (Big-Endian).
     *
     * @return A representation of a hexadecimal number without any leading qualifiers such as "0x" or "x".
     */
    public static String toHex(byte[] bytes) {
        BigInteger bi = new BigInteger(1, bytes);
        return String.format("%0" + (bytes.length << 1) + "X", bi);
    }

    /**
     * Concatenates two byte arrays.
     *
     * @return The array a concatenated with b.  So if r is the returned array, r[0] = a[0] and r[a.length] = b[0].
     */
    public static byte[] concat(byte[] a, byte[] b) {
        byte[] c = new byte[a.length + b.length];
        for (int i = 0; i < a.length; i++) {
            c[i] = a[i];
        }
        for (int i = 0; i < b.length; i++) {
            c[a.length + i] = b[i];
        }
        return c;
    }

    /**
     * Overwrites the extended BitSet NUM_OVERWRITES times with random data for security purposes.
     */
    public static void obliviate(BitSet b) {
        obliviate(b, NUM_OVERWRITES);
    }

    /**
     * Overwrites the byte array NUM_OVERWRITES times with random data for security purposes.
     */
    public static void obliviate(byte[] b) {
        obliviate(b, NUM_OVERWRITES);
    }

    /**
     * Overwrites the extended BitSet with random data for security purposes.
     */
    public static void obliviate(BitSet b, int n) {
        java.security.SecureRandom r = new java.security.SecureRandom();
        for (int i = 0; i < NUM_OVERWRITES; i++) {
            for (int j = 0; j < b.length(); j++) {
                b.set(j, r.nextBoolean());
            }
        }
    }

    /**
     * Overwrites the byte array with random data for security purposes.
     */
    public static void obliviate(byte[] b, int n) {
        for (int i = 0; i < n; i++) {
            b[i] = 0x00;
            b[i] = 0x01;
        }

        java.security.SecureRandom r = new java.security.SecureRandom();
        for (int i = 0; i < n; i++) {
            r.nextBytes(b);
        }
    }


网站公告

今日签到

点亮在社区的每一天
去签到