RSA private key import from PEM format in C#
First of all, I want to apologize for not writing. On the one hand, this is not a good thing for me to disappear from the development community horizons, on the other hand, I am investing all my time into our better feature, which is a good thing. Too many things were done during the last two years. And the good news is that we already delivered whatever was promised and know for sure that we are able to deliver even more in the future. But let’s get to business. First of all, I have a huge pipeline of interesting articles to share with you, second, some people from my team have also decided to contribute to the community and write Better Place development team blog. There are not too many there, but that is only a matter of time.
Today, we’ll speak about security. About how to import OpenSSL private key into .NET application and use it along with X509 public certificate to establish TLS connection with asymmetric encryption and two phase certificates handshake.
Let’s start from the very beginning. What is SSL? SSL is the secure way to communicate when transferred data is encrypted by using one time and per-session cipher. There are different implementations of such connection. The most famous one is the one all of you use when connecting to https://something… When doing this, your browser asks the remote side to provide its public certificate for you in order to check it with local “authority” you trusted in. If everything is ok and the host defined on the remote certificate is the host you are speaking with, your browser allows communication after both sides decide about the one-time cipher for encryption.
You can implement this mode of SSL very easily by using SslStream class in .NET as 1-2-3.
- Resolve host and open TcpClient connection to it:
var host = new IPHostEntry(); try { host = Dns.GetHostEntry(RemoteAddress.DnsSafeHost); } catch (SocketException soe) { if (soe.SocketErrorCode == SocketError.HostNotFound) { host.HostName = RemoteAddress.DnsSafeHost; } } Client.Connect(host.HostName, RemoteAddress.Port);
- Initialize SSL encrypted stream to it by providing validation callback for remote certificate:
var stream = new SslStream(Client.GetStream(), true, _validateCertificate);
- Ask for authorization:
stream.AuthenticateAsClient(host.HostName);
Inside remote certificate validation callback, you should decide what to do if something bad happened during the negotiation phase.
private readonly RemoteCertificateValidationCallback _validateCertificate =
(sender, certificate, chain, sslPolicyErrors) => {
var result = sslPolicyErrors == SslPolicyErrors.None;
if (!result) {
var err = new StringBuilder();
err.AppendFormat("Unable to establish security connection due to {0}.
Error chain:",
sslPolicyErrors);
foreach (var el in chain.ChainElements) {
foreach (var s in el.ChainElementStatus) {
err.AppendFormat("{0} – {1}", el.Certificate.Subject, s.StatusInformation);
}
}
Log.Warn(err.ToString());
}
return result;
};
So far, so good. Now, if everything is OK, just use SslStream
as a regular stream to write and read from the socket. All other complicated things will be done by .NET.
However this is only a part of the game. Now comes the real thing. What if you want to be more secure and want your server to be able to validate that the local client is one that it can trust. This scenario is often used in closed networks, when server side (or any other provisioning entity) can assure that every client is well known and that it is able to provide certificate to each of those. For this scenario, we also have solution in SslStream
implementation, which takes into account this ability, defined by TLS RFC. All we need is to use other override of SslStream
constructor which receives the callback for client certificate, choose logic and authorization method with prepared clients certificates.
var stream = new SslStream(Client.GetStream(),
true, _validateCertificate, _selectCertificate);
stream.AuthenticateAsClient(host.HostName, _clientCerts, SslProtocols.Ssl3, false);
Inside local certificate selection logic, you should receive the remote end choice algorithm and return the most secure client certificate you have.
private readonly LocalCertificateSelectionCallback _selectCertificate =
(sender, target, localCerts, remoteCert, issuers) => {
….
return securestCert;
}
Also you should prepare the local certificates collection, provided as input to negotiation method. This one is simple too. All you need is a standard X509 certificate(s). Usually, such certificates are provided by uber-secure-unix-seriose-unbreakable-machine, which use OpenSSL to export generated keys. This means that in most cases, your public certificate will look inside like this:
Certificate:
Data:
Version: 1 (0×0)
Serial Number: 268436473 (0x100003f9)
Signature Algorithm: md5WithRSAEncryption
Issuer: O=UBER, OU=RD/emailAddress=ca@ubersecurity.org,
L=TLV, ST=Israel, C=IL, CN=ca
Validity
Not Before: May 25 11:26:50 2011 GMT
Not After : May 24 11:26:50 2012 GMT
Subject: C=IL, ST=Israel, O=UBER, OU=SEC, CN=UberSecurity
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
… some random HEX numbers …
Exponent: 65537 (0×10001)
Signature Algorithm: md5WithRSAEncryption
… some other random HEX numbers …
—–BEGIN CERTIFICATE—–
… some BASE64 random characters here …
—–END CERTIFICATE—–
This format is called PEM (Privacy Enhanced Mail). This is the most common and easiest format for secure text transfer. Such file can be easily imported and used by X509Certificate class as follows:
var clientCert = X509Certificate.CreateFromCertFile("myCert.pem");
That’s all. All you need now is to add this certificate into certificate collection (_clientCerts
in this case) and return it when _selectCertificate
delegate is being called.
Looks simple and secure? It is, but there is a small BUT in all this. Real security experts, come from OpenSSL world often do not want to put private key for client (the key will be used for outgoing traffic encryption) inside client certificate and want to provide it via other channel securely.
Now you are asking what I am speaking about? Let me explain:
When SSL uses asymmetric encryption algorithm, local side uses private key to encrypt outgoing traffic. Once it trusts the other side (by validating remote certificate), it sends local public key to the remote side, which uses it for information decryption. So far, we have three entities: public key, private key and certificate. There is a method commonly used by the industry to minimize transit problems. We know to pack public certificate and wrapped public key inside the same store to send it. If we want to go even further, we can also store securely private key inside the same store. Looks not very secure? This is not quite right. First of all, in most cases private certificate is encrypted by using special keyphase only known to the side this certificate intended to, second, it uses the same public key + certificate itself hash values to encrypt it event better. In this case, there is a big advantage of compact and well known package format (keypair + certificate) and high security level.
However, people coming from the OpenSSL world not trust too much in this method (and called it “evil empire bought the patent”) and often provide encrypted private key separately. This key is being transferred in PEM format, however this time it is not the standard one, but specific and designed by OpenSSL geeks. Even if they call it RSA format, it has almost no relation to it.
Such key looks as follows:
—–BEGIN RSA PRIVATE KEY—–
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,…some geeky HEX here …
… some BASE64 random characters here …
—–END RSA PRIVATE KEY—–
Looks simple? Do not hurry this much. .NET has no built in method to read this format. So we’ll have to write one, based on OpenSSL specification. Let’s start:
First of all, “well known headers
”:
private const string _begin = "—–BEGIN ";
private const string _end = "—–END ";
private const string _private = "PRIVATE KEY";
private const string _public = "PUBLIC KEY";
private const string _rsaPublic = "RSA PUBLIC KEY";
Next read the text inside the file:
using (var reader = new StringReader(data)) {
var line = reader.ReadLine();
if (line.NotNull() && line.StartsWith(_begin)) {
line = line.Substring(_begin.Length);
var idx = line.IndexOf(‘-’);
if (idx > 0) {
var type = line.Before(idx);
return _loadPem(reader, type, passKey);
}
}
throw new ArgumentException("This is not valid PEM format", "data",
new FormatException("PEM start identifier is invalid or not found."));
}
…and read headers
:
var end = _end + type;
var headers = new _pemHeaders();
var line = string.Empty;
var body = new StringBuilder();
while ((line = reader.ReadLine()) != null && line.IndexOf(end) == -1) {
if (line == null) {
throw new FormatException("PEM end identifier is invalid or not found.");
}
var d = line.IndexOf(‘:’);
if (d >= 0) {
// header
var n = line.Substring(0, d).Trim();
if (n.StartsWith("X-")) n = n.Substring(2);
var v = line.After(d).Trim();
if (!headers.ContainsKey(n)) {
headers.Add(n, v);
} else {
throw new FormatException("Duplicate header {0} in PEM data.".Substitute(n));
}
When headers are ready, we need to read a body
. This is base64
encrypted:
} else {
// body
body.Append(line);
}
}
if (body.Length % 4 != 0 || type.EndsWith(_private)) {
throw new FormatException("PEM data is invalid or truncated.");
}
return _createPem(type, headers,
Convert.FromBase64String(body.ToString()), passkey);
And now, based on headers, we can decode body. For simplification, we’ll decode only most common encryptions for the key.
type = type.Before(type.Length – _private.Length).Trim();
var pType = headers.TryGet("Proc-Type");
if (pType == "4,ENCRYPTED") {
if (passkey.IsEmpty()) {
throw new ArgumentException
("Passkey is mandatory for encrypted PEM object");
}
var dek = headers.TryGet("DEK-Info");
var tkz = dek.Split(‘,’);
if (tkz.Length > 1) {
var alg = new _alg(tkz[0]);
var saltLen = tkz[1].Length;
var salt = new byte[saltLen / 2];
for (var i = 0; i < saltLen / 2; i++) {
var pair = tkz[1].Substring(2 * i, 2);
salt[i] = Byte.Parse(pair, NumberStyles.AllowHexSpecifier);
}
body = _decodePem(body, passkey, alg, salt);
if (body != null) {
return _decodeRsaPrivateKey(body);
}
} else {
throw new FormatException("DEK information is invalid or truncated.");
}
}
For simplification, we’ll support only the most common encryption algorithms (3DES with CBC mode). In general, RSA private key can be encrypted by AES, Blow Fish, DES/Triple DES and RC2.
private static byte[] _decodePem(byte[] body,
string passkey, _alg alg, byte[] salt) {
if (alg.AlgBase != _alg.BaseAlg.DES_EDE3 && alg.AlgMode != _alg.Mode.CBC) {
throw new NotSupportedException("Only 3DES-CBC keys are supported.");
}
var des = _get3DesKey(salt, passkey);
if (des == null) {
throw new ApplicationException
("Unable to calculate 3DES key for decryption.");
}
var rsa = _decryptRsaKey(body, des, salt);
if (rsa == null) {
throw new ApplicationException("Unable to decrypt RSA private key.");
}
return rsa;
}
And decrypt itself:
private static byte[] _decryptRsaKey(byte[] body, byte[] desKey, byte[] iv) {
byte[] result = null;
using (var stream = new MemoryStream()) {
var alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = iv;
try {
using (var cs = new CryptoStream(stream, alg.CreateDecryptor(),
CryptoStreamMode.Write)) {
cs.Write(body, 0, body.Length);
cs.Close();
}
result = stream.ToArray();
} catch (CryptographicException ce) {
// throw up
throw ce;
} catch (Exception ex) {
Log.Exception(ex, Severity.Info, "Failed to write crypto stream.");
};
}
return result;
}
by getting 3DES key from stream:
private static byte[] _get3DesKey(byte[] salt, string passkey) {
var HASHLENGTH = 16;
var m = 2; // 2 iterations for at least 24 bytes
var c = 1; // 1 hash for Open SSL
var k = new byte[HASHLENGTH * m];
var pk = Encoding.ASCII.GetBytes(passkey);
var data = new byte[salt.Length + pk.Length];
Array.Copy(pk, data, pk.Length);
Array.Copy(salt, 0, data, pk.Length, salt.Length);
var md5 = new MD5CryptoServiceProvider();
byte[] result = null;
var hash = new byte[HASHLENGTH + data.Length];
for (int i = 0; i < m; i++) {
if (i == 0) {
result = data;
} else {
Array.Copy(result, hash, result.Length);
Array.Copy(data, 0, hash, result.Length, data.Length);
result = hash;
}
for (int j = 0; j < c; j++) {
result = md5.ComputeHash(result);
}
Array.Copy(result, 0, k, i * HASHLENGTH, result.Length);
}
var dk = new byte[24]; //final key
Array.Copy(k, dk, dk.Length);
return dk;
}
When we decode the body, we can use create RSACryptoServiceProvider class from it to be used by our SslStream. Oh, yeah, some crazy math here:
using (var ms = new MemoryStream(body)) {
using (var reader = new BinaryReader(ms)) {
try {
var tb = reader.ReadUInt16(); // LE: x30 x81
if (tb == 0×8130) {
reader.ReadByte(); // fw 1
} else if (tb == 0×8230) {
reader.ReadInt16(); // fw 2
} else {
return null;
}
tb = reader.ReadUInt16(); // version
if (tb != 0×0102) {
return null;
}
if (reader.ReadByte() != 0×00) {
return null;
}
var MODULUS = _readInt(reader);
var E = _readInt(reader);
var D = _readInt(reader);
var P = _readInt(reader);
var Q = _readInt(reader);
var DP = _readInt(reader);
var DQ = _readInt(reader);
var IQ = _readInt(reader);
var result = new RSACryptoServiceProvider();
var param = new RSAParameters {
Modulus = MODULUS,
Exponent = E,
D = D,
P = P,
Q = Q,
DP = DP,
DQ = DQ,
InverseQ = IQ
};
result.ImportParameters(param);
return result;
} catch (Exception ex) {
Log.Exception(ex);
} finally {
reader.Close();
}
}
}
Some helper methods to read bytes and we done:
private static Func<BinaryReader, byte[]> _readInt = r => {
var s = _getIntSize(r);
return r.ReadBytes(s);
};
private static Func<BinaryReader, int> _getIntSize = r => {
byte lb = 0×00;
byte hb = 0×00;
int c = 0;
var b = r.ReadByte();
if (b != 0×02) { //int
return 0;
}
b = r.ReadByte();
if (b == 0×81) {
c = r.ReadByte(); //size
} else
if (b == 0×82) {
hb = r.ReadByte(); //size
lb = r.ReadByte();
byte[] m = { lb, hb, 0×00, 0×00 };
c = BitConverter.ToInt32(m, 0);
} else {
c = b; //got size
}
while (r.ReadByte() == 0×00) { //remove high zero
c -= 1;
}
r.BaseStream.Seek(-1, SeekOrigin.Current); // last byte is not zero, go back;
return c;
};
We are done. All we have to do now is to construct our private key and pack it for SslStream use. For this purpose, we have X509Certificate big brother X509Certificate2.
var cert = new X509Certificate2(File.ReadAllBytes(“myCert.pem”)) {
PrivateKey = FromPem(Encoding.ASCII.GetString(File.ReadAllBytes(“myKey.pem”)),
_sslPrivateKeyPasskey)
};
Now when you supply cert as the client certificate, SslStream will use private key for outgoing stream encryption, provide public key for remote incoming stream encryption and certificate for remote side identification.
We are done. Be good people and subscribe to our dev blog, it promised to be one of the most interesting blogs for those who is not satisfied with the way Windows works and want to pimp it a bit.
P.S. If, in case, you got an invitation from Microsoft Israel to participate in “Be what’s next” event next Wednesday 22nd. It is highly recommended to come and see me (and other large ISVs) speak about solutions we did. If you did not get an invitation, and you are a Microsoft partner, please contact local DPE guys. This is for certain ISVs and only by invitations.
发表评论
I think other web site proprietors should take this web site as an model, very clean and wonderful user friendly style and design, as well as the content. You are an expert in this topic!
Wow! Thank you! I always wanted to write on my site something like that. Can I include a fragment of your post to my website?
pretty useful stuff, overall I consider this is really worth a bookmark, thanks
What as Happening i am new to this, I stumbled upon this I ave found It absolutely useful and it has helped me out loads. I hope to contribute & help other users like its aided me. Good job.
Thank you ever so for you post.Thanks Again. Awesome.
I truly appreciate this post. I have been looking everywhere for this! Thank goodness I found it on Bing. You have made my day! Thank you again!
indeed, investigation is having to pay off. So happy to possess found this article.. of course, analysis is having to pay off. Wonderful thoughts you possess here..
Several thanks for the fantastic post C IaаАабТТаЂааАабТТаБТd fun reading it! That i really like this weblog.
Wow! This can be one particular of the most useful blogs We have ever arrive across on this subject. Basically Magnificent. I am also an expert in this topic therefore I can understand your hard work.
Very good post.Really looking forward to read more. Great.
Your style is really unique in comparison to other people I ave read stuff from. Thanks for posting when you ave got the opportunity, Guess I all just book mark this blog.
Terrific post but I was wanting to know if you could write
Some times its a pain in the ass to read what website owners wrote but this web site is rattling user genial !.
Whispering Misty So sorry you can expect to miss the workshop!
Well I really liked studying it. This post procured by you is very effective for proper planning.
Thanks so much for the article post.Really looking forward to read more. Really Cool.
Wow! This could be one particular of the most useful blogs We have ever arrive across on this subject. Actually Excellent. I am also an expert in this topic so I can understand your effort.
Really informative blog article.Really thank you! Keep writing.
wow, awesome blog.Much thanks again. Much obliged.
This is one awesome post.Thanks Again. Awesome.
That is a very good tip particularly to those new to the blogosphere. Short but very accurate information Thanks for sharing this one. A must read article!
I value the blog post.Really looking forward to read more. Keep writing.
Way cool! Some very valid points! I appreciate you writing this post plus the rest of the website is extremely good.
thank you for this post, I am a big fan of this site would like to go along updated.
FUduyi magnificent points altogether, you simply gained a brand new reader. What would you recommend in regards to your post that you made some days ago? Any positive?
This is one awesome article.Really thank you! Will read on
This particular blog is no doubt cool additionally factual. I have picked up a bunch of helpful advices out of this amazing blog. I ad love to come back again and again. Thanks a lot!
There as certainly a great deal to find out about this issue. I really like all the points you made.
Very nice info and straight to the point. I don at know if this is actually the best place to ask but do you folks have any ideea where to employ some professional writers? Thanks in advance
Wow! I cant believe I have found your weblog. Extremely useful information.
Looking forward to reading more. Great article.Much thanks again. Really Great.
Recently, I did not give lots of consideration to leaving feedback on blog web page posts and have positioned comments even considerably less.
Of course, what a splendid blog and illuminating posts, I surely will bookmark your blog.All the Best!
Your style is really unique compared to other folks I ave read stuff from. Many thanks for posting when you have the opportunity, Guess I will just bookmark this blog.
Major thankies for the article.Thanks Again. Awesome.
Looking forward to reading more. Great article.Really thank you! Cool.
Spot on with this write-up, I actually believe this site needs far more attention. I all probably be returning to see more, thanks for the advice!
particular country of the person. You might get one
There is visibly a bundle to realize about this. I assume you made various nice points in features also.
This website certainly has all of the information and facts I wanted about this subject and didn at know who to ask.
Im grateful for the article post.Really looking forward to read more. Will read on
There is clearly a bundle to know about this. I assume you made certain nice points in features also.
Thank you for sharing this excellent piece. Very inspiring! (as always, btw)
Really informative blog.Really thank you! Cool.
I truly appreciate this post. I?аАТаЂаve been looking everywhere for this! Thank goodness I found it on Bing. You ave made my day! Thx again
Really informative article.Much thanks again. Want more.
Very good post. I certainly appreciate this website. Keep writing!
There is evidently a bundle to identify about this. I suppose you made certain good points in features also.
Im thankful for the blog.Thanks Again. Will read on
The action comedy Red is directed by Robert Schewentke and stars Bruce Willis, Mary Louise Parker, John Malkovich, Morgan Freeman, Helen Mirren, Karl Urban and Brian Cox.