'MIFARE'에 해당되는 글 3건

반응형

NFC NDEF tag 및 feliCa, iso7816, iso15693, miFare tag  읽기 / 쓰기 관련 클래스를 만들어 보았다.

//
//  HiNfcManager.swift
//  NFC
//
//  Created by netcanis on 2023/04/18.
//

import UIKit
import CoreNFC

public enum HiNFCError: Error {
    case unavailable
    case notSupported
    case readOnly
    case invalidPayloadSize
    case invalidated(errorDescription: String)
}

open class HiNFCManager: NSObject {
    static let shared = HiNFCManager()
    
    public typealias DidBecomeActive = (HiNFCManager) -> Void
    public typealias DidDetect = (HiNFCManager, Result<[String: Any]?, HiNFCError>) -> Void
    
    private enum HiNFCAction {
        case read
        case write(message: NFCNDEFMessage)
    }
    
    // MARK: - Properties
    private var didBecomeActive: DidBecomeActive?
    private var didDetect: DidDetect?
    private var action: HiNFCAction?
    
    // MARK: - Properties (NDEF, Tag)
    open private(set) var ndefSession: NFCNDEFReaderSession?
    open private(set) var tagSession: Any?
    
    
    
    // MARK: - NFCNDEFTag
    open func read(didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCNDEFReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        let session = NFCNDEFReaderSession(delegate: self,
                                           queue: nil,
                                           invalidateAfterFirstRead: true)
        action = .read
        startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
    }
    
    open func write(message: [String: Any], didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCNDEFReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCNDEFReaderSession(delegate: self,
                                               queue: nil,
                                               invalidateAfterFirstRead: false)
            
            let payload = message["payload"] as? String ?? ""
            let type = message["type"] as? String ?? "T"
            let format: NFCTypeNameFormat = (type == "T") ? (.media) : (.absoluteURI)
            
            let payloadData = payload.data(using: .utf8)!
            let typeData = type.data(using: .utf8)!
            let ndefPayload = NFCNDEFPayload(format: format, type: typeData, identifier: Data(), payload: payloadData)
            let ndefMessage = NFCNDEFMessage(records: [ndefPayload])
            
            action = .write(message: ndefMessage)
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func setMessage(_ alertMessage: String) {
        ndefSession?.alertMessage = alertMessage
    }
    
    
    
    // MARK: - NFCTag
    open func readTag(didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092],
                                              delegate: self,
                                              queue: nil)!
            action = .read
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func writeTag(message: [String: Any], didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092],
                                              delegate: self,
                                              queue: nil)!
            
            let payload = message["payload"] as? String ?? ""
            let type = message["type"] as? String ?? "T"
            let format: NFCTypeNameFormat = (type == "T") ? (.media) : (.absoluteURI)
            
            let payloadData = payload.data(using: .utf8)!
            let typeData = type.data(using: .utf8)!
            let ndefPayload = NFCNDEFPayload(format: format, type: typeData, identifier: Data(), payload: payloadData)
            let ndefMessage = NFCNDEFMessage(records: [ndefPayload])

            action = .write(message: ndefMessage)
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func setTagMessage(_ alertMessage: String) {
        if #available(iOS 13.0, *) {
            if let session = self.tagSession as? NFCTagReaderSession {
                session.alertMessage = alertMessage
            }
        }
    }
}



// MARK: - NFCNDEFReaderSessionDelegate
extension HiNFCManager : NFCNDEFReaderSessionDelegate {
    
