Skip to content
Access system implementation with Raspi and RFID (ISO-14443) cards. Makes uncloneable cards possible with cryptography (unlike many commercial systems). With status reporting over IRC and SFTP.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Brmdoor via libnfc and libfreefare

This is an access-control system implementation via contactless ISO 14443A cards and a PN53x-based reader. So you basically swipe your card, and if it's in database, the door unlocks.

Info about authorized users and their cards and keys is stored in sqlite database.

It also supports physical OPEN/CLOSE state button for people to indicate if place is opened to public. The state can be reported via IRC topic and SFTP upload in format.

This was originally designed for Raspberry (Raspbian), but it also runs on other distros on Rapi and x86 if you have the PN532 USB reader.

The main daemon is implemented in

Exmple one place where it's used -

The brmlab page also show electrical components and connection (Raspi and lock use different voltages). Any lock can be used with brmdoor_libnfc as long as you can trigger it via GPIO. More secure locks (BERA-E/BERA-D with automatic lock and panic trigger) or a cheap electromagnetic lock.

Supported cards and authentication methods

  • by UID of ISO-14443 card (Mifare Classic, Desfire, Ultralight...) - not safe since it's cloneable, but it's commonly used in comme
  • Yubikey Neo HMAC-SHA1 - most safe option, uncloneable
  • Mifare Desfire - Ed25519 signature of UID (currently no known clones available; although some features could be cloned, it's not enough for anyone to create such partial clones)

Test code is also provided to get payment signature (cryptogram) from Visa and Mastercard, but it's not used.

Building and dependencies

You need just to run make. Additional dependencies:

  • libnfc, in Debian and Ubuntu as libnfc-dev
  • libfreefare, in Debian and Ubuntu install libfreefare-bin and libfreefare-dev
  • python-axolotl-curve25519, in Ubuntu and Debian install python-axolotl-curve25519
  • SWIG version 2 - to generate Python-C++ bindings, SWIG 3 is known to cause segfaults sometimes
  • WiringPi2 pythonic binding (for switching lock on Raspberry)
  • python-irc >= 16.0, use "pip install irc", the one in repos is old
  • pysftp - for uploading SpaceAPI-formatted status to some host
    • optional runtime dependency, not needed unless you set SFTP SpaceAPI upload to true

All dependencies can be installed on Ubuntu or Debian/Raspbian via:

apt install libnfc-dev libfreefare-bin libfreefare-dev python-axolotl-curve25519 swig2.0 python-dev
pip install irc wiringpi2 pysftp

To build, just run make:



  1. Create the database

     python authenthicator_db.sqlite
  2. Copy sample config file, edit your pins, DB file location, timeouts

     cp brmdoor_nfc.config.sample brmdoor_nfc.config
  3. Add some users

  • either authentication by UID, e.g.:

    ./ -c brmdoor_nfc.config -a uid 34795FCC SomeUserName
  • authentication by Yubikey's HMAC-SHA1 programmed on slot 2

    ./ -c brmdoor_nfc.config -a hmac 40795FCCAB0701 SomeUserName 000102030405060708090a0b0c0d0e0f31323334
  • to program Yubikey slot 2 to use HMAC with given key (requires package yubikey-personalization), use:

    ykpersonalize -2 -ochal-resp -ohmac-sha1 -ohmac-lt64 -oserial-api-visible
  • authentication using signed UID as NDEF message on Desfire:

    ./ -c brmdoor_nfc.config -a ndef 04631982cc2280 SomeUserName
  • you need to generate Ed25519 keypair, store the private key somewhere safe and put the public in config file

  • you need to program the Desfire card to have the signature

    ./ private_key_in_hex

Finally, run the daemon:

    sudo python brmdoor_nfc.config

Configuring libnfc devices

If you have PN532 device on other bus than USB (e.g. SPI), first search for it using:

sudo nfc-scan-device -i

After that, create file /etc/nfc/libnfc.conf with line describing your device from nfc-scan-device above, e.g. for SPI device:

device.connstring = "pn532_spi:/dev/spidev0.0"

This daemon expects the library to be already configured to find the PN532 device.

If you installed libnfc from source, the default directory might be /usr/local/etc/nfc instead of /etc/nfc.

Conflicts with other running software - pcscd, pn533 kernel modules

If you have pcscd running, it will take over the reader and you can't use it. Kill/stop pcscd service/process if running.

Similarly, you have to blacklist pn533 and pn533_usb kernel modules (usually in a file like /etc/modprobe.d/blacklist.conf).

Photo of an actual installation

Raspberry with stepup

Connection to PN532 reader

Security note: it's better to have reader behind door, but this door is metal (thus external or wormhole antenna needed). Even though the reader is not connected directly to open PIN which could be triggered by applying power to it.

There are two ways to do it:

Startup with systemd and GNU screen

Example of startup unit for systemd, put in /etc/systemd/system/brmdoor.service and this repo cloned in /root/brmdoor_libnfc:

ExecStart=/usr/bin/screen -L -d -m -S brmdoor
WorkingDirectory= /root/brmdoor_libnfc/

After adding the service file, run systemctl daemon-reload to notify systemd that unit was added. To enable automatic startup, use systemctl enable brmdoor.service.

A /root/.screenrc file that will run the daemon in detached screen:

autodetach on
startup_message off 

screen -t brmdoor 0 /root/brmdoor_libnfc/

Security considerations

Using SFTP for upload of status should be used with "internal-sftp" setting. This chroots the upload user's directory, doesn't allow script or code execution. You need to chown the directory to root and make it not writable by non-root users (requirement for internal-sftp). E.g. make brmdoor-web (used for sftp upload) user part of sftp group and have

Subsystem sftp internal-sftp

Match Group sftp
   ChrootDirectory %h
   ForceCommand internal-sftp
   AllowTcpForwarding no

For SFTP upload to work, target host needs to already to be in ~/.ssh/known_hosts when making connection, otherwise you'll get an exception. Simply connect via command-line sftp before running, check and accept the fingeprint beforehand.

Also, as noted before, reader should be behind door (mostly for vandalism). But the reader is not connected directly to open PIN, so it's not possible to simply apply voltage to it in this design even if you expose the reader. PIN to open door should be in no case accessible from outside the door (like having Raspi on the outside).

Known bugs (TODO)

  • IRC disconnect is sometimes detected late, e.g. when trying to send message that door was open. This causes the message to be lost, but the reconnect will kick in
  • Freenode loses packets (RST) seeming silent connection to be still alive when they are not.
  • Periodic PING could theoretically solve this, but when I tried I got kicked out, so also you need to find the right interval


You could use Android Host Card Emulation to emulate a Desfire - it actually just expects one application, D2760000850101.

See an example of HCE NDEF emulation.

You could just modify to write out the JSON into a file and then put the generated NDEF file into application so it will respond with it when


If you don't want to test it on Raspberry directly, it's possible to run on x86 with USB-based PN532, e.g. ACR 122U or ACR 122T.

Open/close switch can be simulated by ordinary file, for unlocker you can use do-nothing unlocker.Unlocker class. Note that there are subtle differences in PN532 handling which we also discovered only by experience, notably that SPI version cannot do interrupts while the USB version can. This has the effect that it causes 100% CPU use on SPI version, because it actively polls, while it works on USB version without 100% CPU usage. This issue has been fixed in the past so that the SPI version doesn't consume 100% CPU by just waiting for card.

You can’t perform that action at this time.