Android DUKPT - 3DES

news2025/3/12 21:27:05

 一、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);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2313940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《苍穹外卖》SpringBoot后端开发项目核心知识点与常见问题整理(DAY1 to DAY3)

目录 一、在本地部署并启动Nginx服务1. 解压Nginx压缩包2. 启动Nginx服务3. 验证Nginx是否启动成功&#xff1a; 二、导入接口文档1. 黑马程序员提供的YApi平台2. YApi Pro平台3. 推荐工具&#xff1a;Apifox 三、Swagger1. 常用注解1.1 Api与ApiModel1.2 ApiModelProperty与Ap…

QT系列教程(20) Qt 项目视图便捷类

视频连接 https://www.bilibili.com/video/BV1XY41127t3/?vd_source8be9e83424c2ed2c9b2a3ed1d01385e9 Qt项目视图便捷类 Qt项目视图提供了一些便捷类&#xff0c;包括QListWidget, QTableWidget&#xff0c; QTreeWidget等。我们分别介绍这几个便捷类。 我们先创建一个Qt …

动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南

目录 一、典型案例&#xff1a;系统发布后的GC雪崩事件 &#xff08;一&#xff09;故障现象 1. 刚刚启动时 GC 次数较多 2. 堆内存锯齿状波动 3. GC日志特征&#xff1a;Allocation Failure &#xff08;二&#xff09;问题定位 二、原理深度解析&#xff1a;JVM内存弹…

AI智能眼镜主控芯片:技术演进与产业生态的深度解析

一、AI智能眼镜的技术挑战与主控芯片核心诉求 AI智能眼镜作为XR&#xff08;扩展现实&#xff09;技术的代表产品&#xff0c;其核心矛盾在于性能、功耗与体积的三角平衡。主控芯片作为设备的“大脑”&#xff0c;需在有限空间内实现复杂计算、多模态交互与全天候续航&#xf…

微服务拆分-远程调用

我们在查询购物车列表的时候&#xff0c;它有一个需求&#xff0c;就是不仅仅要查出购物车当中的这些商品信息&#xff0c;同时还要去查到购物车当中这些商品的最新的价格和状态信息&#xff0c;跟购物车当中的快照进行一个对比&#xff0c;从而去提醒用户。 现在我们已经做了服…

[网络爬虫] 动态网页抓取 — Selenium 介绍 环境配置

&#x1f31f;想系统化学习爬虫技术&#xff1f;看看这个&#xff1a;[数据抓取] Python 网络爬虫 - 学习手册-CSDN博客 0x01&#xff1a;Selenium 工具介绍 Selenium 是一个开源的便携式自动化测试工具。它最初是为网站自动化测试而开发的&#xff0c;类似于我们玩游戏用的按…

【RAGFlow】windows本地pycharm运行

原因 由于官方只提供了docker部署&#xff0c;基于开源代码需要实现自己内部得逻辑&#xff0c;所以需要本地pycharm能访问&#xff0c;且docker运行依赖得其余组件&#xff0c;均需要使用开发服务器得配置。 修改过程 安装python 项目依赖于Python 版本&#xff1a;>3.1…

树莓派5首次开机保姆级教程(无显示器通过VNC连接树莓派桌面)

第一次开机详细步骤 步骤一&#xff1a;树莓派系统烧录1 搜索打开烧录软件“Raspberry Pi Imager”2 选择合适的设备、系统、SD卡3 烧录配置选项 步骤二&#xff1a;SSH远程树莓派1 树莓派插电2 网络连接&#xff08;有线或无线&#xff09;3 确定树莓派IP地址 步骤三&#xff…

html-表格标签

一、表格标签 1. 表格的主要作用 表格主要用于显示&#xff64;展示数据,因为它可以让数据显示的非常的规整,可读性非常好&#xff61;特别是后台展示数据 的时候,能够熟练运用表格就显得很重要&#xff61;一个清爽简约的表格能够把繁杂的数据表现得很有条理&#xff61; 总…

大模型安全新范式:DeepSeek一体机内容安全卫士发布

2月以来&#xff0c;DeepSeek一体机几乎成为了政企市场AI消费的最强热点。 通过一体机的方式能够缩短大模型部署周期&#xff0c;深度结合业务场景&#xff0c;降低中小企业对于大模型的使用门槛。据不完全统计&#xff0c;已约有超过60家企业基于DeepSeek推出一体机产品。 但…

