Read Private Key from PFX-file
Asked Answered
A

6

9

I know, there are many posts about this, but still I cannot find a solution to get this to work. I have generated a PFX-file with openssl on my machine like this:

openssl x509 -req -days 365 -in "myReqest.csr" -signkey "myPrivateKey.pem" -out "myCertificate.crt"
openssl pkcs12 -export -out "myCertificate.pfx" -inkey "myPrivateKey.pem" -in "myCertificate.crt" -certfile "myCertificate.crt"

In my C# app, I access the private key like this:

var cert = new X509Certificate2("myCertificate.pfx", "myPassword");
cert.HasPrivateKey; // This is always true!
cert.PrivateKey; // Works on my machine (only)

This works perfectly fine (on my machine), but when I run the same code on another machine, it throws: "Key set not found", even though HasPrivateKey returns true! Shouldn't the private key be included in the *.pfx-file? Can you tell me:

  1. Was the certificate/private key somehow automatically installed on my machine by openssl when I created it?

  2. How can I read the private key from the *.PFX-file (or alternatively from the *.PEM-file)?

StackTrace of Exception:

at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContaier)
at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContaier, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(CspParameters parameter)
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()

Update:

I've found out, that the following does work:

// on my machine
// read certificate from file (exportable!)
X509Certificate2 cert = new X509Certificate2("filename.pfx", "password", X509KeyStorageFlags.Exportable)
// sign data etc.
((RSACryptoServiceProvider)cert.PrivateKey).SignData(...
// export private key to XML-file
File.WriteAllText("filename.xml", cert.PrivateKey.ToXmlString(true));

// on the other machine
// create new RSA object
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
// import private key from xml
rsa.FromXmlString(File.ReadAllText("filename.xml"));
// verify data etc.
rsa.VerifyData(...

However, to me, this is only a workaround, I would like to do it an a more conventional/standard compliant way!

Alecalecia answered 25/11, 2013 at 16:30 Comment(4)
Wild guess: it's a private key generated on your machine, so your machine only can use it (I abused italics for emphasis)Compendious
are you also running the openssl commands on the other machine? (all you mentioned was "code").Woodworm
@Compendious This is not very helpful. I thought the private key was supposed to be included in the certificate file, since it resided in a separate file which I created beforehand and which I included explicitly when creating the certificate with openssl.Alecalecia
@PeterRitchie Correct, I am only running the code on the other machine. That's what I wanted to do - to provide the private key to the app by handing out the certificate file containing it.Alecalecia
A
5

It seems, there is no straight-forward way to do this in .NET. Therefore I've decided now to load the certificate directly from the certificate store:

X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByThumbprint, CERTIFICATE_THUMB_PRINT, false);
if (certificates.Count == 0)
{
    // "Certificate not installed."
}
else
{
    certificate = certificates[0];
}
store.Close();

For that, of course, if has to be installed on the machine.

I think this is a nice solution to this problem, because it adds an additional layer of security to it (the app must be run on the machine, on which the certificate is installed and as the user who installed it, also the file itself can be stored in a safe place somewhere else).

Alecalecia answered 28/11, 2013 at 14:55 Comment(2)
How would you then extract the private key?Reenter
@Reenter certificate.PrivateKey should work, I guess. Note that with this approach, the private key is handled by the windows cert store.Alecalecia
S
6

You have to load the certificate like this:

X509Certificate2 cert = new X509Certificate2("a.pfx", "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet)

This post explains why http://paulstovell.com/blog/x509certificate2

Sociopath answered 6/9, 2016 at 19:17 Comment(0)
A
5

It seems, there is no straight-forward way to do this in .NET. Therefore I've decided now to load the certificate directly from the certificate store:

X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByThumbprint, CERTIFICATE_THUMB_PRINT, false);
if (certificates.Count == 0)
{
    // "Certificate not installed."
}
else
{
    certificate = certificates[0];
}
store.Close();

For that, of course, if has to be installed on the machine.

I think this is a nice solution to this problem, because it adds an additional layer of security to it (the app must be run on the machine, on which the certificate is installed and as the user who installed it, also the file itself can be stored in a safe place somewhere else).

Alecalecia answered 28/11, 2013 at 14:55 Comment(2)
How would you then extract the private key?Reenter
@Reenter certificate.PrivateKey should work, I guess. Note that with this approach, the private key is handled by the windows cert store.Alecalecia
F
2

Perhaps you can tell us a bit more about why you want to do this. There certainly are good reasons to want to do this, such as writing a program that would sit on your company's internal server to automate product builds.

But if you intend to distribute this application outside of a high trust zone (e.g. to customers) then the answer is DO NOT DO IT! You should never give out your private key file. That opens it up to brute force attack of your password, (which is certainly much weaker than the private key itself). And if you distribute your application, then your password is included in plain text in the MSIL code. There it can be easily viewed using any managed code disassembler (e.g. Reflector) , and it can probably even be viewed with a text or hex editor.

In summary, distributing your private key file along with your application to someone allows them to easily sign anything they want with YOUR cert. The whole point of a private key is to keep it safely locked away in a location where it can never be accessed by anyone but you (or your organization, etc).

Febrifugal answered 17/3, 2014 at 14:18 Comment(1)
such as writing a program that would sit on your company's internal server to automate product builds ... you got it, not more to say here. The other things you mentioned, I know already. However, as you can see from my answer, I have found a better solution.Alecalecia
H
1

The default key set doesn't exist on the other machine (The user key set is usually the default), probably because it's an asp.net application (i.e. it has no user profile). If you pass X509KeyStorageFlags.MachineKeySet as the third argument to the X509Certificate2 constructor, then it should work in the same way on both machines.

The reason it happens only when accessing the PrivateKey property is that it's the first place where an actual CSP object is created to use the key.

Harriot answered 25/11, 2013 at 16:47 Comment(3)
This didn't help. Also, it's not an ASP.NET app.Alecalecia
Maybe X509KeyStorageFlags.Exportable works then? The exception clearly indicates a problem with the key set. You can combine Exportable with MachineKeySet or UserKeySet also.Harriot
It doesn't I tried all that. The problem seems to be sth completely different.Alecalecia
L
0

This worked for me:

// Loading the pfx file with passphrase
x509certificate2 cert = new x509certificate2("d:\\mycer.pfx", "123456789", x509keystorageflags.exportable);

// Getting the private key from the pfx file
// https://www.asptricks.net/2016/09/how-to-export-private-key-from.html
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(rsa);
var myCAprivateKey = keyPair.Private;
Lynnett answered 30/12, 2022 at 12:4 Comment(0)
E
-1

You can also obtain the private through the below method.

System.Security.Cryptography.X509Certificates.X509Certificate2 certificate = LoadCertificate("Certificate.pfx", "PasswordofCertificate");

RSACryptoServiceProvider key = certificate.PrivateKey as RSACryptoServiceProvider;

From certificate variable, you can also obtain other information such as Public Key etc.

Epigraph answered 17/5, 2018 at 7:35 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.