Skip to content

Instantly share code, notes, and snippets.

@canton7
Last active July 1, 2022 08:28
Show Gist options
  • Save canton7/5670788 to your computer and use it in GitHub Desktop.
Save canton7/5670788 to your computer and use it in GitHub Desktop.
C# class to convert OpenSSL private keys into PuTTY'-format private keys. Can't handle encryption or anything else funky
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
// Usage:
// var keyLines = File.ReadAllLines(@"keyfile");
// var keyBytes = System.Convert.FromBase64String(string.Join("", keyLines.Skip(1).Take(keyLines.Length - 2)));
// var puttyKey = RSAConverter.FromDERPrivateKey(keyBytes).ToPuttyPrivateKey();
namespace Keyconv
{
public class RSAConverter
{
public RSACryptoServiceProvider CryptoServiceProvider { get; private set; }
public string Comment { get; set; }
public RSAConverter(RSACryptoServiceProvider cryptoServiceProvider)
{
this.CryptoServiceProvider = cryptoServiceProvider;
this.Comment = "imported-key";
}
public static RSAConverter FromDERPrivateKey(byte[] privateKey)
{
return new RSAConverter(DecodeRSAPrivateKey(privateKey));
}
// Adapted from http://www.jensign.com/opensslkey/opensslkey.cs
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
var RSA = new RSACryptoServiceProvider();
var RSAparams = new RSAParameters();
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
using (BinaryReader binr = new BinaryReader(new MemoryStream(privkey)))
{
byte bt = 0;
ushort twobytes = 0;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
throw new Exception("Unexpected value read");
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
throw new Exception("Unexpected version");
bt = binr.ReadByte();
if (bt != 0x00)
throw new Exception("Unexpected value read");
//------ all private key components are Integer sequences ----
RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr));
RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr));
RSAparams.D = binr.ReadBytes(GetIntegerSize(binr));
RSAparams.P = binr.ReadBytes(GetIntegerSize(binr));
RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr));
RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr));
RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr));
RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
}
RSA.ImportParameters(RSAparams);
return RSA;
}
public string ToPuttyPrivateKey()
{
var publicParameters = this.CryptoServiceProvider.ExportParameters(false);
byte[] publicBuffer = new byte[3 + 7 + 4 + 1 + publicParameters.Exponent.Length + 4 + 1 + publicParameters.Modulus.Length + 1];
using (var bw = new BinaryWriter(new MemoryStream(publicBuffer)))
{
bw.Write(new byte[] { 0x00, 0x00, 0x00 });
bw.Write("ssh-rsa");
PutPrefixed(bw, publicParameters.Exponent, true);
PutPrefixed(bw, publicParameters.Modulus, true);
}
var publicBlob = System.Convert.ToBase64String(publicBuffer);
var privateParameters = this.CryptoServiceProvider.ExportParameters(true);
byte[] privateBuffer = new byte[4 + 1 + privateParameters.D.Length + 4 + 1 + privateParameters.P.Length + 4 + 1 + privateParameters.Q.Length + 4 + 1 + privateParameters.InverseQ.Length];
using (var bw = new BinaryWriter(new MemoryStream(privateBuffer)))
{
PutPrefixed(bw, privateParameters.D, true);
PutPrefixed(bw, privateParameters.P, true);
PutPrefixed(bw, privateParameters.Q, true);
PutPrefixed(bw, privateParameters.InverseQ, true);
}
var privateBlob = System.Convert.ToBase64String(privateBuffer);
HMACSHA1 hmacsha1 = new HMACSHA1(new SHA1CryptoServiceProvider().ComputeHash(Encoding.ASCII.GetBytes("putty-private-key-file-mac-key")));
byte[] bytesToHash = new byte[4 + 7 + 4 + 4 + 4 + this.Comment.Length + 4 + publicBuffer.Length + 4 + privateBuffer.Length];
using (var bw = new BinaryWriter(new MemoryStream(bytesToHash)))
{
PutPrefixed(bw, Encoding.ASCII.GetBytes("ssh-rsa"));
PutPrefixed(bw, Encoding.ASCII.GetBytes("none"));
PutPrefixed(bw, Encoding.ASCII.GetBytes(this.Comment));
PutPrefixed(bw, publicBuffer);
PutPrefixed(bw, privateBuffer);
}
var hash = string.Join("", hmacsha1.ComputeHash(bytesToHash).Select(x => string.Format("{0:x2}", x)));
var sb = new StringBuilder();
sb.AppendLine("PuTTY-User-Key-File-2: ssh-rsa");
sb.AppendLine("Encryption: none");
sb.AppendLine("Comment: " + this.Comment);
var publicLines = SpliceText(publicBlob, 64);
sb.AppendLine("Public-Lines: " + publicLines.Length);
foreach (var line in publicLines)
{
sb.AppendLine(line);
}
var privateLines = SpliceText(privateBlob, 64);
sb.AppendLine("Private-Lines: " + privateLines.Length);
foreach (var line in privateLines)
{
sb.AppendLine(line);
}
sb.AppendLine("Private-MAC: " + hash);
return sb.ToString();
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
throw new Exception("Expected integer");
bt = binr.ReadByte();
if (bt == 0x81)
{
count = binr.ReadByte(); // data size in next byte
}
else if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}
private static void PutPrefixed(BinaryWriter bw, byte[] bytes, bool addLeadingNull = false)
{
bw.Write(BitConverter.GetBytes(bytes.Length + (addLeadingNull ? 1 : 0)).Reverse().ToArray());
if (addLeadingNull)
bw.Write(new byte[] { 0x00 });
bw.Write(bytes);
}
// http://stackoverflow.com/questions/7768373/c-sharp-line-break-every-n-characters
private static string[] SpliceText(string text, int lineLength)
{
return Regex.Matches(text, ".{1," + lineLength + "}").Cast<Match>().Select(m => m.Value).ToArray();
}
}
}
@frankhommers
Copy link