数据分析绘制随时间顺序变化图加入线性趋势线——numpy库的polyfit计算一次多项式拟合

import pandas as pd import numpy as np import matplotlib.pyplot as plt# 导入数据 data pd.read_csv(rC:\Users\11712\notebooktrain1.csv)# 假设数据包含 date_time 和 speed 列 data[date_time] pd.to_datetime(data[date_time]) # 确保时间列是 datetime 类型 data.s…

密闭空间可燃气体监测终端:守护城市命脉,智驭燃气安全!

近年来&#xff0c;陕西省高度重视燃气安全&#xff0c;出台了一系列政策文件&#xff0c;旨在全面加强城镇燃气安全监管&#xff0c;防范化解重大安全风险。2023年&#xff0c;陕西省安委会印发《全省城镇燃气安全专项整治工作方案》&#xff0c;明确要求聚焦燃气经营、输送配…

阿里千问大模型(Qwen2.5-VL-7B-Instruct)部署

参考链接 知乎帖子 B站视频 huggingface 镜像网站&#xff08;不太全&#xff0c;比如 Qwen/Qwen2.5-VL-7B-Instruct就没有&#xff09; huggingface 5种下载方式汇总 通过huggingface-cli下载模型 不一样的部分是预训练权重的下载和demo 首先安装huggingface_hub pip insta…

【Go学习实战】03-3-文章评论及写文章

【Go学习实战】03-3-文章评论及写文章 文章评论注册valine获取凭证加载评论页面 写文章修改cdn位置完善功能查看页面 发布文章POST发布文章发布文章测试 查询文章详情查询详情测试 修改文章修改文章测试 写文章图片上传前端后端逻辑测试 文章评论 这里我们的博客因为是个轻量级…

从零开始用AI开发游戏(一)

1. 核心玩法设计 核心目标&#xff1a;玩家需在随机生成的3D迷宫中寻找出口&#xff0c;躲避陷阱、收集道具、解开谜题。核心机制&#xff1a; 随机生成迷宫&#xff1a;每次游戏生成不同结构的迷宫&#xff08;递归分割算法或深度优先搜索&#xff09;。第一人称视角&#xf…

AI-大模型中的流式输出与非流式输出

1.前言 在大模型API开发中&#xff0c;流式与非流式输出对应着两种不同的数据交互&#xff0c;在代码中stream中通过参数true与false来进行设定。 2.流式输出与非流式输出的原理 2.1.非流式输出-请求一次响应返回完整数据 非流式输出&#xff0c;传统的请求-响应模式&#xf…

【HarmonyOS Next】鸿蒙加固方案调研和分析

【HarmonyOS Next】鸿蒙加固方案调研和分析 一、前言 根据鸿蒙应用的上架流程&#xff0c;本地构建app文件后&#xff0c;上架到AGC平台&#xff0c;平台会进行解析。根据鸿蒙系统的特殊设置&#xff0c;仿照IOS的生态闭环方案。只能从AGC应用市场下载app进行安装。这样的流程…

蓝桥杯javaB组备战第二天 题目 区间次方和 编号3382

这是一个前缀和问题&#xff0c;但是不同于以为前缀和问题 前缀和问题求解思路&#xff1a; 创建一个前缀数组 s[] ,存储输入的元素的a[1]到a[n]的和 及&#xff1a;s[1] s[i-1]a[i] ,i>1 这样比暴力算法的复杂度要低很多可以将 时间复杂度从O(q*n*m)下降到 O(n*mq) …

《Android 平台架构系统启动流程详解》

目录 一、平台架构模块 1.1 Linux 内核 1.2 硬件抽象层 (HAL) 1.3 Android 运行时 1.4 原生 C/C 库 1.5 Java API 框架 1.6 系统应用 二、系统启动流程 2.1 Bootloader阶段 2.2 内核启动 2.3 Init进程&#xff08;PID 1&#xff09; 2.4 Zygote与System Serv…

强化学习(赵世钰版)-学习笔记(3.最优策略与贝尔曼最优方程)

这是本章在课程中的位置&#xff0c;属于基础工具中的最后一章&#xff0c;主要讨论了最优状态值&#xff08;Optimal State Value&#xff09;与最优策略&#xff08;Optimal Policy&#xff09;&#xff0c;并介绍了对应的计算方法-贝尔曼最优方程&#xff08;Bellman Optima…