Distributed data archive on NFC tags. A mobile application for Android and iOS that allows storing files across multiple NFC tags and restoring them when all parts are available.
- File archiving — splitting any file into parts for writing to NFC tags
- Restoration — scanning tags in any order and assembling the original file
- Compression — optional GZIP compression to reduce the number of tags needed
- Encryption — AES-256-GCM encryption with password (PBKDF2 for key derivation)
- Offline operation — no network connection required
- Chameleon Ultra reader — drive a Chameleon Ultra over Bluetooth instead of the phone's NFC radio (guide)
- MIFARE Classic 1K — 752 usable bytes per card, on any phone when using a Chameleon
- Card inspector — read-only block-by-block dump with a decoded NFAR header and CRC check (requires a Chameleon)
The app can drive two different readers, chosen from the reader icon in the top bar:
| Phone NFC | Chameleon Ultra | |
|---|---|---|
| NTAG213/215/216 | ✅ | ✅ |
| MIFARE Classic 1K | Only on phones whose NFC chipset supports CRYPTO1 | ✅ always |
| Card inspector | ❌ not possible on Android's NFC API | ✅ |
| Extra hardware | None | A Chameleon Ultra |
Only one reader is active at a time; switching disconnects the previous one. Cards written by either reader — or by the web app — are byte-identical and interchangeable. See docs/CHAMELEON.md for setup, the card inspector, and limits.
| Tag Type | Capacity | Useful Payload* |
|---|---|---|
| NTAG213 | 144 bytes | ~106 bytes |
| NTAG215 | 504 bytes | ~466 bytes |
| NTAG216 | 888 bytes | ~850 bytes |
| MIFARE Ultralight | 48 bytes | ~10 bytes |
| MIFARE Ultralight C | 144 bytes | ~106 bytes |
| MIFARE Classic 1K | 1 KB (752 usable) | ~720 bytes† |
*After subtracting NFAR header (28 bytes) and NDEF overhead (~10 bytes)
†MIFARE Classic 1K holds raw 16-byte blocks (47 usable = 752 bytes) with no NDEF layer, so only the 28-byte NFAR header and 4-byte CRC are deducted. Available in the Android app on phones whose NFC chipset supports CRYPTO1, and on any phone when using a Chameleon Ultra. Block 0 and every sector trailer are never written, so keys and access bits are left untouched.
Note: This list is not exhaustive — more tag types are supported than shown here, and coverage continues to grow.
┌─────────────────────────────────────────────────────┐
│ Magic (4 bytes): "NFAR" = 0x4E464152 │
├─────────────────────────────────────────────────────┤
│ Version (1 byte): 0x01 │
├─────────────────────────────────────────────────────┤
│ Flags (1 byte): compression | encryption │
├─────────────────────────────────────────────────────┤
│ Archive ID (16 bytes): UUID v4 │
├─────────────────────────────────────────────────────┤
│ Total Chunks (2 bytes): uint16 big-endian │
├─────────────────────────────────────────────────────┤
│ Chunk Index (2 bytes): uint16 big-endian │
├─────────────────────────────────────────────────────┤
│ Payload Size (2 bytes): uint16 big-endian │
├─────────────────────────────────────────────────────┤
│ Payload (N bytes): data │
├─────────────────────────────────────────────────────┤
│ CRC32 (4 bytes): checksum │
└─────────────────────────────────────────────────────┘
- Flutter SDK 3.5+
- Android SDK (API 26+) for Android
- Xcode 15+ for iOS
- Device with NFC support
# Clone
git clone https://github.com/mezinster/nfcarchiver.git
cd nfcarchiver
# Install dependencies
flutter pub get
# Run on device
flutter runFor iOS, NFC entitlements configuration in Xcode is required:
- Open
ios/Runner.xcworkspacein Xcode - Select Runner → Signing & Capabilities
- Add "Near Field Communication Tag Reading"
- Apple Developer Program membership required
lib/
├── core/ # Core system
│ ├── constants/ # NFAR format
│ ├── models/ # Chunk, ArchiveMetadata, NfcTagInfo
│ ├── services/ # Chunker, Compression, Encryption, CRC32
│ └── utils/ # Binary Reader/Writer
│
├── features/
│ ├── archive/ # Archive creation
│ ├── restore/ # Restoration
│ └── nfc/ # NFC abstraction
│
└── shared/ # Theme, shared widgets
- Flutter — cross-platform UI
- Riverpod — state management
- nfc_manager — phone NFC operations
- flutter_reactive_ble — Bluetooth LE, for the Chameleon Ultra reader (BSD-3-Clause)
- pointycastle — cryptography (AES-256-GCM, PBKDF2)
- go_router — navigation
A browser port of NFC Archiver lives in webapp/. It runs entirely
client-side (no server) and drives a Chameleon Ultra reader/writer over Web
Bluetooth to read and write physical cards from Chromium browsers on desktop and
Android.
- Media: Mifare Classic 1K (720 B payload/card) and NTAG213/215/216 (Type-2 / NDEF). Tag type is auto-detected on tap.
- Cross-compatible with this app: the on-tag bytes match exactly — the same
NFAR chunk format, the same filename wrapper, and, for NTAG, the same NDEF MIME
record (
application/vnd.nfcarchiver.chunk). A tag written on the phone restores in the browser and vice versa; an NTAG written in the browser is a standard NDEF tag readable by any NFC phone. - Features: file and text archiving, GZIP compression, AES-256-GCM encryption, filename preservation, a live "≈ N cards" estimate, multi-archive scan-then-pick restore, and a Chameleon "Diagnose card" tool. The UI is a branded, themed, tabbed shell.
cd webapp
source ~/.nvm/nvm.sh && nvm use --lts # Node ≥ 22 (crypto.subtle, CompressionStream)
npm install
npm test # dependency-free NFAR core + transports, tested against a fake device
npm run app # serves http://localhost:8000 — open in Chrome/Edge on the Bluetooth hostWeb Bluetooth is Chromium-only (not Firefox or iOS Safari), and — inside WSL — the
browser must run on the Windows host. Status: a working prototype; the archive →
cards → restore round trip is validated on real hardware. See
webapp/README.md for architecture, the SDK/hardware notes,
and the parity roadmap (localization, an offline file manager, and phone-native
Web NFC writing are still to come).
This app went through 13 iterations of its F-Droid metadata MR before acceptance. Here are the key challenges encountered:
-
Invalid metadata categories —
Utilityis not a valid F-Droid category. Had to useConnectivityinstead, which is the closest match for an NFC-based app. -
Missing required fields — F-Droid's linting requires
AutoName(human-readable app name) andUpdateCheckData(regex to extract version from the repo). Without these, thecheckupdatespipeline fails. -
Flutter version pinning — F-Droid's
flutter@stablesrclib doesn't guarantee a specific Flutter version. The solution was to extractFLUTTER_VERSIONfrom the GitHub release workflow usingsedand explicitlygit checkoutthat version inprebuild:prebuild: - flutterVersion=$(sed -n -E "s/.*FLUTTER_VERSION:\ '(.*)'/\1/p" .github/workflows/release.yml) - git -C $$flutter$$ checkout -f $flutterVersion
-
Package scanning (
scandelete) — F-Droid scans all dependencies for proprietary code betweenprebuildandbuild. This meansflutter pub getmust run inprebuild(notbuild), and.pub-cachemust be listed inscandeletesince it contains pre-compiled binaries. -
compileSdk 35 vs JDK 21 incompatibility — F-Droid's build server uses JDK 21, which has a
jlink/JdkImageTransformbug with Android SDK 35. Multiple approaches failed:sed-patching.pub-cacheplugin files — failed becausescandeleteremoves.pub-cachebefore build- Gradle
afterEvaluateoverride — failed with "project already evaluated" due to Flutter'sevaluationDependsOn - Gradle init script — worked but was overly complex
- Final solution: lower
compileSdkto 34 in the source repo itself, plus anafterEvaluateblock for plugin subprojects
-
JDK 17 installation — Even with
compileSdk34, JDK 21 still caused issues. The fix required installing JDK 17 viasudo, but F-Droid's build server runs Debian Trixie which doesn't have JDK 17 in its repos. Solution: add the Debian Bookworm repo first:sudo: - echo 'deb http://deb.debian.org/debian bookworm main' > /etc/apt/sources.list.d/bookworm.list - apt-get update - apt-get install -y openjdk-17-jdk-headless
-
rewritemetaformatting — F-Droid's linter (rewritemeta) enforces strict field ordering (e.g.,sudo:must come aftercommit:) and formatting rules (multi-part shell commands likeechomust stay on a single line). -
Commit hash requirement — The reviewer required a full commit SHA (
97f2567c...) instead of a tag reference (v1.0.6) for build reproducibility.
MIT
Built by Evgeny Mezin, directing Claude Code as a coding assistant throughout.
Распределённый архив данных на NFC-метках. Мобильное приложение для Android и iOS, позволяющее хранить файлы на множестве NFC-меток и восстанавливать их при наличии всех частей.
- Архивация файлов — разбиение любого файла на части для записи на NFC-метки
- Восстановление — сканирование меток в произвольном порядке и сборка исходного файла
- Сжатие — опциональное GZIP сжатие для уменьшения количества меток
- Шифрование — AES-256-GCM шифрование с паролем (PBKDF2 для ключа)
- Офлайн работа — не требует подключения к сети
- Считыватель Chameleon Ultra — работа через Chameleon Ultra по Bluetooth вместо NFC телефона (руководство)
- MIFARE Classic 1K — 752 полезных байта на карту, на любом телефоне при использовании Chameleon
- Осмотр карты — побайтовый дамп только для чтения с разбором заголовка NFAR и проверкой CRC (нужен Chameleon)
Приложение работает с двумя считывателями, выбор — по значку в верхней панели:
| NFC телефона | Chameleon Ultra | |
|---|---|---|
| NTAG213/215/216 | ✅ | ✅ |
| MIFARE Classic 1K | Только на телефонах с поддержкой CRYPTO1 | ✅ всегда |
| Осмотр карты | ❌ невозможен через NFC API Android | ✅ |
| Доп. оборудование | Не нужно | Chameleon Ultra |
Одновременно активен только один считыватель; переключение отключает предыдущий. Карты, записанные любым считывателем — или веб-приложением — побайтово совместимы. Подробности: docs/CHAMELEON.md.
| Тип метки | Ёмкость | Полезная нагрузка* |
|---|---|---|
| NTAG213 | 144 байт | ~106 байт |
| NTAG215 | 504 байт | ~466 байт |
| NTAG216 | 888 байт | ~850 байт |
| MIFARE Ultralight | 48 байт | ~10 байт |
| MIFARE Ultralight C | 144 байт | ~106 байт |
| MIFARE Classic 1K | 1 КБ (752 доступно) | ~720 байт† |
*После вычета заголовка NFAR (28 байт) и NDEF overhead (~10 байт)
†Карты MIFARE Classic 1K хранят необработанные 16-байтные блоки (47 доступных = 752 байта) без слоя NDEF, поэтому вычитаются только 28-байтный заголовок NFAR и 4-байтный CRC. Доступны в Android-приложении на телефонах с поддержкой CRYPTO1 в NFC-чипе и на любом телефоне при использовании Chameleon Ultra. Блок 0 и трейлеры секторов никогда не записываются, поэтому ключи и биты доступа остаются нетронутыми.
Примечание: список неполный — поддерживается больше типов меток, чем показано здесь, и охват продолжает расширяться.
┌─────────────────────────────────────────────────────┐
│ Magic (4 bytes): "NFAR" = 0x4E464152 │
├─────────────────────────────────────────────────────┤
│ Version (1 byte): 0x01 │
├─────────────────────────────────────────────────────┤
│ Flags (1 byte): compression | encryption │
├─────────────────────────────────────────────────────┤
│ Archive ID (16 bytes): UUID v4 │
├─────────────────────────────────────────────────────┤
│ Total Chunks (2 bytes): uint16 big-endian │
├─────────────────────────────────────────────────────┤
│ Chunk Index (2 bytes): uint16 big-endian │
├─────────────────────────────────────────────────────┤
│ Payload Size (2 bytes): uint16 big-endian │
├─────────────────────────────────────────────────────┤
│ Payload (N bytes): data │
├─────────────────────────────────────────────────────┤
│ CRC32 (4 bytes): checksum │
└─────────────────────────────────────────────────────┘
- Flutter SDK 3.5+
- Android SDK (API 26+) для Android
- Xcode 15+ для iOS
- Устройство с NFC
# Клонирование
git clone https://github.com/mezinster/nfcarchiver.git
cd nfcarchiver
# Установка зависимостей
flutter pub get
# Запуск на устройстве
flutter runДля iOS требуется настройка NFC entitlements в Xcode:
- Откройте
ios/Runner.xcworkspaceв Xcode - Выберите Runner → Signing & Capabilities
- Добавьте "Near Field Communication Tag Reading"
- Требуется Apple Developer Program
lib/
├── core/ # Ядро системы
│ ├── constants/ # Формат NFAR
│ ├── models/ # Chunk, ArchiveMetadata, NfcTagInfo
│ ├── services/ # Chunker, Compression, Encryption, CRC32
│ └── utils/ # Binary Reader/Writer
│
├── features/
│ ├── archive/ # Создание архива
│ ├── restore/ # Восстановление
│ └── nfc/ # NFC абстракция
│
└── shared/ # Тема, общие виджеты
- Flutter — кроссплатформенный UI
- Riverpod — управление состоянием
- nfc_manager — работа с NFC телефона
- flutter_reactive_ble — Bluetooth LE для считывателя Chameleon Ultra (BSD-3-Clause)
- pointycastle — криптография (AES-256-GCM, PBKDF2)
- go_router — навигация
Браузерный порт NFC Archiver находится в webapp/. Работает полностью
на стороне клиента (без сервера) и управляет ридером/райтером Chameleon Ultra
через Web Bluetooth, читая и записывая физические карты из браузеров на базе
Chromium на десктопе и Android.
- Носители: Mifare Classic 1K (720 Б полезной нагрузки на карту) и NTAG213/215/216 (Type-2 / NDEF). Тип метки определяется автоматически при касании.
- Совместимость с этим приложением: байты на метке полностью совпадают — тот
же формат чанка NFAR, та же обёртка имени файла и, для NTAG, та же запись NDEF
MIME (
application/vnd.nfcarchiver.chunk). Метка, записанная на телефоне, восстанавливается в браузере и наоборот; NTAG, записанный в браузере — обычная NDEF-метка, читаемая любым NFC-телефоном. - Возможности: архивация файлов и текста, GZIP-сжатие, шифрование AES-256-GCM, сохранение имени файла, живая оценка «≈ N карт», восстановление нескольких архивов (скан → выбор), инструмент «Diagnose card». Интерфейс — брендированная вкладочная оболочка с темой.
cd webapp
source ~/.nvm/nvm.sh && nvm use --lts # Node ≥ 22 (crypto.subtle, CompressionStream)
npm install
npm test # ядро NFAR без зависимостей + транспорты, тесты против фейкового устройства
npm run app # http://localhost:8000 — открыть в Chrome/Edge на хосте с BluetoothWeb Bluetooth работает только в Chromium (не Firefox и не iOS Safari), а внутри
WSL браузер должен запускаться на Windows-хосте. Статус: рабочий прототип; цикл
архивация → карты → восстановление проверен на реальном железе. Подробности —
архитектура, заметки по SDK/железу и план паритета (локализация, офлайн
файловый менеджер и запись через Web NFC с телефона ещё впереди) — в
webapp/README.md.
Приложение прошло через 13 итераций MR в fdroiddata до принятия. Основные трудности:
-
Невалидные категории метаданных —
Utilityне является допустимой категорией F-Droid. Пришлось использоватьConnectivityкак наиболее подходящую для NFC-приложения. -
Отсутствующие обязательные поля — линтер F-Droid требует
AutoName(человекочитаемое имя) иUpdateCheckData(регулярное выражение для извлечения версии из репозитория). Без них пайплайнcheckupdatesпадает. -
Привязка версии Flutter — srclib
flutter@stableв F-Droid не гарантирует конкретную версию Flutter. Решение — извлекатьFLUTTER_VERSIONиз workflow релиза черезsedи явно делатьgit checkoutнужной версии вprebuild:prebuild: - flutterVersion=$(sed -n -E "s/.*FLUTTER_VERSION:\ '(.*)'/\1/p" .github/workflows/release.yml) - git -C $$flutter$$ checkout -f $flutterVersion
-
Сканирование пакетов (
scandelete) — F-Droid сканирует все зависимости на проприетарный код междуprebuildиbuild. Поэтомуflutter pub getдолжен выполняться вprebuild(не вbuild), а.pub-cacheнужно указать вscandelete, так как он содержит прекомпилированные бинарники. -
Несовместимость compileSdk 35 и JDK 21 — сервер сборки F-Droid использует JDK 21, в котором есть баг
jlink/JdkImageTransformс Android SDK 35. Несколько подходов не сработали:- Патчинг файлов плагинов в
.pub-cacheчерезsed— не работает, т.к.scandeleteудаляет.pub-cacheперед сборкой - Gradle
afterEvaluateoverride — ошибка "project already evaluated" из-заevaluationDependsOnво Flutter - Gradle init script — работал, но слишком сложный
- Итоговое решение: понижение
compileSdkдо 34 в самом репозитории + блокafterEvaluateдля субпроектов плагинов
- Патчинг файлов плагинов в
-
Установка JDK 17 — даже с
compileSdk34 у JDK 21 оставались проблемы. Потребовалась установка JDK 17 черезsudo, но на сервере F-Droid стоит Debian Trixie, где нет JDK 17 в репозиториях. Решение — подключить репозиторий Debian Bookworm:sudo: - echo 'deb http://deb.debian.org/debian bookworm main' > /etc/apt/sources.list.d/bookworm.list - apt-get update - apt-get install -y openjdk-17-jdk-headless
-
Форматирование
rewritemeta— линтер F-Droid (rewritemeta) требует строгого порядка полей (например,sudo:должен идти послеcommit:) и правил форматирования (составные shell-команды вродеechoдолжны быть на одной строке). -
Требование хеша коммита — ревьюер потребовал полный SHA коммита (
97f2567c...) вместо ссылки на тег (v1.0.6) для воспроизводимости сборки.
MIT
Разработано Евгением Мезиным — при участии Claude Code в роли ассистента, под руководством автора на всех этапах.