    open func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
        self.didBecomeActive?(self)
    }
    
    open func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        // iOS 13미만
        guard let message = messages.first, let record = message.records.first else {
            self.didDetect?(self, .failure(.invalidated(errorDescription: "NDEF message나 message record가 없습니다.")))
            self.invalidate(errorMessage: "NDEF message나 message record가 없습니다.")
            return
        }
        
        
        let language = String(data: record.payload.advanced(by: 1), encoding: .utf8)
        let encoding = record.payload[0] & NFCTypeNameFormat.nfcWellKnown.rawValue
        let textData = record.payload.advanced(by: 3)
        let text = String(data: textData, encoding: .utf8)
        
        let result: [String: Any] = [
            "Type":record.type.string,
            "Format": self.formattedTNF(from: record.typeNameFormat),
            "Value": [
                "Encoding": "\(encoding)",
                "Language": "\(language ?? "")",
                "Text": "\(text ?? "")"
            ],
            "Raw value": record.payload.string,
            "Payload": "\(record.payload.hexStringFormatted)",
            "Size": "\(record.payload.count)",
        ]
        print("\(String(describing: result.toJsonString()))")
        
        self.didDetect?(self, .success(result))
        self.invalidate(errorMessage: "NDEF message read successfully.")
    }
    
    @available(iOS 13.0, *)
    open func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
        guard tags.count == 1, let tag = tags.first else {
            DispatchQueue.global().asyncAfter(deadline: .now() + .microseconds(500)) {
                session.restartPolling()
            }
            return
        }
        
        session.connect(to: tag) { [weak self] error in
            guard let self = self else { return }
            if error != nil {
                self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                self.invalidate(errorMessage: error?.localizedDescription)
                return
            }
            
            tag.queryNDEFStatus { status, capacity, error in
                switch (status, self.action) {
                case (.notSupported, _):
                    self.didDetect?(self, .failure(.notSupported))
                    self.invalidate(errorMessage: error?.localizedDescription)

                case (.readOnly, _):
                    self.didDetect?(self, .failure(.readOnly))

                case (.readWrite, .read):
                    tag.readNDEF { message, error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error?.localizedDescription)
                            return
                        }
                        
                        let record = message?.records.first
                        let result: [String: Any] = [
                            "Type":record?.type.string ?? "",
                            "Format": self.formattedTNF(from: record!.typeNameFormat),
                            "Raw value": record?.payload.string ?? "",
                            "Payload": "\(record?.payload.hexStringFormatted ?? "")",
                            "Size": "\(record?.payload.count ?? 0)",
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }

                case (.readWrite, .write(let message)):
                    guard message.length <= capacity else {
                        self.didDetect?(self, .failure(.invalidPayloadSize))
                        self.invalidate(errorMessage: "Invalid payload size")
                        return
                    }

                    tag.writeNDEF(message) { error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error!.localizedDescription)
                            return
                        }
                        let result: [String: Any] = [
                            "result":message.records.first?.payload.string ?? ""
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                default:
                    return
                }
            }
        }
    }
    
    open func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        if let error = error as? NFCReaderError,
           error.code != .readerSessionInvalidationErrorFirstNDEFTagRead &&
            error.code != .readerSessionInvalidationErrorUserCanceled {
            self.didDetect?(self, .failure(.invalidated(errorDescription: error.localizedDescription)))
        }
        self.ndefSession = nil
        self.tagSession = nil
        self.didBecomeActive = nil
        self.didDetect = nil
    }
}


// MARK: - Private helper functions
extension HiNFCManager {
    private func invalidate(errorMessage: String?) {
        if errorMessage != nil {
            if #available(iOS 13.0, *) {
                if let ns = ndefSession {
                    ns.invalidate(errorMessage: errorMessage!)
                }
                if let ts = tagSession as? NFCTagReaderSession {
                    ts.invalidate(errorMessage: errorMessage!)
                }
            } else {
                if let ns = ndefSession {
                    ns.invalidate()
                }
                if #available(iOS 13.0, *) {
                    if let ts = tagSession as? NFCTagReaderSession {
                        ts.invalidate()
                    }
                }
            }
        } else {
            if let ns = ndefSession {
                ns.invalidate()
            }
            if #available(iOS 13.0, *) {
                if let ts = tagSession as? NFCTagReaderSession {
                    ts.invalidate()
                }
            }
        }
        ndefSession = nil
        tagSession = nil
        didBecomeActive = nil
        didDetect = nil
    }
    
    private func startSession(session: NFCNDEFReaderSession,
                              didBecomeActive: DidBecomeActive?,
                              didDetect: @escaping DidDetect) {
        self.ndefSession = session
        self.didBecomeActive = didBecomeActive
        self.didDetect = didDetect
        session.begin()
    }
    
    @available(iOS 13.0, *)
    private func startSession(session: NFCTagReaderSession,
                              didBecomeActive: DidBecomeActive?,
                              didDetect: @escaping DidDetect) {
        self.tagSession = session
        self.didBecomeActive = didBecomeActive
        self.didDetect = didDetect
        session.begin()
    }
    
    private func formattedTNF(from tnf: NFCTypeNameFormat) -> String {
        switch tnf {
        case .empty:        // 0: 이름 없는 레코드 (빈 문자열로 표시됨)
            return "Empty (0x00)"
        case .nfcWellKnown: // 1: NFC Forum에서 정의한 레코드 형식
            return "NFC Well Known (0x01)"
        case .media:        // 2: 미디어 타입의 레코드 (ex. 'audio/mp3')
            return "Media (0x02)"
        case .absoluteURI:  // 3: URI 형식의 레코드
            return "Absolute URI (0x03)"
        case .nfcExternal:  // 4: NFC 포럼에서 정의한 외부 레코드
            return "NFC External (0x04)"
        case .unchanged:    // 6: 이름 형식 변경 없음. (현재의 이름 형식을 유지)
            return "Unchanged (0x06)"
        default:            // 5: 알 수 없는 레코드
            return "Unknown (0x05)"
        }
    }
}





