NFC tag emulator for the M5StickC Plus 1.1 / PLUS2 + GroveNFC v1.2.
Loads dump files exported from Flipper Zero and Proxmark3 and has the GroveNFC module emulate them: Mifare Classic, NTAG / Ultralight, ISO15693 (SLIX / SLIX-L including Privacy Mode, SLI, Tag-it, ...).
One module firmware for everything. Mifare Classic (including Auth/Crypto1), SLIX-L Privacy Mode and the NTAG extensions now all run together in
module-fw/GroveNFC_EXT_fw0x6931.uf2. The vendor's official firmware (Mifare emulation closed-source, no sources available) is no longer needed for this - this firmware ships its own Mifare Classic emulation including a Crypto1 cipher, see "Mifare Classic + Privacy: the custom Crypto1 implementation" below, in particular what has only been simulated on a Mac and what has not been verified against real hardware. License is therefore GPLv3 instead of MIT, see "License" at the end.
pio run -e m5stick-c-plus -t upload # flash the Plus 1.1 (4 MB flash)
pio run -e m5stick-c-plus2 -t upload # flash the PLUS2 (8 MB flash, more storage for tag files)
pio run -t uploadfs # copy data/ into LittleFSB = scroll file list up, power button = scroll down, short press A = start/stop emulation.
(The StickC Plus has no real third "BtnC" button - the power button takes on that role; M5Unified
exposes it as BtnPWR.) Loading a tag takes about 1 s (one flash sector on the module).
While a tag is emulating, B/power directly switch to the previous/next tag file in the same folder (skipping subfolders) and start it immediately - handy for stepping through a whole collection without returning to the menu each time.
Hold A (~1 s) starts WiFi upload mode: the StickC opens its own WiFi network (EmuGrooveTag-xxxx,
WPA2, fixed password shown on the display). Connect with a laptop or phone, open http://192.168.4.1,
and drag files onto the page (multiple at once; listing, download and delete all work too, including
folder creation/navigation). B exits the mode, after which the file list is refreshed. Only the file
extensions listed below are accepted, max. 300 KB per file.
A real USB mass-storage mode isn't possible on this hardware (ESP32-PICO-D4 without native USB, no SD
card slot). Alternative: drop files into data/ and run pio run -t uploadfs (overwrites the entire
filesystem).
| Extension | Source | Contents | Verified against |
|---|---|---|---|
.nfc |
Flipper Zero | Mifare Classic 1K/4K, NTAG/Ultralight (UL, UL-C, NTAG203/213/215/216, UL EV1, NTAG I2C), SLIX/SLIX-L/SLIX2/ISO15693-3 | Flipper source code |
.json |
Proxmark3 | mfc v2/mfc v3/mfcard, mfu, 15693 v4/v5 |
client/src/fileutils.c |
.eml |
Proxmark3 / emulators | Classic (16-byte lines), NTAG/UL (4-byte lines) | fileutils.c |
.mct |
Mifare Classic Tool | Classic | fileutils.c |
.bin |
both | Classic 1024/4096 raw, NTAG/UL raw, PM3 mfu (56-byte header), PM3 ISO15693 (2332/2235 bytes) |
include/{mifare,iso15}.h |
Rejected with an error message: Mifare Mini, Classic 2K, DESFire, FeliCa, iCLASS, Legic, Hitag, ISO14443-4/-3B, ...
| Type | Support |
|---|---|
| Mifare Classic 1K/4K, 4-byte/7-byte UID | custom Crypto1 emulation (auth + encrypted read/write), see below |
| NTAG213/215/216 (read/write) | yes |
| NTAG with password, signature, counter; Ultralight EV1/UL/NTAG203 | yes (from the file, via the extension-record mechanism) |
| SLIX-L / SLIX with Privacy Mode | yes |
| ISO15693 with non-default block count/IC reference | taken exactly from the file |
Flashing: put the module into UF2 mode and copy module-fw/GroveNFC_EXT_fw0x6931.uf2 onto it, or build
it yourself from source (see "Building" below). Back to the vendor firmware: UF2 from Firmware/ in the
NFC-funs/GroveNFC repo (Mifare emulation stays closed-source
there, but without the limitations listed below). The patch lives in module-fw/ext-features.patch
(8 changed/new files), the source in module-fw/src/ (Pico SDK 2.3.0, cmake + make,
PICO_BOARD=pico). Ideally the vendor would upstream at least the Mifare Classic emulation as a
callable function - then this custom addition wouldn't be needed. Nothing was sent back to them.
The GroveNFC API demo this project builds on has no Mifare Classic emulation at all - authentication was
completely missing (Mifare1_Emul(...) was just a comment in the source). To get Mifare Classic and
SLIX-L Privacy into the same firmware, module-fw/src/libNFC/Crypto1/ now contains a custom Crypto1
cipher implementation (Mifare Classic's stream cipher used for card authentication), ported from the
public reference implementation common/crapto1 in the
RfidResearchGroup/proxmark3 repo (GPLv3,
© bla/Proxmark3 contributors) - only the core cipher functions (crypto1_init/_bit/_byte/_word,
prng_successor), none of the key-recovery/cryptanalysis functions. Built on top of that, in
module-fw/src/2-NFC/ISO14A/Mifare1/X_Mifare1.c: anticollision (4-byte/7-byte UID), auth (60/61)
against the KeyA/KeyB already present in the dump file, encrypted read (30) and write (A0).
What it supports: 1K (16×4 blocks) and 4K (32×4 + 8×16 blocks) sector geometry; keys are never read
back (always 0x00 when reading a trailer block); either key (A or B) unlocks both read and write for
the whole sector (no fine-grained access-bit evaluation).
What's missing: value-block arithmetic (increment/decrement/restore/transfer), proper per-key-type
access rights, every other Mifare Classic variant (Mini, Plus, DESFire).
Verified: a host-side test harness (tests/module_mifare1_test.c, part of ./tests/run.sh)
simulates a complete reader (PCD) using the same cipher code and drives the real tag code
(X_Mifare1.c) through REQA → anticollision → auth → encrypted read/write → halt, for both 4-byte and
7-byte UIDs and both 1K and 4K geometry, including wrong-key and key-masking cases (25 checks, all
pass). This proves internal consistency (tag and simulated reader agree on the same cipher state),
not bit-level compatibility with a real external reader.
Not verified, most likely to have issues:
- Encrypted parity bits. After auth, each transmitted byte is expected to carry one additional,
co-encrypted parity bit. Which exact extra cipher clock real NXP chips use for this couldn't be
conclusively determined from publicly available material; the implementation uses the most
plausible variant, the one most commonly described in the literature (one extra clock per byte, fed
unencrypted). If a real reader can't read the card: look here first (
mf1Encrypt/mf1DecryptinX_Mifare1.c). - Whether the module's low-level PIO capture (precompiled, no source available) passes encrypted frames with non-standard parity through unchanged.
- Everything else listed under "Not verified" further below regarding RF timing/I2C.
Reference: nfc_listener_defs.c -
Flipper emulates exactly these 7 protocols:
| Flipper emulation | GroveNFC | Here |
|---|---|---|
| Mifare Classic | 4-byte/7-byte × 1K/4K, auth + write (custom Crypto1 emulation) | implemented |
| Mifare Ultralight / NTAG (all variants) | NTAG213/215/216 natively; Ultralight family via the ext firmware (version/signature/password/counter from the file) | implemented |
| SLIX / SLIX-L / SLIX2 / SLIX-S | ISO15693 inventory/read/write; Privacy added via the patch | implemented (Privacy: SLIX-L tested) |
| ISO15693-3 generic | as above | implemented (block size 4 only) |
| ISO14443-3A (UID only) | via the NTAG/Mifare type | as a workaround |
| ISO14443-4A (APDU, NDEF Type 4) | not in the module | not feasible without module firmware work |
| FeliCa | the register knows FeliCa, but there's no emulation logic | not feasible without module firmware work |
Not emulated by Flipper at all: ISO14443-3B/-4B, DESFire, ST25TB, MIFARE Plus.
From the source code (RfidResearchGroup/proxmark3, hf ... sim):
| Proxmark simulation (HF) | GroveNFC |
|---|---|
hf mf sim / hf 14a sim -t 1,8 Classic 1K/4K |
yes (custom Crypto1 emulation) |
hf 14a sim -t 6 Classic Mini |
no |
hf mfu sim / hf 14a sim -t 2,7,13,14 Ultralight, EV1, NTAG215, UL-C, UL-AES |
NTAG/UL EV1 yes; UL-C without 3DES auth only; UL-AES no |
hf 15 sim ISO15693 (including SLIX privacy password) |
yes |
hf 14a sim -t 3,4,5,9,10,11,12,15 DESFire, ISO14443-4, TNP3xxx, FM11RF005SH, ST25TA, JCOP, Seos, MIFARE Plus |
no |
hf 14b sim, hf felica sim, hf iclass sim, hf legic sim, hf topaz sim, hf st25ta sim, hf mfdes sim, hf seos sim, hf cryptorf/epa/jooki/secc/texkom/thinfilm sim |
no |
lf ... sim (EM410x, HID, Indala, ...) |
no - the module is 13.56 MHz only |
Bottom line: everything the GroveNFC can emulate (Mifare Classic, NTAG/Ultralight, ISO15693) can be loaded from either Flipper or Proxmark files. Both sources produce bit-identical images for the same card (tested: Classic bin = eml = mct = json; NTAG Flipper = raw = eml = PM3 bin = PM3 json; ISO15693 PM3 json = PM3 bin). Anything the module can't emulate simply isn't loaded.
Follows Flipper (slix_listener_i.c) and - confirmed against the Proxmark source
(armsrc/iso15693.c) - NXP:
- Privacy on: the tag is silent, only
Get Random Number (B2)andSet Password (B3)are answered. Set Password(type Privacy, password XOR'd withRN|RN) correct → privacy off;Enable Privacy (BA)→ on.- Wrong password/wrong ID: no response. Initial state comes from the file's
Privacy Mode:field - but since many dump collections export tags as unlocked even though real readers only treat the lock/unlock handshake as "a new tag was presented", a tag is always started locked whenever it carries a privacy password at all, regardless of what the file says. - Proxmark JSON/BIN carry the password but no explicit "privacy on" flag; such tags start visible and
can be muted via
Enable Privacy. - Addressed commands (
Flagsbit0x20set) are only answered when the UID in the command matches the tag's own current UID, as required by the ISO15693 standard - otherwise the tag stays silent. This was missing from the original code (every addressed command was answered regardless of UID), which could leave a reader stuck addressing a stale UID from a previously-loaded tag after a switch; found via real-world hardware testing with a logic-level sniff of a commercial SLIX-L privacy reader.
Stock firmware answers GET_VERSION only from the CC byte, READ_SIG with zeros, and PWD_AUTH with a
NAK. The ext firmware supplies, from the file: a real version (or NAK for UL/NTAG203), signature,
PWD_AUTH (password + PACK from the config pages) and READ_CNT. Write protection/AUTH0 isn't
enforced (everything stays readable). If the password page is blank in the dump (real tags return it
masked), reader authentication will fail - the host flags this.
Verified (against source code or by test, no hardware needed) - ./tests/run.sh, 6 suites:
- Module registers, layouts and type codes: against the register PDF and the vendor API source
(
X_RegPara.h,X_ISO15_App.c,X_Ntag.c). - All file formats: against Flipper and Proxmark source code; cross-checked identical cards across every format.
- The real module sources (
X_ISO15_App.c,X_Ntag.c,X_Mifare1.c+ the portedcrapto1.c) run on the host against simulated reader frames: SLIX-L privacy sequence (including addressed commands and UID matching), NTAG anticollision/read/write/version/signature/PWD_AUTH/counter, Mifare Classic anticollision/auth/encrypted read+write/1K+4K geometry/4-byte+7-byte UID/wrong-key case - each also covering the stock case without an extension record or without Crypto1 interaction. - Host and module firmware both build without errors (one firmware, every feature combined).
Verified against real hardware (Proxmark3 + a real SLIX-L privacy reader):
- UID and full tag-content switching between different loaded files (RF level, not just host-side state).
- SLIX-L privacy password byte order (found via a real capture, documented in the code).
- The addressed-command UID-matching fix described above.
Not verified - please test on your own device/reader:
- Mifare Classic against a real reader - in particular the encrypted parity bits (see "Mifare Classic + Privacy" above). This is the least certain part of the whole firmware.
- Privacy password byte order for Flipper files with an asymmetric password (Proxmark files are
unambiguous). If unlocking doesn't work: try
PRIVACY_PWD_REVERSEDinsrc/config.hset to1.
- Mifare: sectors with
??/-(unknown keys) can't be authenticated; the number of unknown blocks is shown. Mini, 2K and 10-byte UID aren't supported. No value-block arithmetic, no fine-grained access-bit evaluation (see above). - SLIX: read/write/EAS/destroy passwords, lock bits, stay-quiet/select and AFI filtering are missing.
- ISO14B "China ID" (UID only) and FeliCa: not implemented.
- The module's tag buffer can't be read back → writes from a reader can't be saved back out to a file.
- Every emulation writes one flash sector on the module (limited erase cycles, not a concern in normal use).
For your own tags/cards only, or ones you have permission to use (testing, development, backups). You need to already know any passwords involved; nothing here cracks them for you.
- StickC Plus Grove port: SDA = G32, SCL = G33, I2C address
0x48, 100 kHz (reduced from the GroveNFC spec's 400 kHz on the PLUS2 for more margin against weaker pull-ups on that board's Grove port). - The host uses
M5.Ex_I2C(I2C_NUM_0).Wire1is off-limits: M5Unified runs the internal bus (power IC, backlight, IMU) on it. - Plus 1.1: 4 MB flash, ~1.4 MB usable for tag files. PLUS2: 8 MB flash with a custom partition table
(
partitions_plus2_8mb.csv), ~6.4 MB usable for tag files.
Host firmware (ESP32, PlatformIO):
pio run -e m5stick-c-plus # Plus 1.1
pio run -e m5stick-c-plus2 # PLUS2Module firmware (RP2040, Pico SDK 2.3.0 + CMake):
cd module-fw/src
mkdir build && cd build
PICO_SDK_PATH=/path/to/pico-sdk cmake -G "Unix Makefiles" ..
make -j4This produces Pico_NFC.uf2. Flash it by putting the module into UF2 bootloader mode (hold BOOTSEL
while connecting) and either copying the file onto the drive that appears, or using picotool load -u.
Prebuilt binaries: see the Releases page for ready-to-flash images - .bin files
for the ESP32 host (flash with esptool.py write_flash ..., see the release notes for the exact
command) and a .uf2 for the RP2040 module (drag-and-drop in UF2 bootloader mode, or picotool load -u).
The host code (src/) and the vendor-derived module firmware base (module-fw/src/, minus the files
below) are MIT licensed, same as the original upstream repo. The ported Crypto1 implementation
(module-fw/src/libNFC/Crypto1/crapto1.c + crapto1.h, © 2008-2014 bla, © Proxmark3 contributors) is
GPLv3. This makes the compiled module firmware (GroveNFC_EXT_fw0x6931.uf2) GPLv3 as a whole, not
MIT - anyone redistributing it must also offer the complete corresponding source (already the case
here: module-fw/src/). parity.h (evenparity32) is an independent, trivial reimplementation, not a
port.
See LICENSE for the full GPLv3 text.
- NFC-funs/GroveNFC - vendor hardware, register documentation and the original (closed-Mifare) API demo this module firmware is built on.
- RfidResearchGroup/proxmark3 -
common/crapto1(Crypto1 cipher core, GPLv3) and file-format reference (client/src/fileutils.c,include/mifare.h,include/iso15.h). - flipperdevices/flipperzero-firmware -
.nfcfile format andnfc_listener_defs.cprotocol/feature reference. - whywilson/GroveNFC - an independent host driver that helped
confirm the correct
REG_MODEvalue for starting emulation. - M5Unified - M5Stack's hardware abstraction library used for the host firmware.
src/ Host firmware: main.cpp, grovenfc.* (I2C driver), tagfile.* + loader_*.cpp (all formats)
data/ Sample files (SLIX-L, Mifare 1K, NTAG215)
module-fw/ Firmware (UF2), patch, source - including libNFC/Crypto1 (GPLv3, see "License")
tests/ run.sh, samples in every format (make_samples.py), module simulations (including Mifare1)