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: "读取卡号失败") } } } }
关键说明
- APDU指令:EMV卡片通过APDU指令交互,
SELECT用于指定支付应用,READ BINARY用于读取卡片上的专用文件。 - 多品牌支持:代码中先尝试Visa,再尝试MasterCard,你可以添加银联(A000000333)、Amex(A000000025)等应用的选择指令。
- 数据解析: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
相关产品推荐
相关产品推荐

