keyformat.txt (wk 2001-12-18) ----------------------------- Some notes on the format of the secret keys used with gpg-agent. Location of keys ================ The secret keys[1] are stored on a per file basis in a directory below the ~/.gnupg home directory. This directory is named private-keys-v1.d and should have permissions 700. The secret keys are stored in files with a name matching the hexadecimal representation of the keygrip[2]. Unprotected Private Key Format ============================== The content of the file is an S-Expression like the ones used with Libgcrypt. Here is an example of an unprotected file: (private-key (rsa (n #00e0ce9..[some bytes not shown]..51#) (e #010001#) (d #046129F..[some bytes not shown]..81#) (p #00e861b..[some bytes not shown]..f1#) (q #00f7a7c..[some bytes not shown]..61#) (u #304559a..[some bytes not shown]..9b#) ) (created-at timestamp) (uri http://foo.bar x-foo:whatever_you_want) (comment whatever) ) "comment", "created-at" and "uri" are optional. "comment" is currently used to keep track of ssh key comments. "created-at" is used to keep track of the creation time stamp used with OpenPGP keys; it is optional but required for some operations to calculate the fingerprint of the key. This timestamp should be a string with the number of seconds since Epoch or an ISO time string (yyyymmddThhmmss). Actually this form should not be used for regular purposes and only accepted by gpg-agent with the configuration option: --allow-non-canonical-key-format. The regular way to represent the keys is in canonical representation[3]: (private-key (rsa (n #00e0ce9..[some bytes not shown]..51#) (e #010001#) (d #046129F..[some bytes not shown]..81#) (p #00e861b..[some bytes not shown]..f1#) (q #00f7a7c..[some bytes not shown]..61#) (u #304559a..[some bytes not shown]..9b#) ) (uri http://foo.bar x-foo:whatever_you_want) ) Protected Private Key Format ============================== A protected key is like this: (protected-private-key (rsa (n #00e0ce9..[some bytes not shown]..51#) (e #010001#) (protected mode (parms) encrypted_octet_string) (protected-at ) ) (uri http://foo.bar x-foo:whatever_you_want) (comment whatever) ) In this scheme the encrypted_octet_string is encrypted according to the algorithm described after the keyword protected; most protection algorithms need some parameters, which are given in a list before the encrypted_octet_string. The result of the decryption process is a list of the secret key parameters. The protected-at expression is optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000"). The only available protection mode for now is openpgp-s2k3-sha1-aes-cbc which describes an algorithm using using AES in CBC mode for encryption, SHA-1 for integrity protection and the String to Key algorithm 3 from OpenPGP (rfc2440). Example: (protected openpgp-s2k3-sha1-aes-cbc ((sha1 16byte_salt no_of_iterations) 16byte_iv) encrypted_octet_string ) The encrypted_octet string should yield this S-Exp (in canonical representation) after decryption: ( ( (d #046129F..[some bytes not shown]..81#) (p #00e861b..[some bytes not shown]..f1#) (q #00f7a7c..[some bytes not shown]..61#) (u #304559a..[some bytes not shown]..9b#) ) (hash sha1 #...[hashvalue]...#) ) For padding reasons, random bytes are appended to this list - they can easily be stripped by looking for the end of the list. The hash is calculated on the concatenation of the public key and secret key parameter lists: i.e it is required to hash the concatenation of these 6 canonical encoded lists for RSA, including the parenthesis, the algorithm keyword and (if used) the protected-at list. (rsa (n #00e0ce9..[some bytes not shown]..51#) (e #010001#) (d #046129F..[some bytes not shown]..81#) (p #00e861b..[some bytes not shown]..f1#) (q #00f7a7c..[some bytes not shown]..61#) (u #304559a..[some bytes not shown]..9b#) (protected-at "18950523T000000") ) After decryption the hash must be recalculated and compared against the stored one - If they don't match the integrity of the key is not given. Shadowed Private Key Format ============================ To keep track of keys stored on IC cards we use a third format for private kyes which are called shadow keys as they are only a reference to keys stored on a token: (shadowed-private-key (rsa (n #00e0ce9..[some bytes not shown]..51#) (e #010001#) (shadowed protocol (info)) ) (uri http://foo.bar x-foo:whatever_you_want) (comment whatever) ) The currently used protocol is "ti-v1" (token info version 1). The second list with the information has this layout: (card_serial_number id_string_of_key) More items may be added to the list. OpenPGP Private Key Transfer Format =================================== This format is used to transfer keys between gpg and gpg-agent. (openpgp-private-key (version V) (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT) (algo PUBKEYALGO) (skey CSUM c P1 c P2 c P3 ... e PN)) * V is the packet version number (3 or 4). * PUBKEYALGO is a Libgcrypt algo name * CSUM is the 16 bit checksum as defined by OpenPGP. * P1 .. PN are the parameters; the public parameters are never encrypted the secrect key parameters are encrypted if the "protection" list is given. To make this more explicit each parameter is preceded by a flag "_" for cleartext or "e" for encrypted text. * If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum" the old 16 bit checksum is used and if it is "none" no protection at all is used. * PROTALGO is a Libgcrypt style cipher algorithm name * IV is the initialization verctor. * S2KMODE is the value from RFC-4880. * S2KHASH is a a libgcrypt style hash algorithm identifier. * S2KSALT is the 8 byte salt * S2KCOUNT is the count value from RFC-4880. Notes: ====== [1] I usually use the terms private and secret key exchangeable but prefer the term secret key because it can be visually be better distinguished from the term public key. [2] The keygrip is a unique identifier for a key pair, it is independent of any protocol, so that the same key can be used with different protocols. PKCS-15 calls this a subjectKeyHash; it can be calculated using Libgcrypt's gcry_pk_get_keygrip (). [3] Even when canonical representation are required we will show the S-expression here in a more readable representation.