123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- \A{ppk} PPK file format
- This appendix documents the file format used by PuTTY to store private
- keys.
- In this appendix, binary data structures are described using data type
- representations such as \cq{uint32}, \cq{string} and \cq{mpint} as
- used in the SSH protocol standards themselves. These are defined
- authoritatively by
- \W{https://www.rfc-editor.org/rfc/rfc4251#section-5}{RFC 4251 section 5},
- \q{Data Type Representations Used in the SSH Protocols}.
- \H{ppk-overview} Overview
- A PPK file stores a private key, and the corresponding public key.
- Both are contained in the same file.
- The file format can be completely unencrypted, or it can encrypt the
- private key. The \e{public} key is stored in cleartext in both cases.
- (This enables PuTTY to send the public key to an SSH server to see
- whether it will accept it, and not bother prompting for the passphrase
- unless the server says yes.)
- When the key file is encrypted, the encryption key is derived from a
- passphrase. An encrypted PPK file is also tamper-proofed using a MAC
- (authentication code), also derived from the same passphrase. The MAC
- protects the encrypted private key data, but it also covers the
- cleartext parts of the file. So you can't edit the public half of the
- key without invalidating the MAC and causing the key file as a whole
- to become useless.
- This MAC protects the key file against active cryptographic attacks in
- which the public half of a key pair is modified in a controlled way
- that allows an attacker to deduce information about the private half
- from the resulting corrupted signatures. Any attempt to do that to a
- PPK file should be reliably caught by the MAC failing to validate.
- (Such an attack would only be useful if the key file was stored in a
- location where the attacker could modify it without also having full
- access to the process that you type passphrases into. But that's not
- impossible; for example, if your home directory was on a network file
- server, then the file server's administrator could access the key file
- but not processes on the client machine.)
- The MAC also covers the \e{comment} on the key. This stops an attacker
- from swapping keys with each other and editing the comments to
- disguise the fact. As a consequence, PuTTYgen cannot edit the comment
- on a key unless you decrypt the key with your passphrase first.
- (The circumstances in which \e{that} attack would be useful are even
- more restricted. One example might be that the different keys trigger
- specific actions on the server you're connecting to and one of those
- actions is more useful to the attacker than the other. But once you
- have a MAC at all, it's no extra effort to make it cover as much as
- possible, and usually sensible.)
- \H{ppk-outer} Outer layer
- The outer layer of a PPK file is text-based. The PuTTY tools will
- always use LF line termination when writing PPK files, but will
- tolerate CR+LF and CR-only on input.
- The first few lines identify it as a PPK, and give some initial data
- about what's stored in it and how. They look like this:
- \c PuTTY-User-Key-File-version: algorithm-name
- \e bbbbbbb bbbbbbbbbbbbbb
- \c Encryption: encryption-type
- \e bbbbbbbbbbbbbbb
- \c Comment: key-comment-string
- \e bbbbbbbbbbbbbbbbbb
- \s{version} is a decimal number giving the version number of the file
- format itself. The current file format version is 3.
- \s{algorithm-name} is the SSH protocol identifier for the public key
- algorithm that this key is used for (such as \cq{ssh-dss} or
- \cq{ecdsa-sha2-nistp384}).
- \s{encryption-type} indicates whether this key is stored encrypted,
- and if so, by what method. Currently the only supported encryption
- types are \cq{aes256-cbc} and \cq{none}.
- \s{key-comment-string} is a free text field giving the comment. This
- can contain any byte values other than 13 and 10 (CR and LF).
- The next part of the file gives the public key. This is stored
- unencrypted but base64-encoded
- (\W{https://www.rfc-editor.org/rfc/rfc4648}{RFC 4648}), and is preceded
- by a header line saying how many lines of base64 data are shown,
- looking like this:
- \c Public-Lines: number-of-lines
- \e bbbbbbbbbbbbbbb
- \c that many lines of base64 data
- \e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
- The base64-encoded data in this blob is formatted in exactly the same
- way as an SSH public key sent over the wire in the SSH protocol
- itself. That is also the same format as the base64 data stored in
- OpenSSH's \c{authorized_keys} file, except that in a PPK file the
- base64 data is split across multiple lines. But if you remove the
- newlines from the middle of this section, the resulting base64 blob is
- in the right format to go in an \c{authorized_keys} line.
- If the key is encrypted (i.e. \s{encryption-type} is not \cq{none}),
- then the next thing that appears is a sequence of lines specifying how
- the keys for encrypting the file are to be derived from the
- passphrase:
- \c Key-Derivation: argon2-flavour
- \e bbbbbbbbbbbbbb
- \c Argon2-Memory: decimal-integer
- \e bbbbbbbbbbbbbbb
- \c Argon2-Passes: decimal-integer
- \e bbbbbbbbbbbbbbb
- \c Argon2-Parallelism: decimal-integer
- \e bbbbbbbbbbbbbbb
- \c Argon2-Salt: hex-string
- \e bbbbbbbbbb
- \s{argon2-flavour} is one of the identifiers \cq{Argon2d},
- \cq{Argon2i} or \cq{Argon2id}, all describing variants of the Argon2
- password-hashing function.
- The three integer values are used as parameters for Argon2, which
- allows you to configure the amount of memory used (in Kbyte), the number
- of passes of the algorithm to run (to tune its running time), and the
- degree of parallelism required by the hash function. The salt is
- decoded into a sequence of binary bytes and used as an additional
- input to Argon2. (It is chosen randomly when the key file is written,
- so that a guessing attack can't be mounted in parallel against
- multiple key files.)
- The next part of the file gives the private key. This is
- base64-encoded in the same way:
- \c Private-Lines: number-of-lines
- \e bbbbbbbbbbbbbbb
- \c that many lines of base64 data
- \e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
- The binary data represented in this base64 blob may be encrypted,
- depending on the \e{encryption-type} field in the key file header
- shown above:
- \b If \s{encryption-type} is \cq{none}, then this data is stored in
- plain text.
- \b If \s{encryption-type} is \cq{aes256-cbc}, then this data is
- encrypted using AES, with a 256-bit key length, in the CBC cipher
- mode. The key and initialisation vector are derived from the
- passphrase: see \k{ppk-keys}.
- \lcont{
- In order to encrypt the private key data with AES, it must be a
- multiple of 16 bytes (the AES cipher block length). This is achieved
- by appending random padding to the data before encrypting it. When
- decoding it after decryption, the random data can be ignored: the
- internal structure of the data is enough to tell you when you've
- reached the end of the meaningful part.
- }
- Unlike public keys, the binary encoding of private keys is not
- specified at all in the SSH standards. See \k{ppk-privkeys} for
- details of the private key format for each key type supported by
- PuTTY.
- The final thing in the key file is the MAC:
- \c Private-MAC: hex-mac-data
- \e bbbbbbbbbbbb
- \s{hex-mac-data} is a hexadecimal-encoded value, 64 digits long (i.e.
- 32 bytes), generated using the HMAC-SHA-256 algorithm with the
- following binary data as input:
- \b \cw{string}: the \s{algorithm-name} header field.
- \b \cw{string}: the \s{encryption-type} header field.
- \b \cw{string}: the \s{key-comment-string} header field.
- \b \cw{string}: the binary public key data, as decoded from the base64
- lines after the \cq{Public-Lines} header.
- \b \cw{string}: the plaintext of the binary private key data, as
- decoded from the base64 lines after the \cq{Private-Lines} header. If
- that data was stored encrypted, then the decrypted version of it is
- used in this MAC preimage, \e{including} the random padding mentioned
- above.
- The MAC key is derived from the passphrase: see \k{ppk-keys}.
- \H{ppk-privkeys} Private key encodings
- This section describes the private key format for each key type
- supported by PuTTY.
- Because the PPK format also contains the public key (and both public
- and private key are protected by the same MAC to ensure they can't be
- made inconsistent), there is no need for the private key section of
- the file to repeat data from the public section. So some of these
- formats are very short.
- In all cases, a decoding application can begin reading from the start
- of the decrypted private key data, and know when it has read all that
- it needs. This allows random padding after the meaningful data to be
- safely ignored.
- \S{ppk-privkey-rsa} RSA
- RSA keys are stored using an \s{algorithm-name} of \cq{ssh-rsa}. (Keys
- stored like this are also used by the updated RSA signature schemes
- that use hashes other than SHA-1.)
- The public key data has already provided the key modulus and the
- public encoding exponent. The private data stores:
- \b \cw{mpint}: the private decoding exponent of the key.
- \b \cw{mpint}: one prime factor \e{p} of the key.
- \b \cw{mpint}: the other prime factor \e{q} of the key. (RSA keys
- stored in this format are expected to have exactly two prime factors.)
- \b \cw{mpint}: the multiplicative inverse of \e{q} modulo \e{p}.
- \S{ppk-privkey-dsa} DSA
- DSA keys are stored using an \s{algorithm-name} of \cq{ssh-dss}.
- The public key data has already provided the key parameters (the large
- prime \e{p}, the small prime \e{q} and the group generator \e{g}), and
- the public key \e{y}. The private key stores:
- \b \cw{mpint}: the private key \e{x}, which is the discrete logarithm
- of \e{y} in the group generated by \e{g} mod \e{p}.
- \S{ppk-privkey-ecdsa} NIST elliptic-curve keys
- \i{NIST} elliptic-curve keys are stored using one of the following
- \s{algorithm-name} values, each corresponding to a different elliptic
- curve and key size:
- \b \cq{ecdsa-sha2-nistp256}
- \b \cq{ecdsa-sha2-nistp384}
- \b \cq{ecdsa-sha2-nistp521}
- The public key data has already provided the public elliptic curve
- point. The private key stores:
- \b \cw{mpint}: the private exponent, which is the discrete log of the
- public point.
- \S{ppk-privkey-eddsa} EdDSA elliptic-curve keys (Ed25519 and Ed448)
- EdDSA elliptic-curve keys are stored using one of the following
- \s{algorithm-name} values, each corresponding to a different elliptic
- curve and key size:
- \b \cq{ssh-ed25519}
- \b \cq{ssh-ed448}
- The public key data has already provided the public elliptic curve
- point. The private key stores:
- \b \cw{mpint}: the private exponent, which is the discrete log of the
- public point.
- \H{ppk-keys} Key derivation
- When a key file is encrypted, there are three pieces of key material
- that need to be computed from the passphrase:
- \b the key for the symmetric cipher used to encrypt the private key
- \b the initialisation vector for that cipher encryption
- \b the key for the MAC.
- If \s{encryption-type} is \cq{aes256-cbc}, then the symmetric cipher
- key is 32 bytes long, and the initialisation vector is 16 bytes (one
- cipher block). The length of the MAC key is also chosen to be 32
- bytes.
- If \s{encryption-type} is \cq{none}, then all three of these pieces of
- data have zero length. (The MAC is still generated and checked in the
- key file format, but it has a zero-length key.)
- If the amount of key material required is not zero, then the
- passphrase is fed to the Argon2 key derivation function, in whichever
- mode is described in the \cq{Key-Derivation} header in the key file,
- with parameters derived from the various
- \q{\cw{Argon2-}\e{Parameter}\cw{:}} headers.
- (If the key is unencrypted, then all those headers are omitted, and
- Argon2 is not run at all.)
- Argon2 takes two extra string inputs in addition to the passphrase and
- the salt: a secret key, and some \q{associated data}. In PPK's use of
- Argon2, these are both set to the empty string.
- The \q{tag length} parameter to Argon2 (i.e. the amount of data it is
- asked to output) is set to the sum of the lengths of all of the data
- items required, i.e. (cipher key length + IV length + MAC key length).
- The output data is interpreted as the concatenation of the cipher key,
- the IV and the MAC key, in that order.
- So, for \cq{aes256-cbc}, the tag length will be 32+16+32\_=\_80 bytes;
- of the 80 bytes of output data, the first 32 bytes are used as the
- 256-bit AES key, the next 16 as the CBC IV, and the final 32 bytes as
- the HMAC-SHA-256 key.
- \H{ppk-old} Older versions of the PPK format
- \S{ppk-v2} Version 2
- PPK version 2 was used by PuTTY 0.52 to 0.74 inclusive.
- In PPK version 2, the MAC algorithm used was HMAC-SHA-1 (so the
- \cw{Private-MAC} line contained only 40 hex digits).
- The \cq{Key-Derivation:} header and all the
- \q{\cw{Argon2-}\e{Parameter}\cw{:}} headers were absent. Instead of
- using Argon2, the key material for encrypting the private blob was
- derived from the passphrase in a totally different way, as follows.
- The cipher key for \cq{aes256-cbc} was constructed by generating two
- SHA-1 hashes, concatenating them, and taking the first 32 bytes of the
- result. (So you'd get all 20 bytes of the first hash output, and the
- first 12 of the second). Each hash preimage was as follows:
- \b \cw{uint32}: a sequence number. This is 0 in the first hash, and 1
- in the second. (The idea was to extend this mechanism to further
- hashes by continuing to increment the sequence number, if future
- changes required even longer keys.)
- \b the passphrase, without any prefix length field.
- In PPK v2, the CBC initialisation vector was all zeroes.
- The MAC key was 20 bytes long, and was a single SHA-1 hash of the
- following data:
- \b the fixed string \cq{putty-private-key-file-mac-key}, without any
- prefix length field.
- \b the passphrase, without any prefix length field. (If the key is
- stored unencrypted, the passphrase was taken to be the empty string
- for these purposes.)
- \S{ppk-v1} Version 1
- PPK version 1 was a badly designed format, only used during initial
- development, and not recommended for production use.
- PPK version 1 was never used by a released version of PuTTY. It was
- only emitted by some early development snapshots between version 0.51
- (which did not support SSH-2 public keys at all) and 0.52 (which
- already used version 2 of this file format). I \e{hope} there are no
- PPK v1 files in use anywhere. But just in case, the old badly designed
- format is documented here anyway.
- In PPK version 1, the input to the MAC does not include any of the
- header fields or the public key. It is simply the private key data
- (still in plaintext and including random padding), all by itself
- (without a wrapping \cw{string}).
- PPK version 1 keys must therefore be rigorously validated after
- loading, to ensure that the public and private parts of the key were
- consistent with each other.
- PPK version 1 only supported the RSA and DSA key types. For RSA, this
- validation can be done using only the provided data (since the private
- key blob contains enough information to reconstruct the public values
- anyway). But for DSA, that isn't quite enough.
- Hence, PPK version 1 DSA keys extended the private data so that
- immediately after \e{x} was stored an extra value:
- \b \cw{string}: a SHA-1 hash of the public key data, whose preimage
- consists of
- \lcont{
- \b \cw{string}: the large prime \e{p}
- \b \cw{string}: the small prime \e{q}
- \b \cw{string}: the group generator \e{g}
- }
- The idea was that checking this hash would verify that the key
- parameters had not been tampered with, and then the loading
- application could directly verify that
- \e{g}\cw{^}\e{x}\cw{\_=\_}\e{y}.
- In an \e{unencrypted} version 1 key file, the MAC is replaced by a
- plain SHA-1 hash of the private key data. This is indicated by the
- \cq{Private-MAC:} header being replaced with \cq{Private-Hash:}
- instead.
|