// MARK: - NFCTagReaderSessionDelegate
@available(iOS 13.0, *)
extension HiNFCManager : NFCTagReaderSessionDelegate {
    
    public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
        self.didBecomeActive?(self)
    }
    
    public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        guard tags.count == 1, let tag = tags.first else {
            DispatchQueue.global().asyncAfter(deadline: .now() + .microseconds(500)) {
                session.restartPolling()
            }
            return
        }
        
        var ndefTag: NFCNDEFTag
        switch tag {
        case let .feliCa(tag):  /// FeliCa tag. (NFCFeliCaTag)
            ndefTag = tag
            self.parseFeliCaTag(tag)
        case let .iso7816(tag): /// ISO14443-4 type A / B tag with ISO7816 communication. (NFCISO7816Tag)
            ndefTag = tag
            self.parseISO7816Tag(tag)
        case let .iso15693(tag):/// ISO15693 tag.
            ndefTag = tag
            self.parseISO15693Tag(tag)
        case let .miFare(tag):  /// MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443. (NFCMiFareTag)
            ndefTag = tag
            self.parseMIFARETag(tag)
        @unknown default:
            self.didDetect?(self, .failure(.invalidated(errorDescription: "Tag not valid.")))
            self.invalidate(errorMessage: "Tag not valid.")
            return
        }
        
        
        session.connect(to: tag) { [weak self] error in
            guard let self = self else { return }
            if error != nil {
                self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                self.invalidate(errorMessage: error?.localizedDescription)
                return
            }
            
            ndefTag.queryNDEFStatus { status, capacity, error in
                switch (status, self.action) {
                case (.notSupported, _):
                    self.didDetect?(self, .failure(.notSupported))
                    self.invalidate(errorMessage: error?.localizedDescription)

                case (.readOnly, _):
                    self.didDetect?(self, .failure(.readOnly))

                case (.readWrite, .read):
                    ndefTag.readNDEF { (message, error) in
                        if error != nil || message == nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error?.localizedDescription)
                            return
                        }

                        let record = message?.records.first
                        let result: [String: Any] = [
                            "Type":record?.type.string ?? "",
                            "Format": self.formattedTNF(from: record!.typeNameFormat),
                            "Raw value": record?.payload.string ?? "",
                            "Payload": "\(record?.payload.hexStringFormatted ?? "")",
                            "Size": "\(record?.payload.count ?? 0)",
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                case (.readWrite, .write(let message)):
                    guard message.length <= capacity else {
                        self.didDetect?(self, .failure(.invalidPayloadSize))
                        self.invalidate(errorMessage: "Invalid payload size")
                        return
                    }

                    ndefTag.writeNDEF(message) { error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error!.localizedDescription)
                            return
                        }
                        let result: [String: Any] = [
                            "result":message.records.first?.payload.string ?? ""
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                default:
                    return
                }
            }
        }
    }
    
    public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        // NFC 태그 읽기가 중단되었을 때, 호출된다.
        print("NFCTag 읽기 중단: \(error.localizedDescription)")
    }
    
}


