When I was updating my GPG/OpenPGP key, I did some research on the internals of the keys. There appear to be very nice tools to explore the internals of a key. You can also manipulate this key in different aspects: use multiple passwords on a single key, remove part of a secret key for enhanced security; you can even move subkeys between master-keys.

Mandatory note: Before you try any of this on your own key, it would be wise to backup everything.

Another note: All output below is from a temporary key, don’t use the keyid for anything useful.

The parts of a key

As everyone can tell you, a GPG-key consists of 2 parts: a public and a private part. While this is true conceptually, it’s not true in practice: there are a lot of parameters that are in both parts. The gpgsplit and pgpdump utilities can show the actual content of a key:

$ gpg --export > key.pub
$ gpgsplit -v -p key.pub. key.pub
gpgsplit: writing `key.pub.000001-006.public_key'
gpgsplit: writing `key.pub.000002-013.user_id'
gpgsplit: writing `key.pub.000003-002.sig'
gpgsplit: writing `key.pub.000004-014.public_subkey'
gpgsplit: writing `key.pub.000005-002.sig'
$ gpg --export-secret-keys > key.sec
$ gpgsplit -v -p key.sec. key.sec
gpgsplit: writing `key.sec.000001-005.secret_key'
gpgsplit: writing `key.sec.000002-013.user_id'
gpgsplit: writing `key.sec.000003-002.sig'
gpgsplit: writing `key.sec.000004-007.secret_subkey'
gpgsplit: writing `key.sec.000005-002.sig'

GPGsplit splits up the key into its components:

  • 000001 : The master DSA key used for signing. Either the public or the secret variant
  • 000002 : The user_id. This packets contains the name, email and comment. This component is identical in the public and private key
  • 000003 : A signature that binds this identity to the master DSA key
  • 000004 : The ElGamal key used for en/decryption. Either the public or the secret variant
  • 000005 : A signature that binds this encryption key to the master DSA key

Combining multiple parts together is actually even easier: just cat them together!

We can dig even deeper with pgpdump. It shows the actual content of one (or more) parts. I tabulated the output to make it more easily comparable.

$ pgpdump key.pub
$ pgpdump key.sec
Old: Public Key Packet(tag 6)(418 bytes)
	Ver 4 - new
	Public key creation time - Mon Apr 13 11:19:26 CEST 2009
	Pub alg - DSA Digital Signature Algorithm(pub 17)
	DSA p(1024 bits) - ...
	DSA q(160 bits) - ...
	DSA g(1024 bits) - ...
	DSA y(1023 bits) - ...
Old: Secret Key Packet(tag 5)(481 bytes)
	Ver 4 - new
	Public key creation time - Mon Apr 13 11:19:26 CEST 2009
	Pub alg - DSA Digital Signature Algorithm(pub 17)
	DSA p(1024 bits) - ...
	DSA q(160 bits) - ...
	DSA g(1024 bits) - ...
	DSA y(1023 bits) - ...
	Sym alg - CAST5(sym 3)
	Iterated and salted string-to-key(s2k 3):
		Hash alg - SHA1(hash 2)
		Salt - 4f 6d 16 29 91 67 59 c6
		Count - 65536(coded count 96)
	IV - cd 71 8e c5 b8 d1 88 de
	Encrypted DSA x
	Encrypted SHA1 hash
Old: User ID Packet(tag 13)(31 bytes)
	User ID - ______ <______@______.__>
Old: User ID Packet(tag 13)(31 bytes)
	User ID - ______ <______@______.__>
