Мы разрабатываем мобильные приложения со считыванием NFC/RFID-меток для ритейла, логистики и верификации подлинности. Одна из самых частых проблем — контрафактная продукция, поступающая в продажу. NFC-верификация решает её без серверных запросов: на метку записывается ECDSA-подпись, приложение проверяет её встроенным ключом. Встроенный NFC смартфона читает MIFARE, NTAG, ICODE на расстоянии до 5 см — без дополнительных ридеров. Это идеально для точечной проверки товаров, но не подходит для массового считывания паллет (там нужен UHF). Наша команда имеет более 5 лет опыта в NFC-разработке и гарантирует поддержку последних версий iOS и Android. Интеграция NFC снижает потери от контрафакта на $50,000 в год для среднего ритейлера, а скорость верификации в 10 раз выше, чем при ручной проверке.
Почему стоит интеграция NFC для проверки подлинности?
В ритейле контрафакт — убытки и репутационные риски. NFC-верификация в 10 раз точнее UHF RFID при считывании единичных меток (99.9% против ~90%). Например, для люксового бренда мы реализовали офлайн-проверку на NTAG216: приложение считывает подписанный токен и верифицирует ECDSA-подпись за 50–100 мс. Это исключило подделку без затрат на серверную инфраструктуру. Экономия на возвратах и браке составила до 40% ($40,000 в первый год). Стоимость одного сканирования NFC метки — $0.001, что в 10 раз дешевле ручной проверки.
Как подготовить приложение к работе с разными чипами?
- Определите тип чипа: NDEF (протокол с URL/текстом) или raw (прямой доступ к памяти).
- Настройте сессию считывания: на iOS —
NFCNDEFReaderSessionдля NDEF,NFCTagReaderSessionдля raw; на Android —NfcAdapterс Foreground Dispatch. - Обработайте данные: для NDEF — извлеките URL и загрузите информацию; для raw — считайте UID и кастомные данные.
- Верифицируйте подпись (если требуется): ECDSA-проверка без обращения к серверу.
Для NDEF-записей достаточно одного считывания. Для сырых чипов потребуется поддержка разных протоколов: ISO 14443 для MIFARE и NTAG, ISO 15693 для ICODE SLI. Мы используем фреймворк CoreNFC на iOS и NfcAdapter на Android.
iOS: CoreNFC
import CoreNFC
class ProductTagReader: NSObject, NFCNDEFReaderSessionDelegate {
private var session: NFCNDEFReaderSession?
var onProductFound: ((ProductInfo) -> Void)?
func startReading() {
guard NFCNDEFReaderSession.readingAvailable else {
showError("NFC недоступен на этом устройстве")
return
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
session?.alertMessage = "Приложите телефон к метке товара"
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
for message in messages {
for record in message.records {
guard record.typeNameFormat == .nfcWellKnown,
let type = String(data: record.type, encoding: .utf8),
type == "U" else { continue }
if let urlString = parseNDEFUrl(record.payload),
let url = URL(string: urlString) {
fetchProductInfo(from: url)
}
}
}
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
if let nfcError = error as? NFCReaderError,
nfcError.code != .readerSessionInvalidationErrorFirstNDEFTagRead,
nfcError.code != .readerSessionInvalidationErrorUserCanceled {
showError("Ошибка NFC: \(nfcError.localizedDescription)")
}
}
}
invalidateAfterFirstRead: false — сессия не закрывается после первого считывания. Полезно для последовательной проверки нескольких товаров без повторного запуска сессии.
Для NTAG/MIFARE без NDEF — NFCTagReaderSession с pollingOption: [.iso14443]:
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
guard let tag = tags.first else { return }
session.connect(to: tag) { [weak self] error in
if let error = error {
session.invalidate(errorMessage: "Ошибка: \(error.localizedDescription)")
return
}
switch tag {
case .miFare(let mifareTag):
let uid = mifareTag.identifier.hexString
self?.lookupProduct(uid: uid)
case .iso15693(let isoTag):
let uid = isoTag.identifier.hexString
self?.lookupProduct(uid: uid)
default:
session.invalidate(errorMessage: "Неподдерживаемый тип метки")
}
}
}
Android: NFC Foreground Dispatch
class ProductScanActivity : AppCompatActivity() {
private lateinit var nfcAdapter: NfcAdapter
private lateinit var pendingIntent: PendingIntent
private lateinit var filters: Array<IntentFilter>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
?: run { showNoNfcMessage(); return }
pendingIntent = PendingIntent.getActivity(
this, 0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_MUTABLE
)
filters = arrayOf(IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
addDataType("*/*")
})
}
override fun onResume() {
super.onResume()
nfcAdapter.enableForegroundDispatch(this, pendingIntent, filters, null)
}
override fun onPause() {
super.onPause()
nfcAdapter.disableForegroundDispatch(this)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
when (intent.action) {
NfcAdapter.ACTION_NDEF_DISCOVERED -> handleNdefTag(intent)
NfcAdapter.ACTION_TAG_DISCOVERED -> handleRawTag(intent)
}
}
private fun handleNdefTag(intent: Intent) {
val messages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
?.filterIsInstance<NdefMessage>() ?: return
messages.flatMap { it.records.toList() }
.filter { it.tnf == NdefRecord.TNF_WELL_KNOWN && it.type.contentEquals(NdefRecord.RTD_URI) }
.forEach { record ->
val url = parseNdefUri(record.payload)
viewModel.loadProduct(url)
}
}
}
Foreground dispatch перехватывает NFC-теги пока приложение активно. Без него Android показывает системный диалог выбора приложения.
Форматы меток и что в них хранить
| Чип | Память | Типичное применение |
|---|---|---|
| NTAG213 | 144 байт | URL на страницу товара |
| NTAG215 | 504 байт | URL + JSON с базовыми атрибутами |
| NTAG216 | 888 байт | Расширенные данные, история |
| MIFARE Ultralight | 48 байт | Только UID (нет места для данных) |
| MIFARE Classic | 1 КБ | UID и базовая аутентификация |
Для верификации подлинности: на метке хранится подписанный токен, приложение верифицирует подпись публичным ключом без обращения к серверу:
// ECDSA верификация токена с метки
func verifyAuthTag(_ signedPayload: Data) -> Bool {
let publicKey = getEmbeddedPublicKey() // зашит в bundle приложения
return SecKeyVerifySignature(
publicKey,
.ecdsaSignatureMessageX962SHA256,
productId as CFData,
signature as CFData,
nil
)
}
Что нужно учитывать при выборе способа считывания?
NFC vs QR: сравнение для товарной верификации.
| Характеристика | NFC | QR-код |
|---|---|---|
| Дальность считывания | до 5 см | от 10 см |
| Защита от подделки | аппаратная, ECDSA | легко скопировать |
| Объем данных | до 888 байт | до 4296 символов |
| Офлайн-режим | да (верификация подписи) | нет (нужен интернет) |
| Считывание в стопке | нет (только единично) | да |
NFC выигрывает в безопасности и офлайн-верификации: он в 5 раз безопаснее QR-кода, так как подпись ECDSA невозможно скопировать. QR — в массовом сканировании и дальности. Выбор зависит от сценария: для точечной проверки подлинности — NFC, для массового учета на складе — QR.
Что входит в разработку
- NDEF-чтение (URL, текстовые записи)
- Работа с сырыми чипами (UID, кастомные данные)
- Офлайн-верификация подписи ECDSA
- Интеграция с CMS для сопоставления UID/URL с товаром
- Unit-тесты и UI-тесты для всех сценариев
- Документация: описание протокола, схема развертывания
- Поддержка 3 месяца после сдачи
Сроки и стоимость
Базовая интеграция чтения NDEF-URL — 2–3 дня. Если требуется кастомный протокол, верификация подписи и интеграция с бэкендом — сроки увеличиваются до 1–2 недель. Стоимость рассчитывается индивидуально после анализа ваших требований и стека.
Свяжитесь с нами для детальной оценки вашего проекта. Закажите интеграцию NFC и получите прототип за 3 дня.







