P3SKB Format

The idea

For the sake of paranoia, we introduced a new way to store PGP secret keys. We use scrypt passphrase stretching and encrypt secret keys with several ciphers. This might be especially relevant for users who store their encrypted private keys on the server, should our servers ever be hacked.

The Format

The payload is written as an Object with Binary data as follows:

  "body": {
    "pub": <Buffer c6 8d 04 52 a9 d6 99 01 04 00 cb ed ca 84 6e...>,
    "priv": {
      "data": <Buffer 1c 94 d7 de 00 00 00 03 b5 9b b0 52 54 30 8e...>,
      "encryption": 3
  "hash": {
    "value": <Buffer 60 f2 12 50 9f c8 30 8c 39 80 80 f0 03...>,
    "type": 8
  "tag": 513,
  "version": 1

The fields are as follows:

  • version: The version of this P3SKB block, now set to 1
  • tag: The tag value of this packet. Keybase-specific tags start at 512, as opposed to PGP-tag types which are all in the 0-30 range.
  • hash.type: The type of the hash used to hash the packet, using the standard PGP-hash numbering scheme (see RFC 4880 Section 9.4). Here, "8" corresponds to SHA-256
  • hash.body: The result of hashing this packet, written as binary data.
  • body.pub: The standard binary PGP representation of the keymaterial needed for this key, similar to what you would get by issuing `gpg --export <keyid>`.
  • body.priv.data: The TripleSec encryption of the standard binary PGP representation of the secret key material, in plaintext (no S2K encryption). Similar to what you would get by issuing `gpg --export- secret-key <keyid>` if your key wasn't locked with a passphrase.
  • body.private.encryption: The encryption type used here. "3" means TripleSec version 3.

The hash is taken over the whole packet, using the following technique:

  1. Set hash.body = Buffer.alloc(0)
  2. Run Msgpack.pack on the the resulting full JSON object.
  3. Compute the SHA-256 hash on the binary output of the previous step.

Once the hash is computed, the final P3SKB project is written out as:

  1. Compute Msgpack.pack over the full object, with the included hash from the previous step.
  2. Base64-encode the result.

Pssst, we're hiring.