Old: Signature Packet(tag 2)(96 bytes)
	Ver 4 - new
	Sig type - Positive certification of a User ID and Public Key packet(0x13).
	Pub alg - DSA Digital Signature Algorithm(pub 17)
	Hash alg - SHA1(hash 2)
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Mon Apr 13 11:19:26 CEST 2009
	Hashed Sub: key flags(sub 27)(1 bytes)
		Flag - This key may be used to certify other keys
		Flag - This key may be used to sign data
	Hashed Sub: preferred symmetric algorithms(sub 11)(5 bytes)
		Sym alg - AES with 256-bit key(sym 9)
		Sym alg - AES with 192-bit key(sym 8)
		Sym alg - AES with 128-bit key(sym 7)
		Sym alg - CAST5(sym 3)
		Sym alg - Triple-DES(sym 2)
	Hashed Sub: preferred hash algorithms(sub 21)(3 bytes)
		Hash alg - SHA1(hash 2)
		Hash alg - SHA256(hash 8)
		Hash alg - RIPEMD160(hash 3)
	Hashed Sub: preferred compression algorithms(sub 22)(3 bytes)
		Comp alg - ZLIB <RFC1950>(comp 2)
		Comp alg - BZip2(comp 3)
		Comp alg - ZIP <RFC1951>(comp 1)
	Hashed Sub: features(sub 30)(1 bytes)
		Flag - Modification detection (packets 18 and 19)
	Hashed Sub: key server preferences(sub 23)(1 bytes)
		Flag - No-modify
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0xF8FF38F1AE14BF43
	Hash left 2 bytes - ac 14
	DSA r(160 bits) - ...
	DSA s(159 bits) - ...
		-> hash(160 bits)
Old: Signature Packet(tag 2)(96 bytes)
	Ver 4 - new
	Sig type - Positive certification of a User ID and Public Key packet(0x13).
	Pub alg - DSA Digital Signature Algorithm(pub 17)
	Hash alg - SHA1(hash 2)
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Mon Apr 13 11:19:26 CEST 2009
	Hashed Sub: key flags(sub 27)(1 bytes)
		Flag - This key may be used to certify other keys
		Flag - This key may be used to sign data
	Hashed Sub: preferred symmetric algorithms(sub 11)(5 bytes)
		Sym alg - AES with 256-bit key(sym 9)
		Sym alg - AES with 192-bit key(sym 8)
		Sym alg - AES with 128-bit key(sym 7)
		Sym alg - CAST5(sym 3)
		Sym alg - Triple-DES(sym 2)
	Hashed Sub: preferred hash algorithms(sub 21)(3 bytes)
		Hash alg - SHA1(hash 2)
		Hash alg - SHA256(hash 8)
		Hash alg - RIPEMD160(hash 3)
	Hashed Sub: preferred compression algorithms(sub 22)(3 bytes)
		Comp alg - ZLIB <RFC1950>(comp 2)
		Comp alg - BZip2(comp 3)
		Comp alg - ZIP <RFC1951>(comp 1)
	Hashed Sub: features(sub 30)(1 bytes)
		Flag - Modification detection (packets 18 and 19)
	Hashed Sub: key server preferences(sub 23)(1 bytes)
		Flag - No-modify
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0xF8FF38F1AE14BF43
	Hash left 2 bytes - ac 14
	DSA r(160 bits) - ...
	DSA s(159 bits) - ...
		-> hash(160 bits)
Old: Public Subkey Packet(tag 14)(525 bytes)
	Ver 4 - new
	Public key creation time - Mon Apr 13 11:19:26 CEST 2009
	Pub alg - ElGamal Encrypt-Only(pub 16)
	ElGamal p(2048 bits) - ...
	ElGamal g(3 bits) - ...
	ElGamal y(2047 bits) - ...
Old: Secret Subkey Packet(tag 7)(611 bytes)
	Ver 4 - new
	Public key creation time - Mon Apr 13 11:19:26 CEST 2009
	Pub alg - ElGamal Encrypt-Only(pub 16)
	ElGamal p(2048 bits) - ...
	ElGamal g(3 bits) - ...
	ElGamal y(2047 bits) - ...
	Sym alg - CAST5(sym 3)
	Iterated and salted string-to-key(s2k 3):
		Hash alg - SHA1(hash 2)
		Salt - 4f 6d 16 29 91 67 59 c6
		Count - 65536(coded count 96)
	IV - 8c 06 ec cd 38 eb 70 20
	Encrypted ElGamal x
	Encrypted SHA1 hash
