You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS Swift下通过NFC获取信用卡信息的技术实现问题

iOS NFC读取银行卡信息解决方案

可访问的银行卡数据类型

借记卡/信用卡属于EMV标准的ISO7816智能卡,通过Core NFC可以读取以下关键信息:

  • 卡号(PAN)
  • 持卡人姓名
  • 卡品牌(Visa/MasterCard/银联等)
  • 有效期
  • 其他EMV元数据(如服务代码)

这些数据存储在卡片的专用文件(EF)中,需要通过发送标准APDU指令获取。

Core NFC的安全性

完全可以安全提取:

  • 扫描必须由用户主动触发(比如按钮点击),后台无法启动扫描
  • 系统沙箱隔离应用,NFC会话数据不会被其他应用获取
  • 卡片会验证APDU指令的合法性,敏感数据不会被随意读取
  • 通信过程由Core NFC加密处理,无需额外实现加密逻辑

代码修改:从读取UID到提取卡片详情

你的现有代码只完成了标签连接和UID读取,要获取卡片信息,需要在ISO7816标签分支中发送EMV标准指令。以下是完整的可运行代码:

import CoreNFC

class DashboardVC: UIViewController {
    var session: NFCTagReaderSession?

    @IBAction func nfcClick(_ sender: Any) {
        addHapticFeedback()
        
        guard NFCTagReaderSession.readingAvailable else {
            addErrorHapticFeedback()
            
            let alert = UIAlertController(title: "扫描不支持", message: "此设备无法扫描NFC标签", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "确定", style: .default))
            present(alert, animated: true)
            return
        }
        
        session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self)
        session?.alertMessage = "将iPhone靠近银行卡"
        session?.begin()
    }
    
    // 实现你的触觉反馈方法
    func addHapticFeedback() {}
    func addErrorHapticFeedback() {}
}

extension DashboardVC: NFCTagReaderSessionDelegate {
    func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
        print("NFC会话激活")
    }
    
    func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        print("会话失效:\(error.localizedDescription)")
    }
    
    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        guard tags.count == 1, let tag = tags.first else {
            session.alertMessage = "检测到多个标签,请重试"
            session.invalidate()
            return
        }
        
        session.connect(to: tag) { [weak self] error in
            guard let self = self else { return }
            
            if let error = error {
                session.invalidate(errorMessage: "连接失败:\(error.localizedDescription)")
                return
            }
            
            switch tag {
            case .iso7816(let isoTag):
                self.readEMVData(session: session, tag: isoTag)
            case .miFare(let sTag):
                let uid = sTag.identifier.map { String(format: "%.2hhx", $0) }.joined()
                print("MIFARE标签UID:\(uid)")
                session.alertMessage = "已获取标签UID"
                session.invalidate()
            case .iso15693(let sTag):
                let uid = sTag.identifier.map { String(format: "%.2hhx", $0) }.joined()
                print("ISO15693标签UID:\(uid)")
                session.alertMessage = "检测到ISO15693标签"
                session.invalidate()
            default:
                session.invalidate(errorMessage: "不支持的标签类型")
            }
        }
    }
    
    // 读取EMV银行卡核心数据
    private func readEMVData(session: NFCTagReaderSession, tag: NFCISO7816Tag) {
        // 先尝试选择Visa应用,失败则尝试MasterCard
        let visaAPDU = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1: 0x04, p2: 0x00, data: Data([0xA0,0x00,0x00,0x00,0x03]), expectedResponseLength: 0x00)
        
        tag.sendCommand(apdu: visaAPDU) { [weak self] response in
            guard let self = self else { return }
            
            switch response {
            case .success(_, let sw1, let sw2) where sw1 == 0x90 && sw2 == 0x00:
                self.extractCardDetails(session: session, tag: tag)
            case .success:
                // 尝试MasterCard
                let mcAPDU = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xA4, p1: 0x04, p2: 0x00, data: Data([0xA0,0x00,0x00,0x00,0x05]), expectedResponseLength: 0x00)
                tag.sendCommand(apdu: mcAPDU) { response in
                    if case .success(_, 0x90, 0x00) = response {
                        self.extractCardDetails(session: session, tag: tag)
                    } else {
                        session.invalidate(errorMessage: "无法识别支付应用")
                    }
                }
            case .failure(let error):
                session.invalidate(errorMessage: "应用选择失败:\(error.localizedDescription)")
            }
        }
    }
    
    // 提取卡号、姓名、有效期
    private func extractCardDetails(session: NFCTagReaderSession, tag: NFCISO7816Tag) {
        // 1. 读取卡号(PAN)
        let panAPDU = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xB0, p1: 0x00, p2: 0x01, data: Data(), expectedResponseLength: 0x10)
        tag.sendCommand(apdu: panAPDU) { [weak self] response in
            guard let self = self else { return }
            
            if case .success(let data, 0x90, 0x00) = response {
                let panLength = Int(data[0])
                let pan = data.subdata(in: 1..<1+panLength).map { String(format: "%02x", $0) }.joined()
                print("卡号:\(pan)")
                
                // 2. 读取持卡人姓名
                let nameAPDU = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xB0, p1: 0x00, p2: 0x02, data: Data(), expectedResponseLength: 0x20)
                tag.sendCommand(apdu: nameAPDU) { response in
                    if case .success(let data, 0x90, 0x00) = response {
                        let name = String(data: data, encoding: .ascii)?.trimmingCharacters(in: .controlCharacters) ?? "未知"
                        print("持卡人姓名:\(name)")
                        
                        // 3. 读取有效期
                        let expiryAPDU = NFCISO7816APDU(instructionClass: 0x00, instructionCode: 0xB0, p1: 0x00, p2: 0x03, data: Data(), expectedResponseLength: 0x04)
                        tag.sendCommand(apdu: expiryAPDU) { response in
                            if case .success(let data, 0x90, 0x00) = response {
                                let expiry = data.map { String(format: "%02x", $0) }.joined()
                                print("有效期(YYMM):\(expiry)")
                                
                                session.alertMessage = "卡片信息提取完成"
                                session.invalidate()
                            } else {
                                session.invalidate(errorMessage: "读取有效期失败")
                            }
                        }
                    } else {
                        session.invalidate(errorMessage: "读取持卡人姓名失败")
                    }
                }
            } else {
                session.invalidate(errorMessage: "读取卡号失败")
            }
        }
    }
}

关键说明

  1. APDU指令:EMV卡片通过APDU指令交互,SELECT用于指定支付应用,READ BINARY用于读取卡片上的专用文件。
  2. 多品牌支持:代码中先尝试Visa,再尝试MasterCard,你可以添加银联(A000000333)、Amex(A000000025)等应用的选择指令。
  3. 数据解析:EMV数据有固定格式,比如卡号前1字节是长度,姓名是ASCII编码,有效期为YYMM格式。

必做配置

在Info.plist中添加NFC权限描述:

<key>NFCReaderUsageDescription</key>
<string>需要访问NFC功能以读取银行卡信息</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>A000000003</string> <!-- Visa -->
    <string>A000000005</string> <!-- MasterCard -->
    <string>A000000333</string> <!-- 银联 -->
</array>

内容的提问来源于stack exchange,提问作者Virani Vivek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:22:32