Q:

How does Putty derive the encryption key in its .ppk format?

I have been doing some reading on the mechanics of managing RSA key pairs, during which I learned that OpenSSL by default derives the encryption key by using MD5 on the passphrase (bad!) without any key stretching (worse!):

But how do you get from the passphrase to the AES encryption key? I
  couldn’t find it documented anywhere, so I had to dig through the
  OpenSSL source to find it:

Append the first 8 bytes of the IV to the passphrase, without a
  separator (serves as a salt). 
Take the MD5 hash of the resulting
  string (once).

That's it.

The author of the quoted blog post goes on to explain how OpenSSL supports the PKCS#8 format (about which I know precisely nothing), offering protection against brute-forcing encrypted private keys that have fallen into the wrong hands through key stretching (PBKDF2).
However, my own valuable private keys are stored in Putty's own .PPK format which raises the question: how well are they protected against this sort of offline brute-force attack?

A:

Gotta love open source!  The following code is from the stable Putty-0.63 release.
sshpubk.c
/*
 * Decrypt the private blob.
 */
if (cipher) {
unsigned char key[40];
SHA_State s;

if (!passphrase)
    goto error;
if (private_blob_len % cipherblk)
    goto error;

SHA_Init(&s);
SHA_Bytes(&s, "\0\0\0\0", 4);
SHA_Bytes(&s, passphrase, passlen);
SHA_Final(&s, key + 0);
SHA_Init(&s);
SHA_Bytes(&s, "\0\0\0\1", 4);
SHA_Bytes(&s, passphrase, passlen);
SHA_Final(&s, key + 20);
aes256_decrypt_pubkey(key, private_blob, private_blob_len);
}

Your password is hashed twice using SHA1 with "\0\0\0\0" and "\0\0\0\1" as separate salts.  The resulting hashes are concatenated to form the AES 256-bit key.  I am not completely up on all the current password attacks, and while I don't believe this is the best password hashing scheme it's not the worst.  Yes, SHA1 was recently deemed not secure, but I don't believe that this scheme would be trivial to break.
Something to remember about password guessing is that the attacker has to know when there is a success.   In this case the private key bytes themselves are encrypted with a key generated from a password.  So for each password attempt the attacker needs to do an AES 256-bit decrypt in addition to the hashing.  There is an integrity check for the entire keyfile.  For the PuTTY key-file we have the following HMAC:     
    SHA_State s;
    unsigned char mackey[20];
    char header[] = "putty-private-key-file-mac-key";

    SHA_Init(&s);
    SHA_Bytes(&s, header, sizeof(header)-1);
    if (cipher && passphrase)
    SHA_Bytes(&s, passphrase, passlen);
    SHA_Final(&s, mackey);

    hmac_sha1_simple(mackey, 20, macdata, maclen, binary);

    smemclr(mackey, sizeof(mackey));
    smemclr(&s, sizeof(s));

Now after already performing 2 hashes and 1 AES decrypt the attacker now has to perform another SHA1 hash and a keyed SHA1 HMAC.  Just to verify if one guess was correct.  If it wasn't a PuTTY keyfile then the following else occurs:
else {
        SHA_Simple(macdata, maclen, binary);
    }

Obviously not as secure as using a PuTTY keyfile, but that would still total 3 hashings, 1 AES decrypt... and a partridge in a pear tree.

