mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 01:48:00 +00:00
313 lines
11 KiB
Plaintext
313 lines
11 KiB
Plaintext
\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://tools.ietf.org/html/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 2.
|
|
|
|
\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://tools.ietf.org/html/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.
|
|
|
|
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
|
|
with an all-zero initialisation vector. The key is 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, generated using the
|
|
HMAC-SHA-1 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
|
|
|
|
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, the encryption key is derived from the
|
|
passphrase by means of generating a sequence of hashes, concatenating
|
|
them, and taking an appropriate-length prefix of the resulting
|
|
sequence.
|
|
|
|
Each hash in the sequence is a SHA-1 hash of the following data:
|
|
|
|
\b \cw{uint32}: a sequence number. This is 0 in the first hash, and
|
|
increments by 1 each time after that.
|
|
|
|
\b \cw{string}: the passphrase.
|
|
|
|
The MAC key is also derived from the passphrase. It is a single SHA-1
|
|
hash of the following data:
|
|
|
|
\b \cw{string}: the fixed string \cq{putty-private-key-file-mac-key}.
|
|
|
|
\b \cw{string}: the passphrase.
|
|
|
|
\H{ppk-v1} PPK version 1
|
|
|
|
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.
|
|
|
|
PPK version 1 is not recommended for use! It was only emitted in 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).
|