Generate PuTTY PPK v3 file version programmatically in C# with HMAC-SHA-256

112 Views Asked by At

I tried this code: https://antonymale.co.uk/generating-putty-key-files.html

It works OK with PPK version with SHA-1, the file and private-mac generates OK and I'm able to connect to my server.

Now, I have to update my code to work with PPK v3 and HMAC-SHA-256, and I read the documentation and took a look through the source code. It seems that what changed is the MAC derivation key.

public class PuttyKeyFileGenerator
{
    private const int prefixSize = 4;
    private const int paddedPrefixSize = prefixSize + 1;
    private const int lineLength = 64;
    private const string keyType = "ssh-rsa";
    private const string encryptionType = "none";

    public static string RSAToPuttyPrivateKey(RSACryptoServiceProvider cryptoServiceProvider, string comment = "imported-key")
    {
        RSAParameters keyParameters = cryptoServiceProvider.ExportParameters(includePrivateParameters: true);

        byte[] publicBuffer = new byte[3 + keyType.Length + paddedPrefixSize + keyParameters.Exponent.Length +
                                       paddedPrefixSize + keyParameters.Modulus.Length + 1];

        using (var writer = new BinaryWriter(new MemoryStream(publicBuffer)))
        {
            writer.Write(new byte[] { 0x00, 0x00, 0x00 });
            writer.Write(keyType);
            WritePrefixed(writer, keyParameters.Exponent, true);
            WritePrefixed(writer, keyParameters.Modulus, true);
        }
        string publicBlob = Convert.ToBase64String(publicBuffer);

        byte[] privateBuffer = new byte[paddedPrefixSize + keyParameters.D.Length + paddedPrefixSize + keyParameters.P.Length +
                                        paddedPrefixSize + keyParameters.Q.Length + paddedPrefixSize + keyParameters.InverseQ.Length];

        using (var writer = new BinaryWriter(new MemoryStream(privateBuffer)))
        {
            WritePrefixed(writer, keyParameters.D, true);
            WritePrefixed(writer, keyParameters.P, true);
            WritePrefixed(writer, keyParameters.Q, true);
            WritePrefixed(writer, keyParameters.InverseQ, true);
        }
        string privateBlob = Convert.ToBase64String(privateBuffer);

        byte[] bytesToHash = new byte[prefixSize + keyType.Length + prefixSize + encryptionType.Length + prefixSize + comment.Length +
                                      prefixSize + publicBuffer.Length + prefixSize + privateBuffer.Length];

        using (var writer = new BinaryWriter(new MemoryStream(bytesToHash)))
        {
            WritePrefixed(writer, Encoding.ASCII.GetBytes(keyType));
            WritePrefixed(writer, Encoding.ASCII.GetBytes(encryptionType));
            WritePrefixed(writer, Encoding.ASCII.GetBytes(comment));
            WritePrefixed(writer, publicBuffer);
            WritePrefixed(writer, privateBuffer);
        }

        var hmacsha = new HMACSHA256(new SHA256CryptoServiceProvider().ComputeHash(Encoding.ASCII.GetBytes("")));
        string hash = String.Join("", hmacsha.ComputeHash(bytesToHash).Select(x => String.Format((string) "{0:x2}", (object) x)));

        var sb = new StringBuilder();
        sb.AppendLine("PuTTY-User-Key-File-3: " + keyType);
        sb.AppendLine("Encryption: " + encryptionType);
        sb.AppendLine("Comment: " + comment);

        var publicLines = SpliceText(publicBlob, lineLength).ToArray();
        sb.AppendLine("Public-Lines: " + publicLines.Length);
        foreach (var line in publicLines)
        {
            sb.AppendLine(line);
        }

        var privateLines = SpliceText(privateBlob, lineLength).ToArray();
        sb.AppendLine("Private-Lines: " + privateLines.Length);
        foreach (var line in privateLines)
        {
            sb.AppendLine(line);
        }

        sb.AppendLine("Private-MAC: " + hash);

        return sb.ToString();
    }

I tried to change the MAC part with this (I'm not using any encryption mode):

var hmacsha = new HMACSHA256(new SHA256CryptoServiceProvider().ComputeHash(Encoding.ASCII.GetBytes("")));
string hash = String.Join("", hmacsha.ComputeHash(bytesToHash).Select(x => String.Format((string) "{0:x2}", (object) x)));

But it seems that MAC is incorrectly computed.

I tried several other approaches to generate my keys or to start bytesToHash from string and then convert that value to bytes, but still is not working.

Do you have any idea how to compute the Private-Mac with HMAC256?

1

There are 1 best solutions below

0
Ana On

The correct response is this one:

var hmac = new HMACSHA256(new byte[0]);
string hash = String.Join("", hmac.ComputeHash(bytesToHash).Select(x => String.Format((string)"{0:x2}", (object)x)));