Hacking off-card backup to be on-disk key (was: Importing an off-card backup of the encryption key of a Nitrokey fails with "no user ID")

Peter Lebbing peter at digitalbrains.com
Tue Oct 31 14:25:00 CET 2017


Hi Ralf,

On 25/10/17 23:29, Ralf wrote:
> I was hoping for something simple and I think eventually this should be
> simple; nevertheless I would make use of such a workaround / would be
> thankful for such an example :)

I fiddled around with a test card. Prepare for a wall of text.

I created a test key on card:

--8<---------------cut here---------------start------------->8---
sec  rsa2048/A7C45205828E4D09
     created: 2017-10-31  expires: 2017-11-07  usage: SC
     card-no: 0005 0000106E
     trust: never         validity: ultimate
ssb  rsa2048/D614DCD256D4028C
     created: 2017-10-31  expires: 2017-11-07  usage: A
     card-no: 0005 0000106E
ssb  rsa2048/93104C8F5B4A4714
     created: 2017-10-31  expires: 2017-11-07  usage: E
     card-no: 0005 0000106E
--8<---------------cut here---------------end--------------->8---

We start with damage control. Always backup your .gnupg directory before 
doing risky stuff. I'm assuming the backup dir .gnupg~ does not already 
exist; otherwise, delete it first or choose a different name.

--8<---------------cut here---------------start------------->8---
$ cd
$ cp -a .gnupg/ .gnupg~
--8<---------------cut here---------------end--------------->8---

The following actions: export secret key, delete secret key from 
keyring, import secret key, show an interesting behaviour of my GnuPG 
2.1.18 related to card keys:

--8<---------------cut here---------------start------------->8---
$ gpg -o cardkey.gpg --export-secret-keys 0976A143384202C99E7C26EFA7C45205828E4D09
$ gpg --delete-secret-and-public-keys-keys 0976A143384202C99E7C26EFA7C45205828E4D09
[...]
$ gpg --import cardkey.gpg 
gpg: key A7C45205828E4D09: "Test Backup Hack" not changed
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key A7C45205828E4D09: secret key imported
gpg: Total number processed: 1
gpg:              unchanged: 1
gpg:       secret keys read: 1
--8<---------------cut here---------------end--------------->8---

It will not import the secret key stubs[1]. What it is obliquely saying 
is: don't import key stubs, just insert your smartcard and run --card-
status. Keep this in mind. It will come back in a different form.

Don't run --card-status at this time, by the way.

Now we start with packet surgery. Unlike a surgeon, we start by fully 
taking apart the body ;-).

--8<---------------cut here---------------start------------->8---
$ cd tmp/
$ gpgsplit ../cardkey.gpg 
$ ls
000001-005.secret_key  000004-007.secret_subkey  000007-002.sig
000002-013.user_id     000005-002.sig
000003-002.sig         000006-007.secret_subkey
--8<---------------cut here---------------end--------------->8---

I always have a "tmp" dir handy for throwaway stuff. Create an empty dir 
first if necessary.

An OpenPGP file always consists of a stream of packets. gpgslit just 
splits these packets over multiple files without changing anything else. 
We need to figure out which of the "secret_subkey" files is the secret 
key stub for the encryption key. First note that the encryption key is 
the key with ID 93104C8F5B4A4714, as can be told from the off-card 
backup file named sk_93104C8F5B4A4714.gpg.

--8<---------------cut here---------------start------------->8---
$ cat *secret*|gpg --list-packets 
# off=0 ctb=95 tag=5 hlen=3 plen=294
:secret key packet:
        version 4, algo 1, created 1509451630, expires 0
        pkey[0]: [2048 bits]
        pkey[1]: [17 bits]
        gnu-divert-to-card S2K, algo: 0, simple checksum, hash: 0
        serial-number:  d2 76 00 01 24 01 02 00 00 05 00 00 10 6e 00 00
        keyid: A7C45205828E4D09
# off=297 ctb=9d tag=7 hlen=3 plen=294
:secret sub key packet:
        version 4, algo 1, created 1509451630, expires 0
        pkey[0]: [2048 bits]
        pkey[1]: [17 bits]
        gnu-divert-to-card S2K, algo: 0, simple checksum, hash: 0
        serial-number:  d2 76 00 01 24 01 02 00 00 05 00 00 10 6e 00 00
        keyid: D614DCD256D4028C
# off=594 ctb=9d tag=7 hlen=3 plen=294
:secret sub key packet:
        version 4, algo 1, created 1509451630, expires 0
        pkey[0]: [2048 bits]
        pkey[1]: [17 bits]
        gnu-divert-to-card S2K, algo: 0, simple checksum, hash: 0
        serial-number:  d2 76 00 01 24 01 02 00 00 05 00 00 10 6e 00 00
        keyid: 93104C8F5B4A4714
--8<---------------cut here---------------end--------------->8---

These are the three packets with "secret" in their name, *in order*. The 
last of the three has the right key ID, so that means 
000006-007.secret_subkey contains the stub we want to replace.