It doesn't work for my private key and I can't figure out why. Can you?

This is my input:

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAl6tvAf3fTaZalewscwcV854v5A1UK30DCx8G8suzIf4a0CXM
/W07zdJi6j6K0IQEig7+nfo9WbWkfzxS/fhIqh6c+dsXGbW0JWms3vZ8AaVRtEKm
5xA8NErJ6Rc9J4zeDyOieTygZTI+plXzdTDhX7p5qX0fLYRCT1e4TYxmaUqb6AYV
6i/ZQ30mm9lVFfd3cUpE14hQRfFLsCUPMgXe8hU9D83UawPGSReIpLHGwIX4r6MR
ww0mjqje0FgdwH4XxXm5mTdRoHARc4AKv9C6y4vqLAM6CxA39lIsBHEZ6maUoeG8
ZubLD6zd7WW57pEWadq8qw3tjHq7+39ctroYSQIDAQABAoIBAD7/ZLkBj76rkeXC
9wWCVHJf6smBZcw4fUqhxRuVU58TcmSxxs8fD5+iuxZMCxGoPFByAP1yYwg/pAd7
cYdvGROcapx3jcVOjXiax8TGweRXHDRRGQ1R14fq5qoMToj+gH/uT76m2YKfF5m0
is7LElnWVVF6mLgpvvdA2ttPLXye7qZWFiHcPp9+Il8pofWmCX/3Kk6j+8OMIyj0
AJeMHNNDuB0UyIE97uqgcsHhSwthF+DcW5pQN5Zd8MijQJXECtcBS2wEmAdF90Fu
JnWNVWtxUmnN4eDb8jHZCrPZN41daHhDO2LZMBCT70ab3T2GCiSvdf1prdynq1su
DimWt3cCgYEAxyNhK3M3Eq6XuoZF1V5BLto0sPecV0LH18omKo2g/8XGUgvVN/n6
5iDSGBAiSGanUK0bgMeeZwizqiRyTKbdV8LA3FjJLswkvuFlsm406QHdydJ53NQ7
U+xfn0qcfaG4hw5n7KPT2ZeuaoayMkkyRzsP8Eu7PzpNADykjdqDbasCgYEAwvox
5+l2FXjqdtXZAVId+WyrUMbQbaY3E+EdAWBq5+WRh7GMP7bwCYuSpoGhDo4xtBl+
IlauCReyTUqRN9GZVW+DzkNP/9sWwu9EjrAiMFdP1JsoP6KiliUMa6lLMzVjJPaK
BfLyMFMkoQkIZ8aX3SL+U+UIO9H0es4qcgYk1dsCgYEAiYdAm25lsYxMk3AaGIKQ
x9fBHhrTqVwggkJE24yWjS48iftMFO2avoYaMq4pzxC+KrRy0xyJpCTZV/71xURU
h0CkS0kyIBSyp/Uoc/MyrV0xnCm1KbKVCp+rZmpz6zveIb/sO/zk2VitBzrhJGNy
ysjaeB0FQ2RzO1+mUaWAsSMCgYAo4KON55hmMA+YEoWIjVrTbEEOkmAoXOy3Mj8O
htRjAfJc7JBm20e/+I8qcYr+rjkldSLIuDuMlHdBVRzVAxCOpO6RU/N1xM0k2V0a
PTstv2BxRoZNsR89M09DUx79AXhjy3xset7j2sqXKHmmqDA2oJuJoz7yq7YB+S9U
8IzQeQKBgQCyDZtwiqh7mRx2i+B3PmZDgXrxh2wwzarv5QB3WcU8hkWI6y95Q7nm
Itj3/uWUfv74aTfbfBxcq35FpjANFnLRHMn+dXO9paYX/W1d+r0Ih/Si81IWQnqW
K/7ZCxEB0tRSUQjpvq6qpEdOBLdLAGPYCnWA/FbJ+TarXPNf0MqDUQ==
-----END RSA PRIVATE KEY-----

