C.2 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:

PuTTY-User-Key-File-version: algorithm-name
Encryption: encryption-type
Comment: key-comment-string

version is a decimal number giving the version number of the file format itself. The current file format version is 3.

algorithm-name is the SSH protocol identifier for the public key algorithm that this key is used for (such as ‘ssh-dss’ or ‘ecdsa-sha2-nistp384’).

encryption-type indicates whether this key is stored encrypted, and if so, by what method. Currently the only supported encryption types are ‘aes256-cbc’ and ‘none’.

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 (RFC 4648), and is preceded by a header line saying how many lines of base64 data are shown, looking like this:

Public-Lines: number-of-lines
that many lines of base64 data

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 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 authorized_keys line.

If the key is encrypted (i.e. encryption-type is not ‘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:

Key-Derivation: argon2-flavour
Argon2-Memory: decimal-integer
Argon2-Passes: decimal-integer
Argon2-Parallelism: decimal-integer
Argon2-Salt: hex-string

argon2-flavour is one of the identifiers ‘Argon2d’, ‘Argon2i’ or ‘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:

Private-Lines: number-of-lines
that many lines of base64 data

The binary data represented in this base64 blob may be encrypted, depending on the encryption-type field in the key file header shown above:

Unlike public keys, the binary encoding of private keys is not specified at all in the SSH standards. See section C.3 for details of the private key format for each key type supported by PuTTY.

The final thing in the key file is the MAC:

Private-MAC: hex-mac-data

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:

The MAC key is derived from the passphrase: see section C.4.