<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:fh="http://purl.org/syndication/history/1.0"><channel><title>JavaCard Wiki | Blog</title><description>Java Card 开发资源与文档：环境搭建、APDU、CAP、GlobalPlatform 与工具链。</description><link>https://www.javacardwiki.com/</link><language>en</language><fh:complete/><atom:link rel="self" href="https://www.javacardwiki.com/en/blog/rss.xml"/><item><title>Talking between Java Card applets with shareable interfaces</title><link>https://www.javacardwiki.com/en/blog/javacard-shareable-interface/</link><guid isPermaLink="true">https://www.javacardwiki.com/en/blog/javacard-shareable-interface/</guid><description>From firewall rules to a minimal working case — caller identity, array sharing, exceptions, transactions, lifecycle, and what one SIO call actually costs on the card.</description><pubDate>Fri, 23 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Java Card applets live behind a firewall. One applet cannot simply &lt;code dir=&quot;auto&quot;&gt;new&lt;/code&gt; another applet, grab a reference and call a method. Cross-applet calls go through the one door the JCRE provides: a shareable interface.&lt;/p&gt;
&lt;p&gt;That door has more rules than a method signature suggests. To pin them down I built a deliberately small case: a ticket-service applet owns a balance, a client applet buys tickets through the service, and an unauthorized applet tries the same call. Four classes in four packages, run on a contactless D600 card. I measured what could be measured; spec-only boundaries are called out separately.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;one-door-two-contexts&quot;&gt;One door, two contexts&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Java Card firewall isolates &lt;strong&gt;package contexts&lt;/strong&gt;: applets in different packages cannot touch each other’s objects. The exception is an interface that extends &lt;code dir=&quot;auto&quot;&gt;javacard.framework.Shareable&lt;/code&gt;. When a client invokes such an interface, the JCRE switches the active context to the server, runs the method there, and switches back on return.&lt;/p&gt;
&lt;p&gt;So a shareable interface is not an ordinary Java interface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the interface must extend &lt;code dir=&quot;auto&quot;&gt;Shareable&lt;/code&gt; (itself an empty marker interface);&lt;/li&gt;
&lt;li&gt;the server applet implements it and decides in &lt;code dir=&quot;auto&quot;&gt;getShareableInterfaceObject&lt;/code&gt; which object, if any, to hand out;&lt;/li&gt;
&lt;li&gt;the client obtains the reference from &lt;code dir=&quot;auto&quot;&gt;JCSystem.getAppletShareableInterfaceObject&lt;/code&gt;, casts it to the interface type and calls it;&lt;/li&gt;
&lt;li&gt;the method body runs in the &lt;strong&gt;server context&lt;/strong&gt;, while object references passed by the client are still subject to the firewall.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The demo has four packages, one of them a deliberate rogue:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;com.javacardwiki.sio.api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;TicketService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contract only; both sides import it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;com.javacardwiki.sio.server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;TicketServerApplet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Owns the balance, implements &lt;code dir=&quot;auto&quot;&gt;TicketService&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;com.javacardwiki.sio.client&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;TicketClientApplet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Buys tickets through the SIO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;com.javacardwiki.sio.rogue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;TicketRogueApplet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unauthorized caller, used to verify the refusal path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Putting the contract in its own package is not tidiness for its own sake. The client imports the contract, not the server implementation, so the server can be replaced without recompiling the client. Conversely, while any client imports the contract package, the JCRE refuses to delete that package.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;contract-server-client&quot;&gt;Contract, server, client&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The contract keeps the business methods and a few probes used only to observe behaviour:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; com.javacardwiki.sio.api;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; javacard.framework.Shareable;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TicketService&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shareable&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;addTickets&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getTickets&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// Demo probes only; a real service would not expose these&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;callerAidLength&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;callerAidByte&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fail&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The server grants access in &lt;code dir=&quot;auto&quot;&gt;getShareableInterfaceObject&lt;/code&gt;, which the JCRE calls when a client asks for the SIO. &lt;code dir=&quot;auto&quot;&gt;clientAID&lt;/code&gt; is supplied by the JCRE:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Shareable &lt;/span&gt;&lt;span&gt;getShareableInterfaceObject&lt;/span&gt;&lt;span&gt;(AID clientAID, &lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt; parameter) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;clientAID.&lt;/span&gt;&lt;span&gt;equals&lt;/span&gt;&lt;span&gt;(clientAppletAid)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;callerAidLength &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; clientAID.&lt;/span&gt;&lt;span&gt;getBytes&lt;/span&gt;&lt;span&gt;(callerAid, (&lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;addTickets&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt; count) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;requireCaller&lt;/span&gt;&lt;span&gt;();          &lt;/span&gt;&lt;span&gt;// re-check the caller on every invocation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tickets &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; count;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; tickets;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; AID &lt;/span&gt;&lt;span&gt;requireCaller&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AID caller &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JCSystem.&lt;/span&gt;&lt;span&gt;getPreviousContextAID&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (caller &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;caller.&lt;/span&gt;&lt;span&gt;equals&lt;/span&gt;&lt;span&gt;(clientAppletAid)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ISOException.&lt;/span&gt;&lt;span&gt;throwIt&lt;/span&gt;&lt;span&gt;(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; caller;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The client fetches once, stores the reference, and calls it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; TicketService &lt;/span&gt;&lt;span&gt;getService&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (service &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Shareable sio &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JCSystem.&lt;/span&gt;&lt;span&gt;getAppletShareableInterfaceObject&lt;/span&gt;&lt;span&gt;(serverAid, (&lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (sio &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ISOException.&lt;/span&gt;&lt;span&gt;throwIt&lt;/span&gt;&lt;span&gt;(ISO7816.SW_FILE_NOT_FOUND);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;service &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (TicketService)sio;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; service;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Storing the SIO in the &lt;code dir=&quot;auto&quot;&gt;service&lt;/code&gt; field is convenient, but it has a lifecycle consequence described below.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-showed-up-on-the-card&quot;&gt;What showed up on the card&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The test environment is the same card as the benchmark: the contactless reader &lt;code dir=&quot;auto&quot;&gt;ACS ACR1552 1S CL Reader PICC 0&lt;/code&gt;, CPLC &lt;code dir=&quot;auto&quot;&gt;D600&lt;/code&gt; (commonly JCOP 4.5 / J3R452; &lt;a href=&quot;https://www.javacardwiki.com/en/benchmarks/v3.0.5/cards/contactless-d600/&quot;&gt;card signals and performance data&lt;/a&gt;). The client commands returned:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;th&gt;Measured result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;clientAID&lt;/code&gt; passed to &lt;code dir=&quot;auto&quot;&gt;getShareableInterfaceObject&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;F053494F434C4901&lt;/code&gt; — 8 bytes, exactly the client applet AID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;JCSystem.getPreviousContextAID()&lt;/code&gt; inside a method&lt;/td&gt;
&lt;td&gt;the same 8-byte applet AID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The client’s own &lt;code dir=&quot;auto&quot;&gt;JCSystem.getAID()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;also the same AID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;getShareableInterfaceObject&lt;/code&gt; count after two commands&lt;/td&gt;
&lt;td&gt;still &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt;, so the client cached the SIO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unauthorized rogue applet asks for the SIO&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;0000&lt;/code&gt;, i.e. &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordinary &lt;code dir=&quot;auto&quot;&gt;byte[]&lt;/code&gt; passed to the server, server writes it&lt;/td&gt;
&lt;td&gt;server catches &lt;code dir=&quot;auto&quot;&gt;SecurityException&lt;/code&gt;, returns &lt;code dir=&quot;auto&quot;&gt;-1&lt;/code&gt;; the array is unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array created with &lt;code dir=&quot;auto&quot;&gt;JCSystem.makeGlobalArray&lt;/code&gt;, passed to the server&lt;/td&gt;
&lt;td&gt;works; all four bytes become &lt;code dir=&quot;auto&quot;&gt;A5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storing that global array in a client field&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;SecurityException&lt;/code&gt; on assignment (probe returned &lt;code dir=&quot;auto&quot;&gt;-6&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server calls &lt;code dir=&quot;auto&quot;&gt;ISOException.throwIt(0x6A80)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;client catches reason &lt;code dir=&quot;auto&quot;&gt;6A80&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client starts a transaction, SIO adds 100, client aborts&lt;/td&gt;
&lt;td&gt;balance is &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; before and after; the server’s write rolled back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;gp --delete&lt;/code&gt; on the server applet while the client caches the SIO&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;0x6985&lt;/code&gt;, deletion refused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;gp --delete&lt;/code&gt; on the contract package while the client imports it&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;0x6985&lt;/code&gt;, deletion refused&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The easiest wrong assumption is AID granularity. &lt;code dir=&quot;auto&quot;&gt;getPreviousContextAID()&lt;/code&gt; returns the AID of the applet instance that was active at the last context switch, not the package AID. The measured 8-byte value confirms it. &lt;strong&gt;Authenticate by instance AID; isolation is per package&lt;/strong&gt;: applets in one package share a context and do not need an SIO to reach each other, while cross-package calls should use an applet-AID allow-list in the server, not a package AID comparison.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;five-things-that-actually-trip-people-up&quot;&gt;Five things that actually trip people up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. Ordinary arrays do not cross; use a global array.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hand the server a client &lt;code dir=&quot;auto&quot;&gt;byte[]&lt;/code&gt; and the first write raises &lt;code dir=&quot;auto&quot;&gt;SecurityException&lt;/code&gt;. That is not an implementation quirk: an ordinary array belongs to the context that created it, and another context may only access its own arrays or a &lt;strong&gt;global array&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Java Card 3.0.5 lets an applet create one:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[] global &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[])JCSystem.&lt;/span&gt;&lt;span&gt;makeGlobalArray&lt;/span&gt;&lt;span&gt;(JCSystem.ARRAY_TYPE_BYTE, (&lt;/span&gt;&lt;span&gt;short&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The API arrived in 3.0.4, so 2.2.2 code cannot rely on it. More importantly, a global array is a &lt;strong&gt;temporary object&lt;/strong&gt;: it can be used for the current command but must not be assigned to a field, instance variable or array element. My first version stored the result of &lt;code dir=&quot;auto&quot;&gt;makeGlobalArray&lt;/code&gt; in a client field, and the command returned &lt;code dir=&quot;auto&quot;&gt;SecurityException&lt;/code&gt;; with a local variable, the server wrote &lt;code dir=&quot;auto&quot;&gt;A5 A5 A5 A5&lt;/code&gt; without complaint.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Do not use exceptions to refuse in &lt;code dir=&quot;auto&quot;&gt;getShareableInterfaceObject&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Return &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;. The specification requires the client to be unable to distinguish “server AID not found”, “server does not participate”, “caller not allowed” and “the server threw an exception”: the JCRE swallows exceptions from &lt;code dir=&quot;auto&quot;&gt;getShareableInterfaceObject&lt;/code&gt;, and the client sees only &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;. The client must handle &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;; it cannot assume the server is present.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Use &lt;code dir=&quot;auto&quot;&gt;ISOException&lt;/code&gt; for errors that cross the boundary.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ISOException.throwIt(sw)&lt;/code&gt; uses an exception instance owned by the JCRE, which is safe across contexts; the client caught reason &lt;code dir=&quot;auto&quot;&gt;6A80&lt;/code&gt;. An exception object created by the server can be caught by the client, but reading its fields or calling its methods may then hit the firewall. Put the status in the ISO exception or in a return value; do not ask the client to interpret an object owned by the server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Transactions cross the SIO; the server must not start its own.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After a client calls &lt;code dir=&quot;auto&quot;&gt;beginTransaction()&lt;/code&gt;, the server’s persistent writes during an SIO call belong to the same transaction. A client &lt;code dir=&quot;auto&quot;&gt;abortTransaction()&lt;/code&gt; rolls them back — measured: balance &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; before and after. Conversely, the server should not call &lt;code dir=&quot;auto&quot;&gt;beginTransaction()&lt;/code&gt; inside an SIO method: the transaction is a card-wide singleton, and starting one while the client already has one returns &lt;code dir=&quot;auto&quot;&gt;IN_PROGRESS&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Caching the SIO is convenient, but it blocks deletion of the server.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Storing the SIO in a client field really does avoid repeated &lt;code dir=&quot;auto&quot;&gt;getShareableInterfaceObject&lt;/code&gt; calls (the count stayed at &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; across two commands). The price is that the server applet owns an object referenced by a client instance, and the JCRE guarantees that deleting the server applet fails — &lt;code dir=&quot;auto&quot;&gt;gp --delete&lt;/code&gt; returned &lt;code dir=&quot;auto&quot;&gt;0x6985&lt;/code&gt;. For the same reason, deleting the contract package fails while a client imports it.&lt;/p&gt;
&lt;p&gt;If the server must be replaceable, do not keep the SIO forever: fetch it before use and check for &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;, or accept a maintenance order of “delete the client first, then the server”. That ordering is easy to get wrong during upgrades, especially after splitting contract, server and client into three packages.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-one-sio-call-costs&quot;&gt;What one SIO call costs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An SIO call is a context switch, not a local method call. To measure it, the client got two loop commands: one repeatedly calls &lt;code dir=&quot;auto&quot;&gt;getTickets()&lt;/code&gt; (which performs &lt;code dir=&quot;auto&quot;&gt;getPreviousContextAID&lt;/code&gt; plus an AID comparison on every call), the other repeatedly calls &lt;code dir=&quot;auto&quot;&gt;ping()&lt;/code&gt; (returns a constant, no authorization). The host timed the whole APDU at &lt;code dir=&quot;auto&quot;&gt;n = 0 / 1000 / 2000&lt;/code&gt; and fitted the slope, using the same differential method as the benchmark:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Call&lt;/th&gt;
&lt;th&gt;Per-call cost (fitted slope)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ping()&lt;/code&gt;, no authorization&lt;/td&gt;
&lt;td&gt;about 187 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;getTickets()&lt;/code&gt;, caller checked every time&lt;/td&gt;
&lt;td&gt;about 787 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed APDU cost (&lt;code dir=&quot;auto&quot;&gt;n = 0&lt;/code&gt; intercept)&lt;/td&gt;
&lt;td&gt;about 4.9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On this card, &lt;strong&gt;even the cheapest SIO call costs almost 0.2 ms&lt;/strong&gt;, and adding the per-call &lt;code dir=&quot;auto&quot;&gt;getPreviousContextAID&lt;/code&gt; and AID comparison pushes it close to 0.8 ms. Authorization dominates; an SIO is not a microsecond-level local method. If an operation would loop hundreds of times, move the loop into the server and batch it instead of bouncing between the two contexts.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;reproducing-it&quot;&gt;Reproducing it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Put the four classes into the four packages described above and convert them in order: contract, server, client, rogue. One tool detail: the contract package has no applet, so &lt;code dir=&quot;auto&quot;&gt;gp --install&lt;/code&gt; reports “CAP file has no applets”; use &lt;code dir=&quot;auto&quot;&gt;gp --load&lt;/code&gt; to load the package, then &lt;code dir=&quot;auto&quot;&gt;--install&lt;/code&gt; the applet packages. The load order is contract → server → client; uninstall is the reverse.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;reader&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--load&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;api.cap&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;reader&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;server.cap&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;reader&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;client.cap&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The client AID is &lt;code dir=&quot;auto&quot;&gt;F053494F434C4901&lt;/code&gt;; select it and send the APDUs from the table above. To test the rogue path, select &lt;code dir=&quot;auto&quot;&gt;F053494F524F4701&lt;/code&gt; instead and send &lt;code dir=&quot;auto&quot;&gt;8001000000&lt;/code&gt;; it should return &lt;code dir=&quot;auto&quot;&gt;0000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This case covers only a single channel and a single client. Two spec boundaries are still worth remembering: a non-&lt;code dir=&quot;auto&quot;&gt;MultiSelectable&lt;/code&gt; server already active on another logical channel makes &lt;code dir=&quot;auto&quot;&gt;getAppletShareableInterfaceObject&lt;/code&gt; throw &lt;code dir=&quot;auto&quot;&gt;SecurityException&lt;/code&gt;, and RMI (&lt;code dir=&quot;auto&quot;&gt;javacard.framework.service&lt;/code&gt;) is an optional component rather than something every card implements, so it is not required for cross-applet calls.&lt;/p&gt;
&lt;p&gt;One last caveat: these behaviours were measured on a Java Card 3.0.5 API, CPLC &lt;code dir=&quot;auto&quot;&gt;D600&lt;/code&gt; JCOP card; the card’s &lt;code dir=&quot;auto&quot;&gt;getVersion()&lt;/code&gt; reports only &lt;code dir=&quot;auto&quot;&gt;0x0300&lt;/code&gt;, which says nothing about the patch level. On another card or JCRE implementation, the array, exception and deletion error codes can differ. Run these probes before writing business code; it is more reliable than copying the documentation.&lt;/p&gt;</content:encoded><category>shareable-interface</category><category>Java Card 3.0.5</category><category>security</category></item><item><title>Benchmarking two Java Card 3.0.5 cards</title><link>https://www.javacardwiki.com/en/blog/javacard-305-benchmark/</link><guid isPermaLink="true">https://www.javacardwiki.com/en/blog/javacard-305-benchmark/</guid><description>Building a reproducible Java Card benchmark from scratch and running it on two JCOP cards — differential timing, phases bounded by transient RAM, per-algorithm probing, and how the same code differs between cards.</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It started with one contactless Java Card 3.0.5 card (CPLC &lt;code dir=&quot;auto&quot;&gt;D600&lt;/code&gt;, commonly JCOP 4.5 / J3R452). Once the benchmark worked, a second card of the same generation but a different model showed up (CPLC &lt;code dir=&quot;auto&quot;&gt;D321&lt;/code&gt;, commonly JCOP 4 / J3R180). Both ran the same applet version (v6) through the same host code on the same reader, so every difference comes from the card itself — which says more about “Java Card performance” than a single card ever could. The D321 data was later re-measured on another card of the same model, cleaned down to the ISD, the GlobalPlatform support package and the benchmark applet.&lt;/p&gt;
&lt;p&gt;The suite lives in the repository’s &lt;code dir=&quot;auto&quot;&gt;benchmark/&lt;/code&gt; directory: an on-card applet, a Java driver that keeps one PC/SC session open, and a Node harness that orchestrates and verifies. Cross-card comparison is on the &lt;a href=&quot;https://www.javacardwiki.com/en/benchmarks/v3.0.5/results/&quot;&gt;results page&lt;/a&gt;, each card’s full data (ATR, CPLC, memory, capabilities and performance tables) is linked from the &lt;a href=&quot;https://www.javacardwiki.com/en/benchmarks/v3.0.5/cards/&quot;&gt;card signals page&lt;/a&gt;, and the method is on the &lt;a href=&quot;https://www.javacardwiki.com/en/benchmarks/v3.0.5/&quot;&gt;methodology page&lt;/a&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;you-cannot-time-a-single-command&quot;&gt;You cannot time a single command&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Java Card classic has no clock, so the card cannot time itself, and the host can only time an APDU round trip. That fixed cost is not small on a contactless link: about 3.2–4.1 ms for an empty command, and about 9.8–10.7 ms when the applet returns data, because the card answers &lt;code dir=&quot;auto&quot;&gt;61xx&lt;/code&gt; and the host sends &lt;code dir=&quot;auto&quot;&gt;GET RESPONSE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So the applet executes the operation &lt;code dir=&quot;auto&quot;&gt;n&lt;/code&gt; times per command and the host fits:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;elapsed(n) = fixed cost + per-operation cost × n&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;n = 0&lt;/code&gt; point exists to measure the fixed cost directly; the slope is the per-operation time. Each test warms up, calibrates to about 80 ms, discards one sampling pass, then takes the median of three samples at four points and reports &lt;code dir=&quot;auto&quot;&gt;R²&lt;/code&gt; plus the coefficient of variation. You can see at a glance whether a number is trustworthy.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;six-things-that-got-in-the-way&quot;&gt;Six things that got in the way&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. No &lt;code dir=&quot;auto&quot;&gt;int&lt;/code&gt;.&lt;/strong&gt; The converter rejects &lt;code dir=&quot;auto&quot;&gt;int&lt;/code&gt; locals, &lt;code dir=&quot;auto&quot;&gt;int&lt;/code&gt; return types and even int-typed array indexes. Every loop counter has to be a &lt;code dir=&quot;auto&quot;&gt;short&lt;/code&gt;, which caps a command at 32767 iterations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. A card response costs two round trips.&lt;/strong&gt; When the applet returns data with &lt;code dir=&quot;auto&quot;&gt;sendBytesLong&lt;/code&gt;, the card first answers &lt;code dir=&quot;auto&quot;&gt;61xx&lt;/code&gt; and the host then sends &lt;code dir=&quot;auto&quot;&gt;GET RESPONSE&lt;/code&gt;. It cancels out in the slope but inflates the intercept past 10 ms; without differential timing it would end up inside the “crypto performance” number.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Transient RAM is the binding constraint.&lt;/strong&gt; With the applet’s own buffers allocated, the D600 card has only about 3.4 KB of transient memory left (1.7 KB before the factory applets were removed) and the D321 card about 2.8 KB, while every &lt;code dir=&quot;auto&quot;&gt;KeyPair&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Signature&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Cipher&lt;/code&gt; object takes a slice. Building the P-256 objects first and then generating an RSA key pair returns &lt;code dir=&quot;auto&quot;&gt;6B02&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;NO_TRANSIENT_SPACE&lt;/code&gt;). The suite therefore runs in four phases and sends &lt;code dir=&quot;auto&quot;&gt;RESET&lt;/code&gt; between them, where the applet drops all crypto objects and calls &lt;code dir=&quot;auto&quot;&gt;requestObjectDeletion()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Applets that were already on the card consume memory too.&lt;/strong&gt; When the first D321 card arrived it reported roughly 2.8 KB of free persistent memory and only 295 B of transient memory — not even enough for the benchmark buffers. After removing the leftover third-party applets and packages (with the user’s permission), the same card reported about 96 KB of persistent memory and 2.8 KB of transient memory. Some of the earlier “algorithm unavailable” conclusions were really memory starvation: clean first, then measure. The D321 data that ended up published came from another card of the same model, cleaned down to the platform packages before the applet was installed, so its baseline is clean.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Capability probing is itself misled by transient memory.&lt;/strong&gt; Probing dozens of algorithms inside one APDU keeps their transient objects alive until the command ends, so a tight card starts answering &lt;code dir=&quot;auto&quot;&gt;0x7F&lt;/code&gt; (unexpected exception) partway through, which looks like “unsupported”. The fix was a second applet command that probes exactly one algorithm per APDU (INS &lt;code dir=&quot;auto&quot;&gt;12&lt;/code&gt;), with the host retrying every anomalous bulk result. Only then did the real picture appear: on the D600 card HMAC gets as far as building the objects but every &lt;code dir=&quot;auto&quot;&gt;setKey&lt;/code&gt; returns &lt;code dir=&quot;auto&quot;&gt;ILLEGAL_VALUE&lt;/code&gt;; on the D321 card even &lt;code dir=&quot;auto&quot;&gt;Signature.getInstance&lt;/code&gt; returns &lt;code dir=&quot;auto&quot;&gt;NO_SUCH_ALGORITHM&lt;/code&gt;. Unusable either way, but at different points.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. &lt;code dir=&quot;auto&quot;&gt;Signature.verify()&lt;/code&gt; behaves strangely.&lt;/strong&gt; For a key pair generated in an earlier command, on-card verify returns false, while the same signature verifies on a PC with the card’s modulus; putting sign and verify in one command succeeds again. Modulus, exponent and signature bytes were all checked. Without a convincing explanation, the honest move is to record it: the standalone RSA and ECDSA verify timings would not be trustworthy, so they are marked “not reported”.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-cards-differ&quot;&gt;How the cards differ&lt;/h2&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;D600 card&lt;/th&gt;
&lt;th&gt;D321 card&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inferred model&lt;/td&gt;
&lt;td&gt;NXP P71D600 / JCOP 4.5 / J3R452&lt;/td&gt;
&lt;td&gt;NXP P71D321 / JCOP 4 / J3R180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ATR (reader-synthesised)&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;3B8A80010031C173C8400000900090&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;3B8C80015432433134504A34313890009A&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free persistent memory&lt;/td&gt;
&lt;td&gt;336,020 B&lt;/td&gt;
&lt;td&gt;155,632 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free transient memory (RESET)&lt;/td&gt;
&lt;td&gt;4,609 B&lt;/td&gt;
&lt;td&gt;3,974 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free transient memory (DESELECT)&lt;/td&gt;
&lt;td&gt;4,608 B&lt;/td&gt;
&lt;td&gt;3,968 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty command round trip&lt;/td&gt;
&lt;td&gt;4.1 ms&lt;/td&gt;
&lt;td&gt;3.2 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64 B response round trip&lt;/td&gt;
&lt;td&gt;15.6 ms&lt;/td&gt;
&lt;td&gt;6.4 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;getVersion()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;0x0300&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;0x0300&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Link speed and chip speed do not move together: the D321 card’s link is much faster (6.4 ms versus 15.6 ms for a 64 B response), yet its RSA signature is slower (105.8 ms versus 87.9 ms) and re-initialising AES costs much more (&lt;code dir=&quot;auto&quot;&gt;init + encrypt&lt;/code&gt; 8.0 ms versus 3.1 ms). “The newer card” would have been the wrong conclusion.&lt;/p&gt;
&lt;p&gt;Those three rows are read in the benchmark applet’s constructor, before it allocates its own test buffers. That step matters: &lt;code dir=&quot;auto&quot;&gt;getAvailableMemory&lt;/code&gt; reports what is left at the moment of the call, and allocating a few buffers or installing a couple more applets shifts the reading by kilobytes. The benchmark applet’s own fixed cost is 3,924 B persistent and 1,184 B in each transient view (two views of the same pool, not two pools to add), so while it runs the readings are lower by that much. It also explains an earlier hand measurement of about 3.8 KB: that was the transient baseline before the buffers were allocated (3,974 B on this D321 card), not what was left after the applet was installed. Align that premise before comparing cards — a single “free memory” number can support the opposite conclusion.&lt;/p&gt;
&lt;p&gt;One difference remains after cleaning: the D321 card’s clean persistent baseline is only 155.6 KB, about half of the D600 card’s 336.0 KB. That is not applet overhead but a different NVM configuration.&lt;/p&gt;
&lt;p&gt;The algorithm sets are almost identical: AES-128 only, no AES-192/256, no AES-GCM, no HMAC, and no default EC curve on either card (P-256 has to be imported, and &lt;code dir=&quot;auto&quot;&gt;setG&lt;/code&gt; needs the 65-byte &lt;code dir=&quot;auto&quot;&gt;04 || x || y&lt;/code&gt; form). Even the HMAC failure point differs, so “same Java Card version” does not imply “same algorithm set”.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;measured-numbers&quot;&gt;Measured numbers&lt;/h2&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;D600 card&lt;/th&gt;
&lt;th&gt;D321 card&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;16-bit addition (single op)&lt;/td&gt;
&lt;td&gt;34.0 µs&lt;/td&gt;
&lt;td&gt;35.6 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16-bit multiply / divide&lt;/td&gt;
&lt;td&gt;47.8 / 45.0 µs&lt;/td&gt;
&lt;td&gt;50.0 / 47.3 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;byte array read / write&lt;/td&gt;
&lt;td&gt;96.0 / 91.3 µs&lt;/td&gt;
&lt;td&gt;86.8 / 82.5 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AES-128 ECB encrypt (16 B)&lt;/td&gt;
&lt;td&gt;1.67 ms&lt;/td&gt;
&lt;td&gt;1.60 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AES-128 ECB &lt;code dir=&quot;auto&quot;&gt;init + encrypt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.1 ms&lt;/td&gt;
&lt;td&gt;8.0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DES / 3DES ECB (8 B)&lt;/td&gt;
&lt;td&gt;1.66 / 1.72 ms&lt;/td&gt;
&lt;td&gt;1.58 / 1.65 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256 (32 B / 1 KiB)&lt;/td&gt;
&lt;td&gt;2.38 / 26.0 ms&lt;/td&gt;
&lt;td&gt;2.38 / 26.0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;secure random (32 B)&lt;/td&gt;
&lt;td&gt;0.91 ms&lt;/td&gt;
&lt;td&gt;0.76 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECDSA P-256 sign&lt;/td&gt;
&lt;td&gt;46.6 ms&lt;/td&gt;
&lt;td&gt;53.7 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECDH P-256&lt;/td&gt;
&lt;td&gt;30.4 ms&lt;/td&gt;
&lt;td&gt;38.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-2048 sign / decrypt&lt;/td&gt;
&lt;td&gt;87.9 / 84.7 ms&lt;/td&gt;
&lt;td&gt;105.8 / 102.7 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-2048 encrypt&lt;/td&gt;
&lt;td&gt;6.6 ms&lt;/td&gt;
&lt;td&gt;8.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;key pair generation: P-256 / RSA-2048&lt;/td&gt;
&lt;td&gt;135.4 ms / 0.9–11.5 s&lt;/td&gt;
&lt;td&gt;139.0 ms / 5.2–12.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;arrayCopy&lt;/code&gt; 64 B (persistent destination)&lt;/td&gt;
&lt;td&gt;3.23 ms&lt;/td&gt;
&lt;td&gt;3.32 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;one transaction per write&lt;/td&gt;
&lt;td&gt;0.39 ms&lt;/td&gt;
&lt;td&gt;0.39 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allocate a 32-byte persistent array&lt;/td&gt;
&lt;td&gt;8.8 ms&lt;/td&gt;
&lt;td&gt;9.2 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Single-byte reads and writes (about 82–97 µs) are already close to the interpreter loop cost, so they are not EEPROM read/write speed; &lt;code dir=&quot;auto&quot;&gt;arrayCopy&lt;/code&gt; and the transaction rows say more about the medium. Persistent writes also occasionally enter a slow state, the same test going from 84 µs to 1.4 ms, related to wear-levelling — which is why the harness re-measures when fit quality drops. Two storage rows have &lt;code dir=&quot;auto&quot;&gt;R²&lt;/code&gt; below 0.99 and carry a &lt;code dir=&quot;auto&quot;&gt;†&lt;/code&gt;, meaning the slope is only a local estimate.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;same-version-re-run-and-cross-card-comparison&quot;&gt;Same-version re-run and cross-card comparison&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In the first published run the two cards used different applet versions (D600 had v4, D321 had v5). To confirm that this would not contaminate the comparison, the D600 card was measured again with v5: across the 37 valid timing rows the median per-operation deviation was 0.5%, transport round trips differed by under 1.5%, and only the high-variance RSA key generation and allocate+delete rows moved substantially. Both cards’ data now comes from v6; v6 only adds the pre-allocation memory baseline and leaves the timing path unchanged.&lt;/p&gt;
&lt;p&gt;The results page now has an interactive cross-card comparison: pick any card as the baseline, which is always shown in the first column, and the other cards show their relative timing change, with a green &lt;code dir=&quot;auto&quot;&gt;↓&lt;/code&gt; for faster and a red &lt;code dir=&quot;auto&quot;&gt;↑&lt;/code&gt; for slower. The data is read from &lt;code dir=&quot;auto&quot;&gt;latest.json&lt;/code&gt;, so future cards join the selector automatically.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-numbers-mean&quot;&gt;What the numbers mean&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;They are considerably slower than the “hardware-accelerated Java Card” impression: 1.6 ms per symmetric block, roughly 90–105 ms for an RSA signature, and no AES beyond 128 bits at all. On these cards over this link, crypto acceleration cannot be assumed; which algorithm is usable and how fast it is has to be measured.&lt;/p&gt;
&lt;p&gt;The link matters too: a contactless card is powered over RF and is inherently slower than a contact card. The same tool on a contact card and another reader could produce very different conclusions — which is exactly why it is built as a reproducible measurement method rather than a scorecard.&lt;/p&gt;
&lt;p&gt;The cross-card comparison is on the &lt;a href=&quot;https://www.javacardwiki.com/en/benchmarks/v3.0.5/results/&quot;&gt;results page&lt;/a&gt;, where any card can be selected as the baseline; each card’s full tables (fit quality, verification status and capability probes) and identity evidence are linked from the &lt;a href=&quot;https://www.javacardwiki.com/en/benchmarks/v3.0.5/cards/&quot;&gt;card signals page&lt;/a&gt;. To run it on your own card, the &lt;code dir=&quot;auto&quot;&gt;benchmark/&lt;/code&gt; directory has the build, install and one-command runner scripts.&lt;/p&gt;</content:encoded><category>benchmarks</category><category>Java Card 3.0.5</category></item></channel></rss>