This is my public key (for reference, not used through your code):

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl6tvAf3fTaZalewscwcV
854v5A1UK30DCx8G8suzIf4a0CXM/W07zdJi6j6K0IQEig7+nfo9WbWkfzxS/fhI
qh6c+dsXGbW0JWms3vZ8AaVRtEKm5xA8NErJ6Rc9J4zeDyOieTygZTI+plXzdTDh
X7p5qX0fLYRCT1e4TYxmaUqb6AYV6i/ZQ30mm9lVFfd3cUpE14hQRfFLsCUPMgXe
8hU9D83UawPGSReIpLHGwIX4r6MRww0mjqje0FgdwH4XxXm5mTdRoHARc4AKv9C6
y4vqLAM6CxA39lIsBHEZ6maUoeG8ZubLD6zd7WW57pEWadq8qw3tjHq7+39ctroY
SQIDAQAB
-----END PUBLIC KEY-----

This is the output with your code (which doesn't work):

PuTTY-User-Key-File-2: ssh-rsa
Encryption: none
Comment: imported-openssh-key
Public-Lines: 6
AAAAB3NzaC1yc2EAAAAEAAEAAQAAAQEAl6tvAf3fTaZalewscwcV854v5A1UK30D
Cx8G8suzIf4a0CXM/W07zdJi6j6K0IQEig7+nfo9WbWkfzxS/fhIqh6c+dsXGbW0
JWms3vZ8AaVRtEKm5xA8NErJ6Rc9J4zeDyOieTygZTI+plXzdTDhX7p5qX0fLYRC
T1e4TYxmaUqb6AYV6i/ZQ30mm9lVFfd3cUpE14hQRfFLsCUPMgXe8hU9D83UawPG
SReIpLHGwIX4r6MRww0mjqje0FgdwH4XxXm5mTdRoHARc4AKv9C6y4vqLAM6CxA3
9lIsBHEZ6maUoeG8ZubLD6zd7WW57pEWadq8qw3tjHq7+39ctroYSQ==
Private-Lines: 14
AAABAQA+/2S5AY++q5HlwvcFglRyX+rJgWXMOH1KocUblVOfE3JkscbPHw+forsW
TAsRqDxQcgD9cmMIP6QHe3GHbxkTnGqcd43FTo14msfExsHkVxw0URkNUdeH6uaq
DE6I/oB/7k++ptmCnxeZtIrOyxJZ1lVRepi4Kb73QNrbTy18nu6mVhYh3D6ffiJf
KaH1pgl/9ypOo/vDjCMo9ACXjBzTQ7gdFMiBPe7qoHLB4UsLYRfg3FuaUDeWXfDI
o0CVxArXAUtsBJgHRfdBbiZ1jVVrcVJpzeHg2/Ix2Qqz2TeNXWh4Qzti2TAQk+9G
m909hgokr3X9aa3cp6tbLg4plrd3AAAAgQDHI2ErczcSrpe6hkXVXkEu2jSw95xX
QsfXyiYqjaD/xcZSC9U3+frmINIYECJIZqdQrRuAx55nCLOqJHJMpt1XwsDcWMku
zCS+4WWybjTpAd3J0nnc1DtT7F+fSpx9obiHDmfso9PZl65qhrIySTJHOw/wS7s/
Ok0APKSN2oNtqwAAAIEAwvox5+l2FXjqdtXZAVId+WyrUMbQbaY3E+EdAWBq5+WR
h7GMP7bwCYuSpoGhDo4xtBl+IlauCReyTUqRN9GZVW+DzkNP/9sWwu9EjrAiMFdP
1JsoP6KiliUMa6lLMzVjJPaKBfLyMFMkoQkIZ8aX3SL+U+UIO9H0es4qcgYk1dsA
AACBALINm3CKqHuZHHaL4Hc+ZkOBevGHbDDNqu/lAHdZxTyGRYjrL3lDueYi2Pf+
5ZR+/vhpN9t8HFyrfkWmMA0WctEcyf51c72lphf9bV36vQiH9KLzUhZCepYr/tkL
EQHS1FJRCOm+rqqkR04Et0sAY9gKdYD8Vsn5Nqtc81/QyoNR
Private-MAC: 8dd56f5f650ea83f4c96a841b49d53dadbeb8c54

This is the output from puttygen (which does work):

PuTTY-User-Key-File-2: ssh-rsa
Encryption: none
Comment: imported-openssh-key
Public-Lines: 6
AAAAB3NzaC1yc2EAAAADAQABAAABAQCXq28B/d9NplqV7CxzBxXzni/kDVQrfQML
Hwbyy7Mh/hrQJcz9bTvN0mLqPorQhASKDv6d+j1ZtaR/PFL9+EiqHpz52xcZtbQl
aaze9nwBpVG0QqbnEDw0SsnpFz0njN4PI6J5PKBlMj6mVfN1MOFfunmpfR8thEJP
V7hNjGZpSpvoBhXqL9lDfSab2VUV93dxSkTXiFBF8UuwJQ8yBd7yFT0PzdRrA8ZJ
F4ikscbAhfivoxHDDSaOqN7QWB3AfhfFebmZN1GgcBFzgAq/0LrLi+osAzoLEDf2
UiwEcRnqZpSh4bxm5ssPrN3tZbnukRZp2ryrDe2Merv7f1y2uhhJ
Private-Lines: 14
AAABAD7/ZLkBj76rkeXC9wWCVHJf6smBZcw4fUqhxRuVU58TcmSxxs8fD5+iuxZM
CxGoPFByAP1yYwg/pAd7cYdvGROcapx3jcVOjXiax8TGweRXHDRRGQ1R14fq5qoM
Toj+gH/uT76m2YKfF5m0is7LElnWVVF6mLgpvvdA2ttPLXye7qZWFiHcPp9+Il8p
ofWmCX/3Kk6j+8OMIyj0AJeMHNNDuB0UyIE97uqgcsHhSwthF+DcW5pQN5Zd8Mij
QJXECtcBS2wEmAdF90FuJnWNVWtxUmnN4eDb8jHZCrPZN41daHhDO2LZMBCT70ab
3T2GCiSvdf1prdynq1suDimWt3cAAACBAMcjYStzNxKul7qGRdVeQS7aNLD3nFdC
x9fKJiqNoP/FxlIL1Tf5+uYg0hgQIkhmp1CtG4DHnmcIs6okckym3VfCwNxYyS7M
JL7hZbJuNOkB3cnSedzUO1PsX59KnH2huIcOZ+yj09mXrmqGsjJJMkc7D/BLuz86
TQA8pI3ag22rAAAAgQDC+jHn6XYVeOp21dkBUh35bKtQxtBtpjcT4R0BYGrn5ZGH
sYw/tvAJi5KmgaEOjjG0GX4iVq4JF7JNSpE30ZlVb4POQ0//2xbC70SOsCIwV0/U
myg/oqKWJQxrqUszNWMk9ooF8vIwUyShCQhnxpfdIv5T5Qg70fR6zipyBiTV2wAA
AIEAsg2bcIqoe5kcdovgdz5mQ4F68YdsMM2q7+UAd1nFPIZFiOsveUO55iLY9/7l
lH7++Gk323wcXKt+RaYwDRZy0RzJ/nVzvaWmF/1tXfq9CIf0ovNSFkJ6liv+2QsR
AdLUUlEI6b6uqqRHTgS3SwBj2Ap1gPxWyfk2q1zzX9DKg1E=
Private-MAC: 00a506895bd9cc7b57ece9b2791e74b901c28503

@canton7
Copy link
Author

canton7 commented Jun 15, 2018

@frankhommers what makes you say it "doesn't work"?

@frankhommers
Copy link

frankhommers commented Jun 15, 2018

If I put the public key on my server I can only succesfully authenticate with the PuttyGen PPK. The PPK from this code doesn't work.
Also the PPK made by WinSCP /KeyGen works.

Also the output file is different.

@canton7
Copy link
Author

canton7 commented Jun 16, 2018

Right. The output file being different is fine: if I remember, there are multiple possible encodings of the same numbers.

Are the RSA parameters loaded correctly? That is, can you narrow the issue down to the PPK generation, not reading the PEM-encoded key?

@canton7
Copy link
Author

canton7 commented Jun 16, 2018

I loaded the PPK which didn't work into puttygen, and exported the openssh key. Then loaded that and the private key you posted into an online PEM decoder, and they came out as containing identical information. Therefore my code is writing all of the correct information to the PPK, in a way which puttygen can read.

What application on your server is reading the PPK?

@frankhommers
Copy link

I just use standard debian OpenSSHD for the server side. PuttyGen and WinSCP /KeyGen are producing exactly the same PPK.

How can I verify the RSA Parameters? BouncyCastle.NET's PemReader shows the same RSAParameters as the quoted example code... I am clueless.

@canton7
Copy link
Author

canton7 commented Jun 19, 2018

Right, so is it putty that's reading the PPK, or something else? It's it's putty, that's a bit strange. I never did figure out why I couldn't generate an identical file to puttygen, so the next step would be digging into the putty source (and the source of the bignum library it uses) to figure out where the remaining discrepancies come from.

@frankhommers
Copy link

frankhommers commented Jun 25, 2018

Yes. I don't know where de discrepancies come from either. I see that "WinSCP /keygen" and "PuttyGen" generate the exact same .ppk. (Except the Private-MAC line).

@fuji4
Copy link

fuji4 commented Nov 28, 2018

The class works great, but what to do if i need to secure my PPK with a passphrase? Any ideas how to encrypt it?

@amitbarkai
Copy link

the following private key , throws a bad data exception

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,E97EC262B2F5796A

yWFEU14wA65Mb+/GsgbgSBCTR3dPbNIB1zkj9iW6xQw3GCF7hfEfopKb8pvKQlHz
fN0QYiqyhB/AE7sckdaTLK+4glJ/jMjijxFkxJ/nHpvsj5m/UIAM/k0oUpTGuNyc
XZNYJIZj009CaPYGYhICPd/ppgjz2YtTBVHJ9kUl9PfqrPD9QmqGRPCBlPLPpKKd
Ixo9oL54LaI0hWsLy9Pm7yHxBR11qrfuHfvqx2M0xP2G+29z4MLk8ojwrMf+n21K
34RozQI+GdJyVKSsb31Me46qo6KccfbAf9m9rRvsMe8vcLDADEo6IXTPUwGC7rAT
3ocT4mMiJwmvZiFogLfSt36wl1zyRnyrrbzTihzzbEHVBtHuHK9gbru11mVnVOQk
J1k9ppIpDjKCESaWutife1eUY3+UW2BnO+WYNLk09aEV28RVd0sn7Gnr738iF/rI
YyfDbvs1iC4uICC8NrAIt0jFmvxTUyKOKM6VcrFjhFDWy3zTMToGO3a5E4Rc5CX6
DexRhk5MtDBPHN9bflRG2zTA9KjC109TneZ2/kPHgSs758nwu0x3RwhE5M0ont3/
IGhfprpEU2OcdpzxvyElTm9wtQeB2qbNJ3Dz1qa9BFoXw9aMB/6ldcff27ZUQLx9
sSncRdW4IjbTcwHUA+RvMrilCPBEx3pR4iUKyHAK4nG1Lvwa49JzTgdxgRXS14ju
OC7I1+J+U9FoRnPPLBwPSfH8qpVepxf2wM61U2oDlYKgyuELvgGfym8LzLdOxfxR
hphgKW/Eo1zAXkr5EUR7+OkDi9BjgconB4KNFASbEDG3z6ty01JJP0Q9S2FwOE0t
vYNvTmDBDw386bbtVc0XR7GGTkTbb9/yVWcJCQ6P+Wyy0tLwNoUa/BRaHM7lrfi7
NeoZBQzy7NXRpwYHe5IXP56+0lYm5ZJhIqEDb5Ne52lCPDg4YT1bjVjsR4nUrcrl
ZuMbJBKhMukOGCVQuP4vL8RV2sZYTq2gJBOAp3sQOHAlFkWkn1D29Rqxsx8EdDrG
Dv1oMLwNnO4By9YtJHTKrJAubbiXFijJmSsJW7z45lQ7Ql/ERa2okAIYX0EMoLlT
nLRjBraaPxNWI9A1UNxkxwB4s6+Iod47LtegmvEv7meAi9QQGDb5nAUAmD+HEy+H
YWZ6xUXr+AOQdKBUemsgWYtE8CIfJ6SMSz9AcBHDbVAISkGQJxaa7DT3h4BuMxU6
SsQu1awrQhkSw5lntnI7lGNMZzjp/DCb4bGOnDmw34TUXU1+IMI8eXZzQnvQisRM
UBb22u6Fgnc2o4c+Z/3YAk7lO+JMNCEwSznlBQasZpV54Y5aB5vzLDP94W1NtnYA
dgaQ8bwEIdwm25HPv8U0/E1vAci9S4+asjFIVkEwW3n2TPJvXR/0hUWEPfzfgOJk
qtrBsMJDOO2vl5qS9Go2bRQsW0rzUZ9ePPN/RAFdcId1C2PdHasprq6aqFC+k3Ag
bVdsqFJoW5/PQa4QFWXD0EaaCFpgmE57QS+hjQcvzHBnpT/LA8lT5bH9XK3e5/8V
5PUND75X6mn31JfnnWdc3cHfA7MI0vY9kfSaJvMPppVsMGp1HBOe7g==
-----END RSA PRIVATE KEY-----

can you please check , or explain what is the issue ?

@amitbarkai
Copy link

i've mistakenly sent an encrypted key
here is an example of another example of a key that throws the bad data exception

-----BEGIN RSA PRIVATE KEY-----

MIIEoQIBAAKCAQEAuWll7Dd97qYQJOeMcQOvI/KefF4Vogb25eUwIYuCKlSIXnu2
zPxOKN++kJIC/ufJHqGgW6xQWRVMkqY0tWJLqkN0LUiAFsiKm3wVyV0Ax73rGkOn
7YpBuHshmO+bDoiwR+4pH6DDRs7fhTlwQII5EuctvL3oQ/kgDIcGwhYHrwgAm/ug
3W/Z7PFB0Fo6jQ6M4Qd8My6TH1uFIs86G1sg9v75r6/yw0gq/C1lOEmvRcb62KrQ
j0s8B/fZypuOp3OO+95RLsnohaEGrhfgTRTDPSqHHuT1PBoy6CP+3zCf6l+Fwmbg
XzohJE+tzUhk8EqIN0+zBotsdHU9cxySDd3wMQIDAQABAoH/ZtgPsPcW40k5qz7E
p+tjOo1ZW6LUXQt/6AU4rWK00MYS3lXBD/LjZ8iz630SLFJlPJhqWm86Ii+uSann
krTzQ34m8vdEFV8ngxdQ/mCYDjNKNUdR3nDSA4JJIIvHdkhbf4qbRO/nYV9enIc/
vIh/H/0bYZX1P77wCALdvMp7eJoFojE9ZbwsMiJgQRUpUdnh4EI3mPPcBGFprF1U
QraFVZf4Q5OuMckrHVUkMofXyOmmguI9WaA2bWy1VQ1MQ4SqHRCIojq+MDVDlw9Z
DNFB6VbWDUOKco4Vlb86E7eA8sMtZLsEy4JPXGtFBIkcUV/MPRpqKejeBFo8g6pb
EZd5AoGBAObmhBhW6NZNl23PuTofzlyqiHP6Pg7vt/wpq7Mw9G1xahvb3p+4VTiD
FQjJ3dE4u81H7O55cRcr45yHDUswiLAXZcX5gwXp+GPueB5jJeuZLqCvNThH64Ow
E9kFjsszbelNuzhYCFMhbdd4kUlZM56t0Zd7eUsqGHeRRUeOtGz/AoGBAM2RBi8f
z+bm6QXH6ir4C4rYft021KZ+h7e/2ZtsIMkR/q81/LEzkr5e89feyYqNe/iE9jgy
8JDuwt4nJJBqoRWkDtBCUtNurTqMj6Te0XFeZripMkZ9BhBRm03UU0NKS4aKeG8x
gr4FLGstZ41ujbCLeVLzDKpv001VZfKjUDLPAoGBAKWG/4+LTmPDQBmC3qCiiIe5
4RRzguWmSFlHbkWJhNCoi47pMlGCDeXzYrLoNFJ2v3tMYrga603XMtbVolwSsQq7
20PvuVQWBPFu1UHDhj29lMWwlRCBzn6bTb840sMtXU/xX5Pm2CDwSBQ95LmWbwEE
Tsqvw6Z0yRF+XRINZZ71AoGAATUl5Sb5sLCQk+EdxgzY/ILTE/ebfjLmFzVAUQJs
muHJLjxR9LSJ2yZxpkX/xxmXrdkSHThnY2KTsHxoYZTOx3LER4LsO6O9zsc+nMhW
UKUuU01jJzjazUO9dtKVfqK0GOE9XeHbk8QyA5srrZAFsxDOsKcO3v1zL1QeGjPN
Z88CgYByrdTjPg54NBfaRN3uDzjOdCaCJb/RCUSYxKOmjNB4ALTSCmUgGGhIX53o
la30no931wQYvrLQ+KWHyCyya3p31XRnlKD1SReE/n6XSMFui6lLgPNdnCLdIAZq
YXXOjAn2FeoNqkKWlbj2linuAJa5FrRJ07O1DJXajWfbzX7tcA==
-----END RSA PRIVATE KEY-----

** using puttyGen the key is converted without any issue

@davidehnis
Copy link

This worked beautifully for me. Nice work!!

@venkomiri
Copy link

Hi Thanks for the artical..

  1. I have tried this block to create a Putty private key, and I am able to load the generated key from windows Microsoft store putty-gen app, but failed to load through putty-gen which is installed through Winscp.

  2. I am failed to use the key pair to authenticate when I convert the key to OpenSSH format through ssh-keygen command and install in ssh sftp server.

Here is my key

PuTTY-User-Key-File-2: ssh-rsa
Encryption: none
Comment: imported-key
Public-Lines: 6
AAAAB3NzaC1yc2EAAAAEAAEAAQAAAQEAvKAqb2Gvnfi51gvPyTx2j9SdG4D+u/zA
YIZUU99wE9jL6MNuk7zCaI8y/tc5aTWY3XUxHw4y3H8cBmqMlb5tnkHwrogBMsgI
bTbELKeuyeeAcqOQ1qM7s64dpR3BBsrCk5IYhe9BeUCczK1RuYrbuDmCnZOG/8dS
cL8uLUiaO2l2cZu+H8j6O2LK6a7Nb4YP0f28txYaieQ1lw9KdgNVUQyPPfm5aMKk
ay/VfApiZDyUA3/qw5c2VmD+3E1bH0UtZFcBpE2+BP4ax1aabTgeFxSCZF3dPoce
52gujlwz7KE/dcgt8yXNSy4IoEK8ANmIV09RjpEgFcjkmh+NMT7PEQ==
Private-Lines: 14
AAABAQBfGlYq8FLOUEDKZgwuxzh0DlvkKSbGe4o3YKMV4rsslotA4YBYJrzSYRjy
GmvM2wQm7FaG9O6587Can5AgU/IK7+484T/Rbb+p6QoCBc1/6SP/KO72+Tg5wNkb
jiPrm8F9DBUnOlmFnAkyvVROO2/Ks9xiPKa9Qa8UP6A1nrx8pTLVftalG5J2C8If
GRoF8w33OekEiHCjWm0nkX2kvSeDmFn+CU0wopaoODeKZEkZYCkLGl/UHfqxWu6+
xkDQDXbR9Cg44At2ry5rHQGDxwol2DLpwSi+QMaAf/jKUydJ9YBJswNo4PS9/Xeb
V0Mp6WEgS9di1tmsbYm15F+HrF/dAAAAgQDOSXADfwqxPjR1zX1h4/G1eV0rRAdy
dTUzTt2ux7FaSG0DX4A56H90kbwtzz9Sz7I1sbV1dv99FLUNTByZKw9X8O8bMX3F
fkV3T1vg1IkQJB2pF8+iXu3uL5++kLtBr3w2icx+zezz6L8tyqBgRpmO73RzBWvr
2seKmhbvxKwgUwAAAIEA6hUlQj960M4m7qyO0KZC35HX5BqXzReUp+Jq+gE2TAEp
15MM9D+QoOL7bnKGl7LG43U5fuEHNCEE2Fzq6tPI8aX9RS82qzVgHXNR4xP/BE3q
v8d5/1iqDPwA7A581pZT2di4d1dUdtBQXSYL3A4yiDWysXyRwAApWcmhVHsxdosA
AACBAJZIHU23gzL6fKSXhkGyQG5BV2L9ft2iDKQiuMQLdhq13j6uxlK6o/hnW9jB
SMoxf7Pw84xVBWTTv7zFJ6/+d2RTfm87j79fnUZRZ+jFWyEj/+oSv4P7GvyDai7Z
1LTn/nGhx0s7KnuyxEJs94TxJJSzON5+h71K6yHrT8sos9Gc
Private-MAC: 85c5a77eb9afbe937da4d625e11a31502d601ce7

Could you please help me solve this?

@canton7
Copy link
Author

canton7 commented Apr 4, 2020

I'm afraid I wrote this to solve a problem that I had. I'm not an expert on putty key formats -- I hacked this together until it worked for me. This code doesn't come with any support -- if you've got a problem, I'm afraid it's up to you to fix it. If you do find and fix problems, please let me know and I can update the code.

@bosima
Copy link

bosima commented Jun 29, 2022

I may have solved the problem that the putty private key may not be generated correctly.

This is because the padding is not always required, which is related to the processing logic of putty:

dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8;
plen = (bignum_bitcount(rsa->p) + 8) / 8;
qlen = (bignum_bitcount(rsa->q) + 8) / 8;
ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8;

We can do this now:

private const int prefixSize = 4;
private const int paddedPrefixSize = prefixSize + 1;
		
byte[] publicBuffer = new byte[3 + keyType.Length + GetPrefixSize(keyParameters.Exponent) + keyParameters.Exponent.Length +
                GetPrefixSize(keyParameters.Modulus) + keyParameters.Modulus.Length + 1];

using (var writer = new BinaryWriter(new MemoryStream(publicBuffer), Encoding.ASCII))
{
	writer.Write(new byte[] { 0x00, 0x00, 0x00 });
	writer.Write(keyType);
	WritePrefixed(writer, keyParameters.Exponent, CheckIsNeddPadding(keyParameters.Exponent));
	WritePrefixed(writer, keyParameters.Modulus, CheckIsNeddPadding(keyParameters.Modulus));
}
			
private static bool CheckIsNeddPadding(byte[] bytes)
{
	// 128 == 10000000
	// This means that the number of bits can be divided by 8.
	// According to the algorithm in putty, you need to add a padding.
	return bytes[0] >= 128;
}

private static int GetPrefixSize(byte[] bytes)
{
	return CheckIsNeddPadding(bytes) ? paddedPrefixSize : prefixSize;
}

I also added passphrase for ppk.

Full Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment