Nitrokey 3: GPG keys generation
Previous post: Nitrokey 3
PGP vs GPG
PGP (Pretty Good Privacy) is the original encryption system started in 1991 by Phil Zimmermann. It has been standardized by several RFCs, mainly RFC 4880 and is known as the OpenPGP standard. GPG/GnuPG (GNU Privacy Guard) is a software suite implementing OpenPGP.
How to generate the keys
There are basically 2 ways to generate a GPG key pair for a smart card:
- directly on the smart card itself;
- on the computer then transfer it to the card.
The main issue with the first method is that the private part of the key never goes out of the smart card, so if you lose the smart card… it can be quite a pain (it depends on what you are using it for).
The main issue with the second method is that you have to generate the key on a non-compromised device and find out how to safely store a backup of the key.
With the Nitrokey 3, I’m not sure the on-device generation is already supported (looks like the official documentation has been updated and on-device key generation is supported) and I want to be able to back the keys up. So I went for the second method. Also, sometimes the embedded key generation has vulnerabilities (see ROCA vulnerability in some Yubikeys and other devices using a specific Infineon security chip… and of course I had one).
One way to start on a non-compromised computer is to boot on a read-only operating system like Tails (it’s not 100 % safe, but it’s way safer than doing it on your everyday system).
What type of key?
Keys have different capabilities:
- Certify (C), allows you to make modification to the keys;
- Sign (S);
- Encrypt (E);
- Authenticate (A).
The idea is usually to create a primary key with the minimum capability (which is “Certify”) then a sub-key for each other capability.
Which algorithm to use and which key size?
Given that the keys will be used on a Nitrokey 3, first we need to check what the Nitrokey supports:
At the moment, a 256 bits ECC key is marginally stronger than a 2048 bits RSA key but weaker than a 4096 bits RSA key. On the other hand, in a post-quantum world where the length of the key is more important, a 2048 bits RSA key is much stronger than a 256 bits ECC key. A 4096 bits RSA key would require a 8192 qubits computer (currently the biggest one has less than 500 of them) but IBM wants to create a 100 000 qubits by 2033… We will have to change a lot of cryptography in a lot of devices by then, given the speed of adoption of technologies, it looks like there will be a few years during which most people won’t have privacy against big governments and companies.
So let’s go with a 4096 bits RSA key.
Computers are supposed to be deterministic, and generating random data is non-deterministic by definition. So computers use different methods to generate pseudo-random numbers with various success (nowadays it’s generally quite good). Since cryptography needs random numbers (in current case to generate the keys), it’s better to ensure the entropy source’s quality.
We can measure that with
rngtest (installed with
sudo apt install rng-tools).
Here is how to generate 1 MB of random data and test it:
head -c 1000000 /dev/random | rngtest
default installation (debian 11):
rngtest: bits received from input: 8000000 rngtest: FIPS 140-2 successes: 398 rngtest: FIPS 140-2 failures: 1 rngtest: FIPS 140-2(2001-10-10) Monobit: 1 rngtest: FIPS 140-2(2001-10-10) Poker: 0 rngtest: FIPS 140-2(2001-10-10) Runs: 0 rngtest: FIPS 140-2(2001-10-10) Long run: 0 rngtest: FIPS 140-2(2001-10-10) Continuous run: 0 rngtest: input channel speed: (min=577.984; avg=2848.174; max=9536.743)Mibits/s rngtest: FIPS tests speed: (min=20.732; avg=66.440; max=88.303)Mibits/s rngtest: Program run time: 120743 microseconds
havegedinstalled (which is supposed to ensure a steady flow of good entropy), installed with
sudo apt install haveged:
rngtest: bits received from input: 8000000 rngtest: FIPS 140-2 successes: 398 rngtest: FIPS 140-2 failures: 1 rngtest: FIPS 140-2(2001-10-10) Monobit: 0 rngtest: FIPS 140-2(2001-10-10) Poker: 1 rngtest: FIPS 140-2(2001-10-10) Runs: 0 rngtest: FIPS 140-2(2001-10-10) Long run: 0 rngtest: FIPS 140-2(2001-10-10) Continuous run: 0 rngtest: input channel speed: (min=635.783; avg=2774.452; max=9536.743)Mibits/s rngtest: FIPS tests speed: (min=22.897; avg=66.908; max=88.714)Mibits/s rngtest: Program run time: 118325 microseconds
and using the Nitrokey 3’s built-in physical random number generator with
nitropy nk3 rng --length 1000000 | xxd -r -p | rngtest:
rngtest: bits received from input: 8000000 rngtest: FIPS 140-2 successes: 399 rngtest: FIPS 140-2 failures: 0 rngtest: FIPS 140-2(2001-10-10) Monobit: 0 rngtest: FIPS 140-2(2001-10-10) Poker: 0 rngtest: FIPS 140-2(2001-10-10) Runs: 0 rngtest: FIPS 140-2(2001-10-10) Long run: 0 rngtest: FIPS 140-2(2001-10-10) Continuous run: 0 rngtest: input channel speed: (min=10.999; avg=55.260; max=19531250.000)Kibits/s rngtest: FIPS tests speed: (min=101.997; avg=168.852; max=176.606)Mibits/s rngtest: Program run time: 142350215 microseconds
Results are a bit underwhelming, basically all the sources seems to be good enough. The Nitrokey being 1000 times slower (142 s vs 120 ms to generate 1 MB of random data). Although the Nitrokey might have better entropy quality. I ran the tests several times and never had any failure with the Nitrokey while the others had regularly 1 failure.
As seen above,
haveged is not relevant anymore but
haveged should make a difference if you are running linux kernel < 5.6.
If you want a true random number generator (TRNG), you can find USB dongle for that, like the Infinite Noise TNRG.
OpenPGP smart cards should all have a random number generator (generating random numbers is part of the OpenPGP specifications, but nothing about the quality and the quantity it can provide).
Also using the smartcard for feeding Linux’s
/dev/random seems to make the smart card not available for other operations, so can’t use it for both.
So, on a clean computer with a clean operating system (a debian based one here), we need to install GnuPG:
$ sudo apt install gnupg scdaemon
gpg commands are in “interactive” mode and some of them also have a fully scriptable equivalent.
To create the primary key with certification capability, you can either type
gpg --full-gen-key --expert and answer
gpg questions or directly type:
$ gpg --quick-gen-key 'Firstname Lastname <email@example.com>' rsa4096 cert 0
In the previous command,
rsa4096 defines the algorithm and the key size,
cert is for the capability and
0 is for the expiration date (0 meaning that the key doesn’t expire).
The expiration date is a bit meaningless on the primary key given that if the key is stolen, the thief can change the expiration date.
Expiration date is more interesting on sub-keys.
You can list your private keys and show their fingerprints with
gpg --list-secret-keys --fingerprint.
The fingerprint will be useful to pass as an argument to the other functions.
Usually you can just use the last 16 characters of the key’s fingerprint (hereafter I will use
0123456789ABCDEF as an example of the fingerprint).
You may want to add extra “identities” to that key (other email addresses):
$ gpg --quick-add-uid 0123456789ABCDEF 'Firstname Lastname <firstname.lastname@example.org>'
You may want to define one of those identifies has the default one, to do that the command is interactive:
$ gpg --edit-key 0123456789ABCDEF pub rsa4096/0123456789ABCDEF created: 2023-01-01 expires: never usage: C trust: ultimate validity: ultimate [ultimate] (1). Firstname Lastname <email@example.com> [ultimate] (2) Firstname Lastname <firstname.lastname@example.org> > uid 2 # select the second identity as the main identity > save
Now we create the 3 sub-keys, either in interactive mode with
gpg --edit-key 0123456789ABCDEF then the
addkey command, or in command line:
$ gpg --quick-addkey 0123456789ABCDEF rsa4096 sign 0 $ gpg --quick-addkey 0123456789ABCDEF rsa4096 encr 0 $ gpg --quick-addkey 0123456789ABCDEF rsa4096 auth 0
One last thing to do is to generate a revocation certificate. In case the key is compromised, it’s possible to mark it as revoked (and then publish the revoked public key so other people know that the key must not be used anymore).
$ gpg --output revoke.asc --gen-revoke 0123456789ABCDEF