Now let's take a look at that pesky sk_93104C8F5B4A4714.gpg that you 
were trying to import, with the off-card backup of the encryption key:

--8<---------------cut here---------------start------------->8---
$ gpg --list-packets ~/.gnupg/sk_93104C8F5B4A4714.gpg 
# off=0 ctb=95 tag=5 hlen=3 plen=966
:secret key packet:
        version 4, algo 1, created 1509451630, expires 0
        pkey[0]: [2048 bits]
        pkey[1]: [17 bits]
        iter+salt S2K, algo: 7, SHA1 protection, hash: 2, salt: 0B784F565A0849EB
        protect count: 28311552 (235)
        protect IV:  84 f1 35 77 5c f1 e2 70 b7 00 76 aa ef 85 86 6e
        skey[2]: [v4 protected]
        keyid: 93104C8F5B4A4714
--8<---------------cut here---------------end--------------->8---

This is a "secret key packet", but we want a "secret sub key packet"
(sic). Let's first copy this "secret key packet" in the correct place, 
and then grab your scalpel:

--8<---------------cut here---------------start------------->8---
$ cp ../.gnupg/sk_93104C8F5B4A4714.gpg 000006-007.secret_subkey 
$ dd if=000006-007.secret_subkey bs=1 count=1|hd
1+0 records in
1+0 records out
00000000  95                                                |.|
00000001
1 byte copied, 3.1911e-05 s, 31.3 kB/s
$ echo -ne '\x9d' | dd of=000006-007.secret_subkey bs=1 conv=notrunc
1+0 records in
1+0 records out
1 byte copied, 3.4443e-05 s, 29.0 kB/s
--8<---------------cut here---------------end--------------->8---

With the first "dd", we check if the file starts with the byte 0x95. If 
so, we should replace that byte by 0x9d. If it doesn't start with 0x95, 
we need to grab a copy of RFC 4880 and figure out what to do next, but I 
have no reason to believe GnuPG will have used something else than 0x95 
when it created your backup. It's just a safety check to be sure.

Flipping that single bit in the first byte is what changes the packet 
from a "secret key packet" to a "secret sub key packet".

So now we can reconstruct an OpenPGP file containing your private key, 
for just the encryption subkey. The other two keys (primary and 
authentication sub) are still key stubs pointing to the smartcard. 

--8<---------------cut here---------------start------------->8---
$ cat * >/../uncarded-key.gpg
$ cd ..
$ gpg --import uncarded-key.gpg
gpg: key A7C45205828E4D09: "Test Backup Hack" not changed
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key A7C45205828E4D09: secret key imported
gpg: Total number processed: 1
gpg:              unchanged: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1
--8<---------------cut here---------------end--------------->8---

Ah, here is our friend "run gpg --card-status" from before. It wasn't 
phrased very nicely the first time around, but this time it's even more 
confounding. Of the one key processed, one key is unchanged. Of the same 
one key, one is imported. Uhuh. What has happened is that it has not 
imported the primary key and the authentication subkey. But it *has* 
imported the encryption subkey. So it has both not changed and imported 
one key. In a universe with a different logic, this makes perfect sense. 
Note the double meaning of "secret key": we use it both to refer to 
individual keys like the primary and each subkey, as well as to refer to 
the whole of a primary key with its subkeys. It's what makes this even 
more confounding.

But, nonetheless, it works. We cannot use the primary or the auth key, 
at least until we insert the smartcard and run "gpg --card-status", but 
we *can* use the encryption subkey. It is now an on-disk key.

--8<---------------cut here---------------start------------->8---
$ echo test | gpg -r 0976A143384202C99E7C26EFA7C45205828E4D09 -o test.gpg -e
gpg: test backup hack: Verified 0 signatures and encrypted 0 messages.
File 'test.gpg' exists. Overwrite? (y/N) y
$ gpg -d test.gpg 
gpg: encrypted with 2048-bit RSA key, ID 93104C8F5B4A4714, created 2017-10-31
      "Test Backup Hack"
test
--8<---------------cut here---------------end--------------->8---

Workaround difficult enough for ya? :-)

If you screw up your installation, you should be able to put it back by 
deleting ~/.gnupg and copying back ~/.gnupg~ in its place. I haven't 
encountered any issues with gpg-agent staying alive throughout this 
swapping of the floor under its feet. Either it is watching the inode 
number of its homedir or something like that and notices it changed, or 
I simply haven't managed to trip it up yet. It might be prudent to kill 
the agent in between.

HTH,

Peter.

[1] "Secret key stub": a small bit of data that indicates on which 
smartcard the key is, rather than the actual secret key itself that 
would normally be there.

-- 
I use the GNU Privacy Guard (GnuPG) in combination with Enigmail.
You can send me encrypted mail if you want some privacy.
My key is available at <http://digitalbrains.com/2012/openpgp-key-peter>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: OpenPGP digital signature
URL: <https://lists.gnupg.org/pipermail/gnupg-users/attachments/20171031/ad059460/attachment-0001.sig>


More information about the Gnupg-users mailing list