Skip to content

Simple, extensible, header-only C++23 libnfc wrapper library, 13.56MHz looks more modern now.

License

Notifications You must be signed in to change notification settings

Redbeanw44602/nfcpp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NFC++

nfcpp is a libnfc wrapper library, which uses C++23 best practices.

Features

  • Header-only & Single-file. (Mostly)
  • Commonly used operations are encapsulated.
  • Exception-safety.

Get started

1. Deploy to your project.

First, I must explain why this library is mostly a header-only single-file library.

  • The library itself only contains nfc.hpp
  • You still need to handle libnfc compilation/linking. - I'm just a wrapper.
  • Because some header files are missing in the default libnfc installation, nfc-extra is required.
  • If you require crypto functionality for Mifare Classic cards, you will also need the crapto1 library.

Important

Please continue reading for details on enabling crapto1 features.

For non-buildsystem projects.

  • First, import libnfc in a reasonable way (details omitted).
  • Please git checkout to the latest release.
  • Move files under the src directory, at least requires nfc.hpp, nfc-extra/.

For non-xmake projects.

  • TODO I will provide a reasonable import method for cmake/meson as soon as possible.

For xmake projects.

  • TODO I wil submit this package to xrepo as soon as possible.
add_requires('nfcpp')

target('dummy')
    add_packages('nfcpp')
    ...

2. Include it from the souce.

You only need to...

#include <nfc.hpp>

// It is recommended to use `using` to avoid overly long names, most classes in
// nfcpp begin with `Nfc`, so name conflicts are generally not a problem.

using namespace nfcpp;
using namespace nfcpp::mifare;    // Provides `MifareCrypto1Cipher`.
using namespace nfcpp::util;      // Some utilities may contain common names. (!)

That's great! It will bring in everything you need.

3. Try a simple demo.

The complete source code for the demo is here.

To avoid making the README too complicated, I have omitted many common things (such as include/using namespace/select_passive_targets...) and only kept what I thought was valuable.

A key feature of nfcpp is its support for transforming bitstreams (or bytestreams) to be transmitted at compile time (or runtime). This is supported by the NfcTransmitData template and several useful transformers is included by default; you will learn more in the documentation.

The following shows two commonly used transformers that add CRC or calculate parity for the input data. To avoid excessively long names, it is recommended to using them.

template <std::size_t N>
using data_parity = NfcTransmitDataAutoParity<N>;

template <std::size_t N>
using data_crc_parity = NfcTransmitDataAutoCRCParity<N, NfcCRC::ISO14443A>;

Then, we will implement Mifare Classic Auth in pure software, with the card reader only transmitting the bitstream.

auto device    = context.open_device();
auto initiator = device->as_initiator();

// The sector number and key to be authenticate.
constexpr auto SECTOR_ADDR = 0x00;
constexpr auto KEY_A       = 0xFFFFFFFFFFFF;

// [R -> T] Request plaintext nonce. (Nt)
auto nt_r = initiator->transceive_bits(data_crc_parity(MC_AUTH_A, SECTOR_ADDR), buffer);

// [T -> R] Answer plaintext nonce.
//          Each initializes its Crypto1 state.
auto nt = nt_r.as_big_endian().expect<uint32_t>();

MifareCrypto1Cipher cipher(KEY_A);
cipher.word(nuid ^ nt, false);

// [R -> T] Send reader answer. (Ar)
std::array<uint8_t, 4> nr{};
std::array<uint8_t, 4> ntt{};

nt = prng_successor(nt, 32);
for (auto i : std::views::iota(0, 4)) {
    nt     = prng_successor(nt, 8);
    ntt[i] = nt & 0xff;
}

auto at_r = initiator->transceive_bits(
    data_parity(nr, ntt).with_encrypt(
        cipher,
        [](auto&& cipher) {
            cipher.crypt_feed(4);
            cipher.crypt(4);
        }
    ),
    buffer
);

// [T -> R] Tag answer and reader verification.
//          Authentication completed.
auto at = at_r.as_decrypted(cipher, false, false)
              .as_big_endian()
              .expect<uint32_t>();

nt = prng_successor(nt, 32);
if (at == nt) {
    std::println("Authentication completed.");
}

It's easy, isn't it? Only about 50 lines. Thanks to the power of nfcpp, you can enjoy the convenience without losing control over every bit, which is especially useful when writing RFID security tools.

Enable crypto1 feature

The crypto1 implementation used by nfcpp comes from proxmark3, which is licensed under GPLv3.

To enable this feature, build nfcpp with following option.

xmake f --crapto1=true
xmake build

This will automatically build the crapto1 library and enable all crypto1 features in nfc.hpp (via defines NFCPP_ENABLE_CRAPTO1=1).

Caution

With crapto1 enabled, this library is now subject to the GPLv3 license.

Documentation

TODO

License

MIT by default, GPLv3 if crapto1 is used.

About

Simple, extensible, header-only C++23 libnfc wrapper library, 13.56MHz looks more modern now.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published