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