// MARK: - Private helper functions (for NFCTag)
@available(iOS 13.0, *)
extension HiNFCManager {
    /// FeliCa tag. (NFCFeliCaTag)
    private func parseFeliCaTag(_ tag: NFCFeliCaTag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ FeliCa tag ] ::::::::::
                    "- Type : FeliCa tag."
                    "- currentIDm : \(String(describing: tag.currentIDm))"
                    "- currentSystemCode : \(String(describing: tag.currentSystemCode))"
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }
    
    /// ISO14443-4 type A / B tag with ISO7816 communication. (NFCISO7816Tag)
    private func parseISO7816Tag(_ tag: NFCISO7816Tag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ ISO7816 tag ] ::::::::::
                    "- Type : ISO14443-4 type A / B tag with ISO7816 communication."
                    "- Identifier : \(String(describing: tag.identifier))"
                    "- historicalBytes : \(String(describing: tag.historicalBytes?.hexStringFormatted))"
                    "- applicationData : \(String(describing: tag.applicationData?.hexStringFormatted))"
                    "- initialSelectedAID : \(tag.initialSelectedAID)"
                    "- proprietaryApplicationDataCoding : \(tag.proprietaryApplicationDataCoding)"
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }

    /// ISO15693 tag.
    private func parseISO15693Tag(_ tag: NFCISO15693Tag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ ISO15693 tag ] ::::::::::
                    "- Type : ISO15693 tag."
                    "- Identifier : \(tag.identifier.hexString)"
                    "- icSerialNumber : \(String(describing: tag.icSerialNumber.hexStringFormatted))" // IC 시리얼 번호(IC serial number)
                    "- icManufacturerCode : \(String(describing: tag.icManufacturerCode))" // C 제조사 코드(IC manufacturer code)
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }
    
    /// MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443. (NFCMiFareTag)
    private func parseMIFARETag(_ tag: NFCMiFareTag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ MiFare tag ] ::::::::::
                    "- Type : MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443."
                    "- Identifier : \(tag.identifier.hexString)"
                    "- Historical bytes : \(tag.historicalBytes?.hexString ?? "None")"
                    "- mifareFamily : \(self.formattedMiFareFamily(tag))"
                    "- description : \(String(describing: tag.description))"
                    "- Block count : \(tag.mifareFamily == .plus ? 256 : 16)"
                    ------------------------------------
                    """
        print(log)
    }
    
    private func formattedMiFareFamily (_ tag: NFCMiFareTag) -> String {
        switch (tag.mifareFamily) {
        case .unknown:
            return "MiFare compatible ISO14443 Type A tag." // ISO14443 Type A 호환제품
        case .ultralight:
            return "MiFare Ultralight series."
        case .plus:
            return "MiFare Plus series."
        case .desfire:
            return "MiFare DESFire series."
        default:
            return "Unknown"
        }
    }
}



extension Data {
    // Data([0x12, 0x34, 0x56]) -> 123456
    var hexString: String {
        return map { String(format: "%02hhx", $0) }.joined() // big-endian byte order : reversed().map
    }
    
    // Data([0x12, 0x34, 0x56]) -> 0x12 0x34 0x56
    var hexStringFormatted: String {
        let hexArray = map { String(format: "0x%02hhx", $0) }
        return hexArray.joined(separator: " ")
    }
    
    // Data([0x12, 0x34, 0x56]) -> 313233343536
    public func hexEncodedString() -> String {
        let hexDigits = Array("0123456789abcdef".utf16)
        var hexChars = [UTF16.CodeUnit]()
        hexChars.reserveCapacity(count * 2)
        for byte in self {
            let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
            hexChars.append(hexDigits[index1])
            hexChars.append(hexDigits[index2])
        }
        return String(utf16CodeUnits: hexChars, count: hexChars.count)
    }
    
    var string: String {
        return String(data: self, encoding: .utf8)!
    }
    
    var jsonString: String {
        do {
            let json = try JSONSerialization.jsonObject(with: self, options: [])
            let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
            return String(data: data, encoding: .utf8) ?? ""
        } catch let error {
            print("JSON serialization error: \(error.localizedDescription)")
            return ""
        }
    }
    
    func isJsonString() -> Bool {
        do {
            let _ = try JSONSerialization.jsonObject(with: self, options: [])
            return true
        } catch {
            return false
        }
    }
}

extension Dictionary {
    func toJsonString() -> String? {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted, .sortedKeys])
            return String(data: jsonData, encoding: .utf8)
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }
}

extension Array where Element == Data {
    func toStrings() -> [String] {
        return self.map { String(data: $0, encoding: .utf8) ?? "" }
    }
    
    func toHexStrings() -> [String] {
        return self.map { $0.reduce("") { $0 + String(format: "%02x", $1) } }
    }
}

extension NSObject {
    func rootWindow() -> UIWindow? {
        var window: UIWindow?
        if #available(iOS 15.0, *) {
            window = UIApplication.shared.connectedScenes
                .compactMap { $0 as? UIWindowScene }
                .flatMap { $0.windows }
                .first(where: { $0.isKeyWindow })
        } else {
            window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
        }
        return window
    }
    
    func showAlert(title: String?, message: String?, actions: [UIAlertAction] = [UIAlertAction(title: "OK", style: .default, handler: nil)], preferredStyle: UIAlertController.Style = .alert) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
        for action in actions {
            alertController.addAction(action)
        }
        guard let rootViewController = self.rootWindow()?.rootViewController else { return }
        rootViewController.present(alertController, animated: true)
    }
}

 

사용법 :

// NFC NDEF 테그 쓰기
let str = "테스트 메시지 입니다."
let type = str.hasPrefix("http") == true ? "U" : "T"

let message: [String: Any] = [
    "payload": str,
    "type": type
]
HiNFCManager.shared.write(message: message) { manager in
    manager.setMessage("Place iPhone near the tag to be written on")
} didDetect: { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to write tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag successfully written")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}


// NFC NDEF 테그 읽기
HiNFCManager.shared.read { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to read tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag read successfully")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}




// NFC Tag 쓰기
let str = "https://www.apple.com"
let type = str.hasPrefix("http") == true ? "U" : "T"

let message: [String: Any] = [
    "payload": str,
    "type": type
]
HiNFCManager.shared.writeTag(message: message) { manager in
    manager.setMessage("Place iPhone near the tag to be written on")
} didDetect: { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to write tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag successfully written")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}


// NFC Tag 읽기
HiNFCManager.shared.readTag { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to read tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag read successfully")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}

 

결과값 로그 :

// NFC NDEF tag 읽기결과 로그
{
  "Format" : "Media (0x02)",
  "Payload" : "0x74 0x65 0x73 0x74 0x20 0x6d 0x65 0x73 0x73 0x61 0x67 0x65",
  "Raw value" : "test message",
  "Size" : "12",
  "Type" : "T"
}

// NFC NDEF tag 쓰기결과 로그
{
  "result" : "test message"
}


// NFC Tag 읽기결과 로그
{
  "Format" : "Absolute URI (0x03)",
  "Payload" : "0x68 0x74 0x74 0x70 0x73 ... 0x65 0x2e 0x63 0x6f 0x6d",
  "Raw value" : "https://www.apple.com",
  "Size" : "21",
  "Type" : "U"
}

// NFC Tag 쓰기결과 로그
{
  "result" : "https:\/\/www.apple.com"
}

 

 

2023.04.26 - [iOS] - NFC tag read/write Manager Class (1/2)

2023.04.26 - [iOS] - NFC tag read/write Manager Class (2/2)

 

반응형

'개발 > iOS' 카테고리의 다른 글

XOR 연산을 이용한 문자열 비교  (0) 2023.12.07
Rosetta2  (0) 2023.04.26
NFC tag read/write Manager Class (1/2)  (0) 2023.04.26
Carthage 설치 및 제거  (0) 2023.01.11
NSURLSessionTask 캐싱 비활성화  (0) 2022.10.24
블로그 이미지

SKY STORY

,
반응형

NFC NDEF tag 읽기 / 쓰기 및 feliCa, iso7816, iso15693, miFare tag 읽기 관련 클래스를

만들기 위해 우선 다음과 같이 환경 설정부터 진행해 보자.

 

우선 개발자 사이트에서 App ID 설정에서 NFC 읽기 활성화 한다.

Xcode에서는 다음과 같이 적용된다.

entitlements 파일에 NDEF, TAG 등록.

Info.plist 권한 설정은 다음과 같다.

NFC 읽기 권한  설정을 한다.

<key>NFCReaderUsageDescription</key>
<string>결제를 위해 NFC 사용이 승인되어야 합니다.</string>

 

소니의 Felica tag를 읽기 위해 시스템 코드를 등록한다.

<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
	<string>88B4</string>
	<string>88A8</string>
	<string>8031</string>
	<string>88CA</string>
	<string>8FC7</string>
	<string>8E5E</string>
	<string>8E6F</string>
	<string>8F5B</string>
	<string>869F</string>
	<string>12FC</string>
	<string>0003</string>
	<string>FE00</string>
</array>
</plist>

 

iso7816 tag의 경우 다음과 같이 읽을 id를 등록한다.

<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
	<string>D2760000850101h</string>
	<string>315041592E5359532E4444463031</string>
	<string>D4100000030001</string>
	<string>325041592E5359532E4444463031</string>
	<string>44464D46412E44466172653234313031</string>
	<string>A00000000101</string>
	<string>A000000003000000</string>
	<string>A00000000300037561</string>
	<string>A00000000305076010</string>
	<string>A0000000031010</string>
	<string>A000000003101001</string>
	<string>A000000003101002</string>
	<string>A0000000032010</string>
	<string>A0000000032020</string>
	<string>A0000000033010</string>
	<string>A0000000034010</string>
	<string>A0000000035010</string>
	<string>A000000003534441</string>
	<string>A0000000035350</string>
	<string>A000000003535041</string>
	<string>A0000000036010</string>
	<string>A0000000036020</string>
	<string>A0000000038002</string>
	<string>A0000000038010</string>
	<string>A0000000039010</string>
	<string>A000000003999910</string>
	<string>A0000000040000</string>
	<string>A00000000401</string>
	<string>A0000000041010</string>
	<string>A00000000410101213</string>
	<string>A00000000410101215</string>
	<string>A0000000041010BB5449435301</string>
	<string>A0000000042010</string>
	<string>A0000000042203</string>
	<string>A0000000043010</string>
	<string>A0000000043060</string>
	<string>A000000004306001</string>
	<string>A0000000044010</string>
	<string>A0000000045010</string>
	<string>A0000000045555</string>
	<string>A0000000046000</string>
	<string>A0000000048002</string>
	<string>A0000000049999</string>
	<string>A0000000050001</string>
	<string>A0000000050002</string>
	<string>A0000000090001FF44FF1289</string>
	<string>A0000000101030</string>
	<string>A00000001800</string>
	<string>A0000000181001</string>
	<string>A000000018434D</string>
	<string>A000000018434D00</string>
	<string>A00000002401</string>
	<string>A000000025</string>
	<string>A0000000250000</string>
	<string>A00000002501</string>
	<string>A000000025010104</string>
	<string>A000000025010402</string>
	<string>A000000025010701</string>
	<string>A000000025010801</string>
	<string>A0000000291010</string>
	<string>A00000002945087510100000</string>
	<string>A00000002949034010100001</string>
	<string>A00000002949282010100000</string>
	<string>A000000029564182</string>
	<string>A00000003029057000AD13100101FF</string>
	<string>A0000000308000000000280101</string>
	<string>A0000000421010</string>
	<string>A0000000422010</string>
	<string>A0000000423010</string>
	<string>A0000000424010</string>
	<string>A0000000425010</string>
	<string>A0000000426010</string>
	<string>A00000005945430100</string>
	<string>A000000063504B43532D3135</string>
	<string>A0000000635741502D57494D</string>
	<string>A00000006510</string>
	<string>A0000000651010</string>
	<string>A00000006900</string>
	<string>A000000077010000021000000000003B</string>
	<string>A0000000790100</string>
	<string>A0000000790101</string>
	<string>A0000000790102</string>
	<string>A00000007901F0</string>
	<string>A00000007901F1</string>
	<string>A00000007901F2</string>
	<string>A0000000790200</string>
	<string>A0000000790201</string>
	<string>A00000007902FB</string>
	<string>A00000007902FD</string>
	<string>A00000007902FE</string>
	<string>A0000000790300</string>
	<string>A0000000791201</string>
	<string>A0000000791202</string>
	<string>A0000000871002FF49FF0589</string>
	<string>A00000008810200105C100</string>
	<string>A000000088102201034221</string>
	<string>A000000088102201034321</string>
	<string>A0000000960200</string>
	<string>A000000098</string>
	<string>A0000000980840</string>
	<string>A0000000980848</string>
	<string>A0000001110101</string>
	<string>A0000001110201</string>
	<string>A0000001160300</string>
	<string>A0000001166010</string>
	<string>A0000001166030</string>
	<string>A0000001169000</string>
	<string>A000000116A001</string>
	<string>A000000116DB00</string>
	<string>A000000118010000</string>
	<string>A000000118020000</string>
	<string>A000000118030000</string>
	<string>A000000118040000</string>
	<string>A0000001184543</string>
	<string>A000000118454E</string>
	<string>A0000001211010</string>
	<string>A0000001320001</string>
	<string>A0000001408001</string>
	<string>A0000001410001</string>
	<string>A0000001510000</string>
	<string>A00000015153504341534400</string>
	<string>A0000001523010</string>
	<string>A0000001524010</string>
	<string>A0000001544442</string>
	<string>A0000001570010</string>
	<string>A0000001570020</string>
	<string>A0000001570021</string>
	<string>A0000001570022</string>
	<string>A0000001570023</string>
	<string>A0000001570030</string>
	<string>A0000001570031</string>
	<string>A0000001570040</string>
	<string>A0000001570050</string>
	<string>A0000001570051</string>
	<string>A0000001570100</string>
	<string>A0000001570104</string>
	<string>A0000001570109</string>
	<string>A000000157010A</string>
	<string>A000000157010B</string>
	<string>A000000157010C</string>
	<string>A000000157010D</string>
	<string>A0000001574443</string>
	<string>A0000001574444</string>
	<string>A000000167413000FF</string>
	<string>A000000167413001</string>
	<string>A000000172950001</string>
	<string>A000000177504B43532D3135</string>
	<string>A0000001850002</string>
	<string>A0000001884443</string>
	<string>A0000002040000</string>
	<string>A0000002281010</string>
	<string>A0000002282010</string>
	<string>A00000022820101010</string>
	<string>A0000002471001</string>
	<string>A0000002472001</string>
	<string>A0000002771010</string>
	<string>A00000030600000000000000</string>
	<string>A000000308000010000100</string>
	<string>A00000031510100528</string>
	<string>A0000003156020</string>
	<string>A00000032301</string>
	<string>A0000003230101</string>
	<string>A0000003241010</string>
	<string>A000000333010101</string>
	<string>A000000333010102</string>
	<string>A000000333010103</string>
	<string>A000000333010106</string>
	<string>A000000333010108</string>
	<string>A000000337301000</string>
	<string>A000000337101000</string>
	<string>A000000337102000</string>
	<string>A000000337101001</string>
	<string>A000000337102001</string>
	<string>A000000337601001</string>
	<string>A0000003591010</string>
	<string>A0000003591010028001</string>
	<string>A00000035910100380</string>
	<string>A0000003660001</string>
	<string>A0000003660002</string>
	<string>A0000003710001</string>
	<string>A00000038410</string>
	<string>A00000038420</string>
	<string>A0000003964D66344D0002</string>
	<string>A00000039742544659</string>
	<string>A0000003974349445F0100</string>
	<string>A0000004271010</string>
	<string>A0000004320001</string>
	<string>A0000004360100</string>
	<string>A0000004391010</string>
	<string>A0000004540010</string>
	<string>A0000004540011</string>
	<string>A0000004762010</string>
	<string>A0000004763030</string>
	<string>A0000004766C</string>
	<string>A000000476A010</string>
	<string>A000000476A110</string>
	<string>A000000485</string>
	<string>A0000005241010</string>
	<string>A0000005271002</string>
	<string>A000000527200101</string>
	<string>A000000527210101</string>
	<string>A0000005591010FFFFFFFF8900000100</string>
	<string>A0000005591010FFFFFFFF8900000200</string>
	<string>A0000005591010FFFFFFFF8900000D00</string>
	<string>A0000005591010FFFFFFFF8900000E00</string>
	<string>A0000005591010FFFFFFFF8900000F00</string>
	<string>A0000005591010FFFFFFFF8900001000</string>
	<string>A00000061700</string>
	<string>A0000006200620</string>
	<string>A0000006260101010001</string>
	<string>A0000006351010</string>
	<string>A0000006581010</string>
	<string>A0000006581011</string>
	<string>A0000006582010</string>
	<string>A0000006723010</string>
	<string>A0000006723020</string>
	<string>A0000007705850</string>
	<string>A0000007790000</string>
	<string>B012345678</string>
	<string>D040000001000002</string>
	<string>D040000002000002</string>
	<string>D040000003000002</string>
	<string>D040000004000002</string>
	<string>D04000000B000002</string>
	<string>D04000000C000002</string>
	<string>D04000000D000002</string>
	<string>D040000013000001</string>
	<string>D040000013000001</string>
	<string>D040000013000002</string>
	<string>D040000013000002</string>
	<string>D040000014000001</string>
	<string>D040000015000001</string>
	<string>D040000015000001</string>
	<string>D0400000190001</string>
	<string>D0400000190002</string>
	<string>D0400000190003</string>
	<string>D0400000190004</string>
	<string>D0400000190010</string>
	<string>D268000001</string>
	<string>D276000005</string>
	<string>D276000005AA040360010410</string>
	<string>D276000005AA0503E00401</string>
	<string>D276000005AA0503E00501</string>
	<string>D276000005AA0503E0050101</string>
	<string>D276000005AB0503E0040101</string>
	<string>D27600002200000001</string>
	<string>D27600002200000002</string>
	<string>D27600002200000060</string>
	<string>D276000025</string>
	<string>D27600002545410100</string>
	<string>D27600002545500100</string>
	<string>D27600002547410100</string>
	<string>D276000060</string>
	<string>D2760000850100</string>
	<string>D2760000850101</string>
	<string>D276000118</string>
	<string>D2760001180101</string>
	<string>D27600012401</string>
	<string>D276000124010101FFFF000000010000</string>
	<string>D2760001240102000000000000010000</string>
	<string>D27600012402</string>
	<string>D2760001240200010000000000000000</string>
	<string>D4100000011010</string>
	<string>D5280050218002</string>
	<string>D5780000021010</string>
	<string>D7560000010101</string>
	<string>D7560000300101</string>
	<string>D8040000013010</string>
	<string>E80704007F00070302</string>
	<string>E82881C11702</string>
	<string>E828BD080F</string>
	<string>F0000000030001</string>
</array>
</plist>

 

 

2023.04.26 - [개발노트] - NFC tag read/write Manager Class (1/2)

2023.04.26 - [분류 전체보기] - NFC tag read/write Manager Class (2/2)

 

반응형

'개발 > iOS' 카테고리의 다른 글

Rosetta2  (0) 2023.04.26
NFC tag read/write Manager Class (2/2)  (0) 2023.04.26
Carthage 설치 및 제거  (0) 2023.01.11
NSURLSessionTask 캐싱 비활성화  (0) 2022.10.24
UIScrollView 스크린샷 만들기  (0) 2022.10.24
블로그 이미지

SKY STORY

,

Mifare

개발/Note 2020. 5. 29. 10:01
반응형

1. 개요

네덜란드 NXP반도체[1]의 RF 기술. 이용하는 주파수는 13.56MHz로, 국제표준 규격인 ISO/IEC 14443 Type A와 일부 호환된다. 원래는 Mikron에서 개발한 것이라 이름도 Mikron Fare Collecting System의 두문자어로 MIFARE가 되었는데, 이 기술을 필립스가 현질해서 관련 제품을 생산하기 시작했다.

 

2. 종류

MIFARE 카드는 스마트카드의 일종이지만, 가장 처음에 등장한 MIFARE Classic 1K/4K 카드는 안테나가 달린 외장 메모리였다. 암호화같은 건 정말 팬티 한 장 걸친 수준으로만 해 놓고, 단말기에 접촉해서 정보를 읽고 쓰는 것만 가능하게 만든 것이다. 따라서 보안성이 떨어진다. 이름에서 알 수 있듯이 1K는 1,024바이트, 4K는 4,096바이트를 저장할 수 있다. 유패스가 MIFARE Classic 1K를 이용해서 세계 최초로 상용화된 교통카드다. 대경교통카드도 MIFARE Classic이다. 아예 칩셋까지 NXP 제품을 이용 중. 유패스는 일부 카드만 NXP 제품이다.

일회용으로 쓰기엔 Classic도 오버스펙이라고 해서 MIFARE Ultralight와 MIFARE Ultralight C도 나와 있다. Ultralight는 저장 용량도 512바이트에 암호화 없이 생으로 정보를 기록한다(...) Ultralight C는 보안을 눈꼽만큼 신경써서, 192바이트의 저장 용량에 Triple DES를 올려서 나왔다. 둘 다 일회용 RF 티켓에 사용할 목적으로 개발되었다. 하지만 간혹 울트라라이트를 올려서 나오는 NFC 공태그도 보인다. 대표적으로 LG전자 옵티머스 LTE TAG와 옵티머스 G옵티머스 뷰2 박스에 번들로 들어갔던 LG Tag+(...)가 있다.

가장 대중적으로 쓰이는 것 중에 MIFARE DESFire가 있다. NFC 리더기로 읽어보면 간혹 카드 종류를 MIFARE SmartMX라고도 읽는다. ISO/IEC 14443 표준에 맞는 모델로, 카드 내부에 8051 마이크로프로세서를 탑재하고 Triple DES와 AES를 올려서 나왔다. 티머니와 OysterOV-Chipkaart 등에 절찬리 이용 중. 문제는 이 모델이 2011년에 해킹되었다는 것(...) 후속작으로 보안이 강화되고 DESFire와 하위호환이 되는 MIFARE DESFire EV1이 나왔다.

MIFARE Classic의 보안 수준이 허접한 관계로, 강화된 보안 모듈을 끼워넣은 MIFARE Plus가 나왔다. 메모리는 2K/4K 두 종류로 나왔고 128비트 암호화를 지원하는 듯.

한국은 ISO/IEC 14443기반의 대한민국 표준규격인 KS X 6924 표준으로 정하면서 MIFARE에서 벗어나게 되었다.

 

출처 : https://namu.wiki/w/MIFARE

 

2020/12/16 - [개발노트] - Code 128 Barcode의 Check Digit 계산방법

2020/12/15 - [iOS/Tips] - 디버깅 차단 처리 (Anti Debug)

2020/12/14 - [iOS/Tips] - bundle id 알아내기

2020/12/12 - [AI/Algorithm] - 2D 충돌처리

2020/12/11 - [iOS/Swift] - UIViewController 스위칭

2020/12/11 - [개발노트] - PlantUML 설치 (Mac OS X)

2020/12/11 - [개발노트] - 특수문자 발음

2020/12/10 - [iOS/Objective-C] - 웹뷰에서 javascript 함수 동기식 호출

2020/12/10 - [iOS/Tips] - Fat Static Library 빌드 (2/2)

2020/12/10 - [iOS/Tips] - Fat Static Library 빌드 (1/2)

2020/12/10 - [iOS/Tips] - Custom UserAgent 설정

2020/12/10 - [iOS/Tips] - CocoaPods 설치 및 제거

2020/12/10 - [iOS/Tips] - Clang diagnostic 경고 무시하기

2020/12/10 - [개발노트] - Bluetooth UUID

2020/12/08 - [개발노트] - 모바일 앱 메모리덤프 이슈 해결방법

 

반응형

'개발 > Note' 카테고리의 다른 글

NFC Tag 저장용량  (0) 2020.05.29
NDEF  (0) 2020.05.29
QR 코드 결제 타입  (0) 2020.05.29
Base64 encode / decode in C++  (0) 2020.05.29
iPhone SDK location on hard drive  (0) 2020.05.29
블로그 이미지

SKY STORY

,