Modern Java 17+ smart-card library built on one functional interface, BIBO: byte[] transceive(byte[]).
BIBO stands for "Bytes In, Bytes Out". Like
SCardTransmit,IsoDep.transceive().CardChannel.transmit()etc.
Decorators (timed logging, dumping, typed APDU access etc) stack as needed with then().
Use apdu4j-pcsc for desktop PC/SC readers. It pulls everything else in transitively. Use apdu4j-core where
javax.smartcardio is not available or needed: Android, headless tests, embedded.
Tip
Start with Quick start, see Modules for the full picture, or browse the wiki.
Select a reader and send an APDU in 3 lines:
Readers.select()
.withCard()
.accept(bibo -> {
var response = bibo.transmit(new CommandAPDU(0x00, 0xA4, 0x04, 0x00));
System.out.println("SW: %04X".formatted(response.getSW()));
});With logging and session recording:
Readers.select()
.withCard()
.log(System.out)
.dump(new FileOutputStream("session.dump"))
.run(bibo -> bibo.transmit(new CommandAPDU("00A4040007A0000002471001")));| Module | Java | Purpose |
|---|---|---|
apdu4j-core |
17 | BIBO, BIBOSA, APDU types, decorators, protocol handlers; no javax.smartcardio |
apdu4j-pcsc |
17 | PC/SC readers via javax.smartcardio, fluent Readers API, thread-safe sessions |
apdu4j-pcsc-sim |
17 | Synthesized javax.smartcardio provider over a BIBO |
apdu4j-remote |
17 | Remote transport adapters (JCSDK, VSmartCard, JSON) |
apdu4j-prefs |
17 | Typed Preference / Preferences |
apdu4j-apdulette |
21 | Lazy, composable APDU recipes |
apdu4j-tool |
21 | CLI tool |
BIBO - byte[] transceive(byte[]) throws BIBOException, extending AutoCloseable. Equivalent to Android's
IsoDep.transceive(), PC/SC SCardTransmit(), or javax.smartcardio CardChannel.transmit().
CommandAPDU - immutable ISO 7816-4 command record. Construct from fields, hex, or raw bytes, compatible with
javax.smartcardio:
new CommandAPDU(0x00, 0xA4, 0x04, 0x00, data) // fields
new CommandAPDU("00A4040007A0000002471001") // hex stringResponseAPDU - immutable response: getSW(), getSW1(), getSW2(), getData(), getSWBytes().
Decorators chain with then():
BIBO transport = ...; // any BIBO: PC/SC, Android NFC, mock, network
var bibo = transport
.then(GetResponseWrapper::wrap)
.then(RetryWithRightLengthWrapper::wrap)
.then(b -> LoggingBIBO.wrap(b, System.out))
.then(b -> DumpingBIBO.wrap(b, new FileOutputStream("session.dump")));
var response = bibo.transmit(new CommandAPDU("00A4040007A0000002471001"));apdu4j-core ships LoggingBIBO, DumpingBIBO, GetResponseWrapper, GetMoreDataWrapper (ETSI 9F),
RetryWithRightLengthWrapper (6C retry), LogicalChannelBIBO, and MockBIBO. BIBO is a drop-in for code using
javax.smartcardio APDU types: bibo.transmit(CommandAPDU) mirrors CardChannel.transmit(CommandAPDU). Change imports
and the rest works.
StatefulBIBO<S> threads typed state through atomic wrap-send-unwrap cycles. State implements AutoCloseable for key
zeroing on close. Building block for SCP02/SCP03 secure channels; see the javadoc.
BIBOSA is BIBO with a typed Preferences sidecar, composed like WSGI middleware. Each BIBOMiddleware layer can
wrap the transport and contribute typed preferences (effective block size, protocol version, security parameters) that
downstream layers and callers query after the stack is built:
var BLOCK_SIZE = Preference.of("blockSize", Integer.class, 255, true);
BIBOMiddleware secureChannel = stack -> {
var session = openSecureChannel(stack.bibo(), keys);
var prefs = stack.preferences().with(BLOCK_SIZE, session.maxPayload());
return new BIBOSA(session, prefs);
};
var stack = new BIBOSA(transport)
.then(GetResponseWrapper::wrap) // Function<BIBO,BIBO>: preferences pass through
.then(secureChannel); // BIBOMiddleware: adds preferences
var response = stack.transmit(cmd); // BIBOSA is a BIBO; transmit() is on BIBO
int blockSize = stack.preferences().get(BLOCK_SIZE);Simple wrappers (Function<BIBO, BIBO>) work unchanged because then() passes preferences through. Only layers that
contribute metadata implement BIBOMiddleware. Typed keys come from apdu4j-prefs (
Preference.of(name, type, default, readonly)).
Stub responses directly:
var mock = MockBIBO.with("00A4040007A0000002471001", "6F10A5049F6501FF9000")
.then("80CA9F7F00", "9000");
var response = mock.transmit(new CommandAPDU("00A4040007A0000002471001"));
assertEquals(0x9000, response.getSW());Replay from a dump file:
var mock = MockBIBO.fromDump(getClass().getResourceAsStream("/card.dump"));DumpingBIBO writes a hex dump that MockBIBO.fromDump() reads back:
# ATR: 3BFE1800008031FE4553434536302D43443038312D6E46A9
# PROTOCOL: T=1
00A4040000
# 24ms
6F108408A000000003000000A5049F6501FF9000
80500000084D080A4D1C5EBC92
# 70ms
00001248950019F738700103002421796B41BB3B7014659BFC8A54B2479000
Readers.select() does the right thing when there is only one reader. Pass a name fragment or a 1-indexed number to
disambiguate, or wire your own typed preference keys to environment variables (myapp.reader resolves to
MYAPP_READER) or CLI parameters:
var READER = Preference.of("myapp.reader", String.class, "", false);
var IGNORE = Preference.of("myapp.reader.ignore", String.class, "", false);
Readers.fromPreferences(Preferences.fromEnvironment(), READER, IGNORE)
.withCard()
.protocol("T=1")
.log(System.out)
.run(apdu -> {...});Other knobs (ignore, filter, exclusive, dump, whenReady, onCard) are also available.
For raw javax.smartcardio access, use .terminal() and .card().
Ongoing work on lazy, composable APDU interaction recipes. See apdulette/README.md.
Pull from Maven (see Usage from Java below) or build from source:
git clone https://github.com/martinpaljak/apdu4j
cd apdu4j
./mvnw package<dependency>
<groupId>com.github.martinpaljak</groupId>
<artifactId>apdu4j-pcsc</artifactId>
<version>LATEST</version>
</dependency>For headless / Android / test-only use (no javax.smartcardio), swap apdu4j-pcsc for apdu4j-core.
Add the repository to your pom.xml:
<repositories>
<repository>
<id>javacard-pro</id>
<url>https://mvn.javacard.pro/maven/</url>
</repository>
</repositories>- SCUBA (LGPL) - http://scuba.sourceforge.net/
- :| written in Java
- :( no command line utility
- :) has Provider-s for weird hardware
- jnasmartcardio (CC0) - https://github.com/jnasmartcardio/jnasmartcardio
- :| written in Java
- :) provides a "better" wrapper for system PC/SC service with JNA as a Provider
- :) used by apdu4j
- OpenCard Framework (OPEN CARD CONSORTIUM SOURCE LICENSE) - http://www.openscdp.org/ocf/
- :| written in Java
- :( really old (pre-2000, comparable to CT-API)
- :( no command line utility
- intarsys smartcard-io (BSD) - https://github.com/intarsys/smartcard-io
- :| written in Java
- :| similar to jnasmartcardio (alternative native Provider)
- OpenSC (opensc-tool, LGPL) - https://github.com/OpenSC/OpenSC
- :| written in C
- :| related to rest of OpenSC, but allows to send APDU-s from command line with
opensc-tool -s XX:XX:XX:XX
- Countless other apdu/script tools
- :| written in different languages
- :| use different input formats and script files
- :| just FYI
Extracted from GlobalPlatformPro and JavaCard work to keep low-level PC/SC code in one place. Also fills the gap of a Java command line tool for APDU-level reader access (previously only available in C).
- jnasmartcardio for PC/SC access (CC0 / public domain)
- picocli for parsing command line (Apache 2.0)
- Launch4j for generating .exe (BSD/MIT)
- MIT
- Annotated with SPDX for SBOM uses