Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iPhoneでFeliCaを読み取ってみた

 iPhoneでFeliCaを読み取ってみた

iPhoneでFeliCaを読み取ってみた
#potatotips #64

作ったライブラリ
https://github.com/tattn/NFCReader

Tatsuya Tanaka

August 27, 2019
Tweet

More Decks by Tatsuya Tanaka

Other Decks in Programming

Transcript

  1. iPhoneͰFeliCaΛಡΈऔͬͯΈͨ
    ాதୡ໵ (@tattn)
    #potatotips #64

    View Slide

  2. ాத ୡ໵ / ͨͳͨͭ (@tattn)
    • Yahoo!৐׵Ҋ಺
    • iOSΞϓϦΤϯδχΞ
    @tattn
    @tanakasan2525
    @tattn
    Copyright (C) 2019 Yahoo Japan Corporation. All Rights Reserved.

    View Slide

  3. iOS13ͰFeliCaͷ
    ಡΈॻ͖͕Մೳʹʂ
    (CoreNFC)

    View Slide

  4. SuicaͷσʔλΛྫʹ

    ಡΈࠐΈํΛ঺հ͠·͢
    (৐߱ཤྺ)

    View Slide

  5. FeliCaͷಡΈࠐΈखଓ͖ͷϑϩʔ
    IUUQTXXXTPOZDPKQ1SPEVDUTGFMJDBCVTJOFTTUFDITVQQPSUJOEFYIUNM
    begin()
    connect()
    requestService()
    readWithoutEncryption()
    λάͷϙʔϦϯά։࢝
    λάʹίϚϯυΛૹΕΔΑ͏ʹ઀ଓ
    αʔϏεͷଘࡏ֬ೝ (σʔλͷ༗ແ)
    αʔϏε಺ͷσʔλಡΈࠐΈ
    NFCTagReaderSession
    NFCFeliCaTag
    IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPODPSFOGD

    View Slide

  6. FeliCaͷಡΈࠐΈʹඞཁͳઃఆ
    • Info.plistʹPrivacyઃఆ/System CodeΛ௥Ճ
    • Capabilitiesʹ

    Near Field Communication Tag ReadingΛ௥Ճ
    Suicaͷ৔߹͸0003

    View Slide

  7. Sessionͷ࡞੒ & ϙʔϦϯάͷ։࢝
    let session = NFCTagReaderSession(
    pollingOption: NFCTagReaderSession.PollingOption.iso18092,
    delegate: self
    )
    session.alertMessage = "iPhoneΛSuicaʹ͚͍ۙͮͯͩ͘͞"
    session.begin()
    FeliCaΛಡΈࠐΉ࣌͸ISO18092Λࢦఆ
    /**
    * @enum NFCPollingOption
    *
    * @constant NFCPollingISO14443 Support both Type A & B modulation. NFCTagTypeISO7816Compatible and NFCTagTypeMiFare tags will be discovered.
    * @constant NFCPollingISO15693 NFCTagTypeISO15693 tag will be discovered.
    * @constant NFCPollingISO18092 NFCTagTypeFeliCa tag will be discovered.
    */
    public struct PollingOption : OptionSet {
    public init(rawValue: Int)
    public static var iso14443: NFCTagReaderSession.PollingOption { get }
    public static var iso15693: NFCTagReaderSession.PollingOption { get }
    public static var iso18092: NFCTagReaderSession.PollingOption { get }
    }

    View Slide

  8. Tag ͷಡΈࠐΈ४උ
    public func tagReaderSession(_ session: NFCTagReaderSession,
    didDetect tags: [NFCTag]) {
    guard case .feliCa(let tag) = tags.first else {
    return
    }
    session.connect(to: tag) { error in
    guard error == nil else { return }
    // TagͷಡΈࠐΈ (࣍ϖʔδ)
    }
    }
    delegateͰݕग़ͨ͠λάΛड͚औͬͯ

    ઀ଓ͢Δ
    public enum NFCTag {
    case feliCa(NFCFeliCaTag)
    case iso7816(NFCISO7816Tag)
    case iso15693(NFCISO15693Tag)
    case miFare(NFCMiFareTag)
    }

    View Slide

  9. ϒϩοΫσʔλͷಡΈࠐΈ
    let serviceCodeList = [Data([0x0f, 0x09])]
    let blockList = (0..feliCaTag.requestService(nodeCodeList: serviceCodeList) { nodes, error in
    guard error == nil, nodes.first == Data([0xff, 0xff]) else {
    return
    }
    feliCaTag.readWithoutEncryption(
    serviceCodeList: serviceCodeList,
    blockList: blockList) { status1, status2, dataList, error in
    guard error == nil, status1 == 0, status2 == 0 else {
    return
    }
    // dataͷಡΈࠐΈ (࣍ϖʔδ)
    }
    } Service code: αʔϏε(σʔλ)Λಛఆ͢Δίʔυ
    Block list: σʔλͷऔಘํ๏/ҐஔΛܾΊΔ

    View Slide

  10. Suicaͷσʔλͷσίʔυ
    for data in dataList {
    let year = data[4] >> 1
    let month = UInt16(bytes: data[4...5]) >> 5 & 0b1111
    let day = data[5] & 0b11111
    print("ར༻೔: \(year)/\(month)/\(day)") // 19/8/27
    let entrance = UInt16(bytes: data[6...7])
    let exit = UInt16(bytes: data[8...9])
    print("ೖ৔Ӻ: \(entrance), ग़৔Ӻ: \(exit)")
    let balance = UInt16(bytes: data[10...11].reversed())
    print("࢒ߴ: ", balance)
    }
    IUUQTXXXXEJDPSHX3"*-αΠόωن֨ *$Χʔυ

    year͕2000೥Λج४ʹ͍ͯ͠Δͷ͕໘ന͍

    View Slide

  11. όΠτ഑ྻΛIntܕʹม׵͢Δ֦ு
    extension FixedWidthInteger {
    init(bytes: UInt8...) {
    self.init(bytes: bytes)
    }
    init(bytes: T) {
    let count = bytes.count - 1
    self = bytes.enumerated().reduce(into: 0) { (result, item) in
    result += Self(item.element) << (8 * (count - item.offset))
    }
    }
    }
    XCTAssertEqual(UInt16(bytes: 0x35, 0x0B), 13579)
    XCTAssertEqual(Int(bytes: 0x07, 0x5B, 0xCD, 0x15), 123456789)

    View Slide

  12. ಡΈࠐΈɺͪΐͬͱ໘౗…

    View Slide

  13. FeliCaΛಡΈࠐΉϥΠϒϥϦΛ࡞Γ·ͨ͠
    https://github.com/tattn/NFCReader

    View Slide

  14. NFCReader - ࢖͍ํ
    let reader = Reader()
    reader.read(didBecomeActive: { _ in
    print("ಡΈࠐΈ։࢝")
    }, didDetect: { reader, result in
    switch result {
    case .success(let suica):
    let balance = suica.boardingHistories.first?.balance ?? 0
    reader.setMessage(balance)
    case .failure(let error):
    reader.setMessage("ಡΈࠐΈʹࣦഊ͠·ͨ͠")
    }
    }) ௒؆୯

    View Slide

  15. NFCReader - ࢖͍ํ
    Reader()
    Reader()
    Reader()
    Reader()
    Reader()
    Reader()
    ৭ʑରԠͯ͠Έ·ͨ͠
    ←Suica
    ←nanaco
    ←WAON
    ←্هͷશͯΛೝࣝ
    ←ࣗ࡞ͷλά
    ←Edy
    Pasmo, Kitaca, ICOCA, TOICAɺ
    manacaɺPiTaPaɺnimocaɺ
    SUGOCAɺ͸΍͔͚Μɺଞ
    ( )

    View Slide

  16. ·ͱΊɾײ૝
    • CoreNFCΛ࢖ͬͯFeliCa (Suica) ΛಡΈࠐΉํ๏ɺ

    ࡞ͬͨϥΠϒϥϦΛ঺հ
    • Suicaͷ࢒ߴྖҬ͸2bytes͔͠༻ҙ͞Εͯͳ͍ͷͰ

    ೖۚͰ͖Δ্ݶΛ؆୯ʹ͸૿΍ͤͳ͍ࣄ΋Θ͔ͬͨ
    • FeliCaͷ࢓༷͸Sony͕೔ຊޠͰ

    ஸೡʹॻ͍͍ͯΔͷͰͱͯ΋Θ͔Γ΍͔ͬͨ͢

    View Slide

  17. References
    • https://developer.apple.com/videos/play/wwdc2019/715
    • https://developer.apple.com/documentation/corenfc
    • https://www.sony.co.jp/Products/felica/business/tech-
    support/index.html
    • https://www.wdic.org/w/RAIL/αΠόωن֨%20(ICΧʔυ)
    • http://www014.upp.so-net.ne.jp/SFCardFan/
    • https://ja.osdn.net/projects/felicalib/wiki/FrontPage

    View Slide