So you have an instance of an X509Certificate2
(or X509Certificate
) that you want to export as a byte array – and you want to exclude the private key – and encrypt the output using a password.
You have found the Export
method of the certificate class which takes one of the X509ContentType
enum values and an optional password. The MSDN help informs that you must choose between X509ContentType.Cert
, X509ContentType.SerializedCert
and X509ContentType.Pkcs12
for the export to work. You also find out (by experimenting or googling) that exporting using X509ContentType.Cert
produces a serialized certificate without the private key – just what you want! Hooray!
Now you specify a password and think you’re OK.
Fail. You are not. The password is actually ignored.
If you try this:
1 2 3 |
byte[] a = cert.Export(X509ContentType.Cert); byte[] b = cert.Export(X509ContentType.Cert, "P@ssw0rd #1"); byte[] c = cert.Export(X509ContentType.Cert, "P@ssw0rd #2"); |
The resulting byte arrays a
, b
and c
will have the exact same content, even though you specified different passwords! (And it behaves the same if you use the SecureString
class.)
Personally I would expect the Export
method to throw an exception when specifying X509ContentType.Cert
together with a password (other than null). That would give me, as a developer, a clear sign that I am trying to use an unsupported parameter combination which gives me a chance to try to figure out a work-around. As it is now I am lead to believe that the output content is in fact encrypted.
It is also possible to recreate the certificate again from the byte array giving any password:
1 2 |
var certX = new X509Certificate2(a, "P@ssw0rd #2"); var certY = new X509Certificate2(b, "correct horse battery staple"); |
Both certX
and certY
above will be correctly reconstructed.
Here is a simple solution you can use to export a certificate without its private key and encrypt the exported bytes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
byte[] ExportCertificate(X509Certificate certificate, string password, bool includePrivateKey) { if (!includePrivateKey) { // Export the certificate (temporarily) using the content type "Cert". // The exported cert will NOT include the private key -- but it will not // be encrypted (the given password will be ignored). So we are not there yet. byte[] exportedWithoutPrivateKey = certificate.Export(X509ContentType.Cert, ""); // Now recreate the certificate again from the exported bytes. // The recreated certificate will not contain a private key. certificate = new X509Certificate2( exportedWithoutPrivateKey, "", // The (ignored) password X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); } // Export the certificate using the PKCS #12 format. // This content type will include the private key among the exported bytes (if there is one). return certificate.Export(X509ContentType.Pkcs12, password); } |
Now calling this method, specifying two different passwords and asking not to include the private key…
1 2 |
byte[] d = ExportCertificate(cert, "P@ssw0rd #1", false); byte[] e = ExportCertificate(cert, "P@ssw0rd #2", false); |
…generates two byte arrays d
and e
that are different. Further on, if you try to recreate it you must specify the correct password.
1 2 |
var certZ = new X509Certificate2(d, "P@ssw0rd #1"); var fails = new X509Certificate2(d, "correct horse battery staple"); |
The certZ
will be correctly reconstructed, but the second try (with wrong password) will throw a CryptographicException
with the message “The specified network password is not correct.
”