Old: Signature Packet(tag 2)(73 bytes)
	Ver 4 - new
	Sig type - Subkey Binding Signature(0x18).
	Pub alg - DSA Digital Signature Algorithm(pub 17)
	Hash alg - SHA1(hash 2)
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Mon Apr 13 11:19:26 CEST 2009
	Hashed Sub: key flags(sub 27)(1 bytes)
		Flag - This key may be used to encrypt communications
		Flag - This key may be used to encrypt storage
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0xF8FF38F1AE14BF43
	Hash left 2 bytes - 2e a7
	DSA r(159 bits) - ...
	DSA s(160 bits) - ...
		-> hash(160 bits)
Old: Signature Packet(tag 2)(73 bytes)
	Ver 4 - new
	Sig type - Subkey Binding Signature(0x18).
	Pub alg - DSA Digital Signature Algorithm(pub 17)
	Hash alg - SHA1(hash 2)
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Mon Apr 13 11:19:26 CEST 2009
	Hashed Sub: key flags(sub 27)(1 bytes)
		Flag - This key may be used to encrypt communications
		Flag - This key may be used to encrypt storage
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0xF8FF38F1AE14BF43
	Hash left 2 bytes - 2e a7
	DSA r(158 bits) - ...
	DSA s(159 bits) - ...
		-> hash(160 bits)

There are several things to discover within this output:

  • The secret part of the master DSA packet contains all the information of the public key, plus some extra fields. It is thus possible to convert a secret key into a public key. This is exactly what “gpgsplit –secret-to-public” does.
  • The public fields of the master DSA key (p, q, g and y) are plain-text; the secret field (x) is encrypted using a CAST5 encryption and a password (specified when creating the keypair)
  • The same is true for the ElGamal key: p, g and y are public and plain text; x is secret and encrypted.
  • Note that the secret parts of the DSA-key and the ElGamal key are seperately encrypted. I’ll explore this further in the following section
  • The signature that binds the user_id to the master DSA key also contains the users preferences: which encryption and hashing algorithms are supported and in what order are they prefered.

Passwords on the secret keys

As noted above, the two secret keys (signing and encryption) are encrypted seperately. This opens up some nice opportunities for extra security. There is no requirement that the passphrase for both keys are the same! This is originally documented here (local mirror).

The principle behind this is actually fairly easy:

  • Change the passphrase to passphrase1 using the “gpg –edit-key” command
  • Export the secret key: “gpg –export-secret-key > key.sec.pass1″
  • Change the passphrase to passphrase2 using the “gpg –edit-key” command
  • Export the secret key: “gpg –export-secret-key > key.sec.pass2″
  • Split both keys into their parts, cat together the relevant parts. You can choose between pass1 and pass2, but you need every part, in order! Only the “secret_key” and “secret_subkey” parts will differ; the other parts should be identical.
$ gpgsplit -p key.sec.pass1. -v key.sec.pass1
gpgsplit: writing `key.sec.pass1.000001-005.secret_key'
gpgsplit: writing `key.sec.pass1.000002-013.user_id'
gpgsplit: writing `key.sec.pass1.000003-002.sig'
gpgsplit: writing `key.sec.pass1.000004-007.secret_subkey'
gpgsplit: writing `key.sec.pass1.000005-002.sig'
$ gpgsplit -p key.sec.pass2. -v key.sec.pass2
gpgsplit: writing `key.sec.pass2.000001-005.secret_key'
gpgsplit: writing `key.sec.pass2.000002-013.user_id'
gpgsplit: writing `key.sec.pass2.000003-002.sig'
gpgsplit: writing `key.sec.pass2.000004-007.secret_subkey'
gpgsplit: writing `key.sec.pass2.000005-002.sig'
$
$ cat key.sec.pass1.000001-005.secret_key \
      key.sec.pass1.000002-013.user_id \
      key.sec.pass1.000003-002.sig \
      key.sec.pass2.000004-007.secret_subkey \
      key.sec.pass1.000005-002.sig \
   > key.sec.bothpass
  • Delete your secret key from the GPG keyring: “gpg –delete-secret-key keyid”
  • Import the multi-password key: “gpg –import key.sec.bothpass”
  • Optional but highly recommended: Test the new setup
