If you have caught up with my blog for a long time, you may know what OpenPGP is. And you probably know what a YubiKey is, which can act as a smartcard.

Traditional OpenPGP stores your private key on your computer, generally in ~/.gnupg, which is nowhere near safe. Storing your private keys to a OpenPGP smartcard has following benefits.

  1. Safe, only people who physically has access to your smartcard and know your PIN can do operations i.e. signing, authenticating, decrypting.

  2. Ultra safe, once the keys are imported, it is not possible to extract them out.

  3. Still safe, all the key generation operation is done on the card.

Pay attention to the last line. If you still have no idea, it means the card must has the ability to do computing, more specifically cryptographic operations. That’s why YubiKeys are so expensive. It actually has a CPU (sort of) in it, it’s not just as simple as a piece of EEPROM.

Since YubiKeys are expensive, and if you are only interested in OpenPGP smartcards (well, we are going to use Java Card, where multiple applets can be loaded on it, like PIV and FIDO2, so…), you might want to DIY one.

Let’s do no more bullshit, let’s get started.

Prerequisites

  1. A Java Card. Currently I have NXP JCOP4 P71 J3R180 on my hand, and it works. Keep in mind that your card MUST support Java Card standard 3.0.4+

  2. Smart Card reader, must support PCSC/CCID/ISO7816, contactless reader here is not suggested since we are going to flash it. So, non-contactless reader.

That’s all we need. If you know the right way to buy them, the cost can be as low as 15 USD.

Software

  1. A Linux PC, Windows and macOS should also work fine, but this tutorial will be on Linux.

  2. JDK 8. JDK 8 is the only version that will work. 9 and 10 might also work, but any version above 11 will NOT work. When I was doing this for the first time, I used OpenJDK 17, and it just doesn’t succeed.

  3. JCDK 3.0.5, or any version your Java Card supports. (will do that later)

  4. GlobalPlatformPro, to write the applet to the Java Card. Can be downloaded here. You can clone the repository and compile it, or download existing releases.

  5. Apache ANT. Download at here, or install with sudo apt install ant

Getting Started

First, we need to set our environment variable.

export JAVA_HOME=<directory of JDK8, e.g. /home/frank/java/jdk8>

Next, clone SmartPGP repository.

git clone https://github.com/ANSSI-FR/SmartPGP && cd SmartPGP

Now we need to get JCDKs.

git submodule add https://github.com/martinpaljak/oracle_javacard_sdks sdks
git submodule init
git submodule update

Depending on what version you need, set environment variable.

export JC_HOME=./sdks/<version, e.g. jc305u4_kit>

If you need support for RSA 4096, you need to modify the code. (only possible for cards that have more RAM, like J3R180)

diff --git a/src/fr/anssi/smartpgp/Constants.java b/src/fr/anssi/smartpgp/Constants.java
index 421baa3..d1d8890 100644
--- a/src/fr/anssi/smartpgp/Constants.java
+++ b/src/fr/anssi/smartpgp/Constants.java
@@ -25,7 +25,7 @@ import javacard.framework.*;
 public final class Constants {

     protected static final short INTERNAL_BUFFER_MAX_LENGTH =
-        (short)0x500;
+        (short)0x730;

     protected static final short APDU_MAX_LENGTH = (short)0x400;
--- a/src/fr/anssi/smartpgp/Constants.java
+++ b/src/fr/anssi/smartpgp/Constants.java
@@ -219,7 +219,7 @@ public final class Constants {

     protected static final byte[] ALGORITHM_ATTRIBUTES_DEFAULT = {
         (byte)0x01, /* RSA */
-        (byte)0x08, (byte)0x00, /* 2048 bits modulus */
+        (byte)0x10, (byte)0x00, /* 4096 bits modulus */
         (byte)0x00, (byte)0x11, /* 65537 = 17 bits public exponent */
         (byte)0x03 /* crt form with modulus */
     };
(END)

Essentially just change two files.

Now you can compile the code.

ant

If everything goes well, you will see output like this.

$ ant
Buildfile: /home/frank/Documents/Code/SmartPGP/build.xml
      [get] Destination already exists (skipping): /home/frank/Documents/Code/SmartPGP/ant-javacard.jar

convert:
      [cap] INFO: using JavaCard 3.0.5 SDK in /home/frank/Documents/Code/SmartPGP/sdks/jc305u4_kit
      [cap] INFO: Setting package name to fr.anssi.smartpgp
      [cap] Building CAP with 1 applet from package fr.anssi.smartpgp (AID: D27600012401)
      [cap] fr.anssi.smartpgp.SmartPGPApplet D276000124010304AFAF000000000000
  [compile] Compiling files from /home/frank/Documents/Code/SmartPGP/src
  [compile] Compiling 13 source files to /tmp/jccpro3058849679416212175
  [compile] /home/frank/Documents/Code/SmartPGP/src/fr/anssi/smartpgp/Common.java:53: warning: [deprecation] ALG_SECURE_RANDOM in RandomData has been deprecated
  [compile]         random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
  [compile]                                                   ^
  [compile] /home/frank/Documents/Code/SmartPGP/src/fr/anssi/smartpgp/SmartPGPApplet.java:1468: warning: [deprecation] generateData(byte[],short,short) in RandomData has been deprecated
  [compile]             common.random.generateData(transients.buffer, (short)0, le);
  [compile]                          ^
  [compile] 2 warnings
  [convert] [ INFO: ] Converter [v3.0.5]
  [convert] [ INFO: ]     Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
  [convert]     
  [convert]     
  [convert] [ INFO: ] conversion completed with 0 errors and 0 warnings.
   [verify] Verification passed
      [cap] CAP saved to /home/frank/Documents/Code/SmartPGP/SmartPGPApplet.cap

BUILD SUCCESSFUL
Total time: 2 seconds

Assuming your GlobalPlatformPro locates at ../ (parent directory).

Load the applet to your card using the following command.

java -jar ../gp.jar -install SmartPGPApplet.cap

If everything goes well, you will see CAP Loaded.

To see the details of the card, use the following command.

java -jar ../gp.jar -list

Last but not least, lock the card.

openssl rand --hex 16 | awk '{print toupper($0)}' > .card_secret
cat .card_secret | xargs java -jar ../gp.jar -lock # Specify your card reader with -r if necessary.

FROM THE POINT YOU LOCKED YOUR CARD, ALL COMMANDS MUST TAKE THE ARGUMENT -key {YOUR_SECRET} OR YOU MAY BRICK YOUR CARD!

If you need to unlock your card.

cat .card_secret | xargs java -jar ../gp.jar -unlock -key

CAUTION (MUST READ)

There is a known bug that haven’t been solved at the time of writing is that you can’t import keys to the card. You can ONLY generate the key on the card. And you MUST use key-attr when gpg --edit-card to change the preferences of the key generation. Unfortunately Curve 25519 and Brainpool is not available.

In my case, only RSA 1024~4096 and NIST P-384 is available. But I mean, hey it works.

You MUST choose NO when GnuPG is asking you whether to backup the key. This step is essentially generating the keys on the computer and then import them to the card, which is still bugged and can’t be done. You will fail.

The disadvantage of this solution is very obvious, is that you have NO WAY to backup your private keys. So… If lost, it’s lost. If broken, it’s broken.

Conclusion

Hoo! Now you have a fully-functional OpenPGP Smart Card that can do signing, decrypting and authenticating.

References

基于SmartPGP低成本DIY OpenPGP卡(YubiKey替代品) | 飞翔的种子, archived in Frankapository on January 28, 2024.

DIY Smartcard Authentication with SmartPGP, archived in Frankapository on January 28, 2024.