$ date | gpg --clearsign   # should work with passphrase1
$ date | gpg --encrypt --armour --recipient keyid | gpg --decrypt   # should work with passphrase 2

Multiple subkeys

A GPG/PGP key actually has three purposes:

  • Sign/verify other keys
  • Sign/verify messages
  • Encrypt/decrypt messages

By default, GPG creates 2 keys: one for encrypting (by default ElGamal), one for signing (by default DSA). It does not differentiate between both signing purposes.

An important thing to note is that the userID is bound to the master DSA-key. This means that you cannot change your master DSA-key without loosing all your signatures on your userID(s). However, you are free to change your subkeys as often as you like. This is exactly the reason why I seperated the two signing-purposes into two different keys: The master DSA-key is still used to sign other keys, but I use a DSA-subkey to sign my messages. This way, I can change ElGamal and DSA-key every year without loosing all my signatures. This also has a security advantage: I don’t have to keep my master DSA secret key on my computer and can store it safely offline. The way to get this working is documented here (local mirror).

Basically it boils down to this: use “gpg –edit-key” to add a DSA subkey. GPG will sign messages with this subkey by default.

To get a bit extra security, you can remove the master DSA secret key from your computer. Make sure you have a backup: you will need this secret key to sign other keys and to renew your subkeys. Since a subkey cannot exist without its parent, you need some tricks to get this working:

$ gpg --export-secret-subkeys n > key.subsec

This exports only the subkeys and places them inside a dummy master key. Note the difference from above:

$ pgpdump key.subsec
Old: Secret Key Packet(tag 5)(426 bytes)
	Ver 4 - new
	Public key creation time - Mon Apr 13 11:19:26 CEST 2009
	Pub alg - DSA Digital Signature Algorithm(pub 17)
	DSA p(1024 bits) - ...
	DSA q(160 bits) - ...
	DSA g(1024 bits) - ...
	DSA y(1023 bits) - ...
	Sym alg - CAST5(sym 3)
	GnuPG string-to-key(s2k 101)
	Encrypted DSA x
	Encrypted SHA1 hash
<...>

To get this version into your keyring you need to delete your secret key and import the crippeled one:

$ gpg --list-secret-key
/tmp/gnupg/secring.gpg
-----------------------
sec   1024D/AE14BF43 2009-04-13
uid                  ______ <______@______.__>
ssb   2048g/56B47206 2009-04-13

$ gpg --delete-secret-key keyid
$ gpg --import key.subsec
$ gpg --list-secret-key
/tmp/gnupg/secring.gpg
-----------------------
sec#  1024D/AE14BF43 2009-04-13
uid                  ______ <______@______.__>
ssb   2048g/56B47206 2009-04-13
ssb   1024D/56FB4157 2009-04-13

The “sec#” output indicates that the key material is not present.

Note that you can combine this trick with the multiple-passwords trick mentioned above. I personally have a password for my master DSA key, and another password for my current DSA and ElGamal key.

Migrating keys

You can also migrate subkeys from one master key to another. This is not as simple as the multiple-passwords trick, since the signatures that bind the subkey to the master key need to be changed as well. You can even change a master DSA key into a DSA subkey! This page (local mirror) goes into the gory details.

One Comment

Leave a Reply