Certificate's private key is exported as a different value? - c#

I have created a self-signed-certificate (pfx) for testing purposes.
I'm able to export ist public key via :
X509Certificate2 cer = new X509Certificate2();
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificateCollection = store.Certificates.Find(...);
var cert = certificateCollection[0] ;
Console.WriteLine(Convert.ToBase64String(cert.Export( X509ContentType.Cert ), Base64FormattingOptions.InsertLineBreaks));
Result :
MIIDFTCCAf2gAw...........eFUpBB9C0/UNRmD7EAg==
This is consistent with openssl command :
$ openssl pkcs12 -in domain.name.pfx -clcerts -nokeys -out domain.name.crt
Result :
-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAw........9C0/UNRmD7EAg==
-----END CERTIFICATE-----
However , when I export the private key via C# :
Convert.ToBase64String(cert.Export( X509ContentType.Pfx ), Base64FormattingOptions.InsertLineBreaks)
I get :
MIIDFTCCAf2gAw............OVeFUpBB9C0/UNRmD7EAg==
While with openssl command :
$ openssl pkcs12 -in domain.name.pfx -nocerts -nodes -out domain.name.key
I get :
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w.........6HNjF2h7uuFdvbH2VAVg=
-----END PRIVATE KEY-----
Question:
Why do I get different results in the private keys ? And how can I fix my C# code to yield the same result as openssl's result ?
(NB - I'm not talking about -----begin/end keys----- boundries , but the actual value.
Additional info , the PFX file

The X509ContentType.Cert representation is the same because the certificate is a static structure. If you export it in a loop you'll always get the same answer.
The X509ContentType.Pfx representation is different because there are random salts in the PFX structure. One for each certificate, one for each private key, and one final one. If you export the same public+private pair as a PFX in a loop it will be different every time (288 bits of random data).
The end of the base64 data is different because it contains the last salt and the MAC over the rest of the data (including the encrypted cert salt and the encrypted key salt). It also contains an integer work factor, which Windows chooses as 2000 and OpenSSL chooses as 2048; further differentiating the OpenSSL and Windows exports.
So what you are seeing is by design. If you need a stable export you'll have to do something else. If you were just concerned that they're different, it's almost certainly fine.

Why do I get different results in the private keys ?
You are trying to do two separate things. With openssl you are using pfx file that contains private key and certificate(s). In c# you are using some representation of certificate with private key and you are doing export to a pfx file!
And how can I fix my C# code to yield the same result as openssl's result ?
C# has no friendly function that could export only private key (in openssl-like way) from X509Certificate2. As #darksquirell42 mentioned in comments you could use X509Certificate2.PrivateKey property, then cast it to RSACryptoServiceProvider and do ToXmlString(true). But this will give you private key parameters in a xml structure. Then you would have to take each parameter and construct an ASN.1 structure PKCS#1 or (IMHO better) PKCS#8. But C# has by default no friendly methods to work with ASN.1 objects.
OR you could use some crypto library to do it for you like BouncyCastle. This method from BouncyCastle could be helpful.

Related

Create a X509Certificate2 object using PEM with password in .NET Framework

I really don't understand how certificates work and can't find much information on how to create a X509Certificate2 object in .NET Framework. I need the certificate object so that I can add it to an Http handler. I have three items: a cert.pem file, a key.pem file and a password that I provided when I created the files. The files where generated from running this command:
> "C:\Program Files\Git\usr\bin\openssl.exe" req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365
The console then asked me to provide a "PEM pass phrase" and I entered it. Now I was able to create a X509Certificate2 object in .NET 6, but cannot find a way to do the same in .NET Framework.
This code works in .NET 6, but I have no idea how to do the same thing in .NET Framework 4.6.1.
public static X509Certificate2 CreateCertificate(string publicCert, string privateCert, string password = #"P#ssw0rd1")
{
byte[] publicPemBytes = Encoding.UTF8.GetBytes(publicCert);
using var publicX509 = new X509Certificate2(publicPemBytes);
var privateKeyBlocks = privateCert.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
RSA rsa = RSA.Create();
if (privateKeyBlocks[0] == "BEGIN ENCRYPTED PRIVATE KEY")
{
rsa.ImportFromEncryptedPem(privateCert, password);
}
X509Certificate2 keyPair = publicX509.CopyWithPrivateKey(rsa);
return keyPair;
}
The variable publicCert has the raw value of the cert.pem file (the one that start with -----BEGIN CERTIFICATE-----. The variable privateCert has the raw value of the key.pem file (the one that start with -----BEGIN ENCRYPTED PRIVATE KEY-----.

Unable to load a Password Encrypted Pem

I am able to load an unencrypted PEM file with RSA.ImportFromPem() with no issues, but I am not able to load a PEM file when it's password encrypted.
Here is the code I am attempting to use.
var pwRSA = RSA.Create();
pwRSA.ImportFromEncryptedPem(pwPemKey, "password1");
I am getting the following exception:
HResult=0x80070057
Message=No supported key formats were found. Check that the input represents the contents of a PEM-encoded key file, not the path to such a file. (Parameter 'input')
Source=System.Security.Cryptography.Algorithms
StackTrace:
at Internal.Cryptography.PemKeyImportHelpers.ImportEncryptedPem[TPass](ReadOnlySpan`1 input, ReadOnlySpan`1 password, ImportEncryptedKeyAction`1 importAction)
at System.Security.Cryptography.RSA.ImportFromEncryptedPem(ReadOnlySpan`1 input, ReadOnlySpan`1 password)
at RSAEncryptionTest.Program.Main(String[] args) in C:\source\tests\RSAEncryptionTest\Program.cs:line 57
Given this is just a test, I don't mind sharing the private key.
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,F7922A7F105CC716E9A19297BAC0BE49
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
-----END RSA PRIVATE KEY-----
Any help is greatly appreciated.
As mentioned by Topaco above, the key is to convert the private key into PKCS#8 format. Since no one else included it here, here's the OpenSSL command I used to accomplish this:
openssl pkcs8 -inform PEM -topk8 -in private_key.pem -out private_key2.pem
After doing this I was able to import private_key2.pem using ImportFromEncryptedPem

C# BouncyCastle PKCS#8

I want load the PEM using .net framework (not .netcore)
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIHs........................................................CAAw
DAYI........................................................gZAf
Y/Iu........................................................X7DZ
ZKoE........................................................OYQQ
3ZST........................................................A2E=
-----END ENCRYPTED PRIVATE KEY-----
I tried to use the following code using BouncyCastle, but it throw PemException:
"problem creating ENCRYPTED private key: Org.BouncyCastle.Crypto.InvalidCipherTextException: pad block corrupted"
class Passowrd : IPasswordFinder
{
private string v;
public Passowrd(string v)
{
this.v = v;
}
public char[] GetPassword()
{
return v.ToCharArray();
}
}
var pemReader = new PemReader(new StringReader(privateKeyText), new Passowrd("PASSWORD"));
var pemObj = pemReader.ReadObject(); // this line throw PemException
However, I load the exact same PEM file using .netcore3.1 by the following code:
var ecdsa = ECDsa.Create();
ecdsa.ImportEncryptedPkcs8PrivateKey(passSpan, privateKeyBytes, out _);
You can read the PEM either using
var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(new StringReader(privateKeyText), new Passowrd("PASSWORD"));
or
var pemReader = new Org.BouncyCastle.Utilities.IO.Pem.PemReader(new StringReader(privateKeyText));
then read content like
var pemObject = pemReader.ReadPemObject();
The problem seems to be related to the encryption of the key: The key cannot be read with the PemReader if the option -v2prf hmacWithSHA1 is not set when the key is encrypted.
The option -v2prf exists since V1.1.0 and specifies the PRF (pseudo-random function) algorithm used with PKCS#5 v2.0 or the key derivation function PBKDF2.
Case 1: Creating a new key
The following two statements create and encrypt the key:
openssl ecparam -name secp256r1 -genkey -noout -out <out-path>
openssl pkcs8 -topk8 -v2 aes256 -v2prf hmacWithSHA1 -in <in-path> -out <out-path> // aes256 as of OpenSSL 1.1.0 inclusive
A key generated in this way can be successfully read with the PemReader. However, if the option -v2prf hmacWithSHA1 is missing, the exception is thrown.
Case 2: Fixing an already generated key
The following statement decrypts an already encrypted key:
openssl pkcs8 -topk8 -nocrypt -in <in-path> -out <out-path>
If the key is subsequently encrypted with the -v2prf hmacWithSHA1 option set (see above), the key can be read successfully with the PemReader. This way I was able to fix the posted key so that it could be read with the PemReader!
A possible (unfortunately only superficial) explanation can be found in the documentation of openssl pkcs8. There it can be read about the -v2prf option:
Some implementations may not support custom PRF algorithms and may require the hmacWithSHA1 option to work.
Interestingly, the critical keys can be read without problems using ECDsa#ImportEncryptedPkcs8PrivateKey from .NET Core 3.x. For a complete explanation a look into the source code would probably be necessary.

How to create a X509Certificate2 from crt and key files? [duplicate]

I have a .crt certificate and a .key private key file on a Linux machine. The private key is in encrypted PKCS#8 format (BEGIN ENCRYPTED PRIVATE KEY...). I would like to import these into an X509Certificate2 object for further use. Since we're on Linux, we're using .NET Core 2.2 (we cannot migrate to 3.0 yet).
I have explored a few possible solutions, detailed below:
Use openssl to convert the files to a .pfx and import that using X509Certificate2
I do not want to use this option since I don't want to execute shell code from within C#. I would like the solution to be completely programmatically achieved in C#.
Use the C# BouncyCastle libraries to do either:
A conversion of both the certificate and the key to .pfx (as above), or
Importing the certificate and private key separately and using X509Certificate2.CopyWithPrivateKey() to combine them.
However, I cannot find an API for the C# version of BouncyCastle, so I'm not sure what methods I could possibly use to do this.
Some other programmatic method in C# that I'm missing here
Essentially, the end goal is to obtain an X509Certificate2 object from the .crt and .key files. Any help/insight into what approach to use, or even a pointer to helpful BouncyCastle documentation, would be much appreciated. Thanks!
This is possible, though not as friendly as it could be, in .NET Core 3.0:
private static byte[] UnPem(string pem)
{
// This is a shortcut that assumes valid PEM
// -----BEGIN words-----\nbase64\n-----END words-----
const string Dashes = "-----";
int index0 = pem.IndexOf(Dashes);
int index1 = pem.IndexOf('\n', index0 + Dashes.Length);
int index2 = pem.IndexOf(Dashes, index1 + 1);
return Convert.FromBase64String(pem.Substring(index1, index2 - index1));
}
...
string keyPem = File.ReadAllText("private.key");
byte[] keyDer = UnPem(keyPem);
X509Certificate2 certWithKey;
using (X509Certificate2 certOnly = new X509Certificate2("certificate.cer"))
using (RSA rsa = RSA.Create())
{
// For "BEGIN PRIVATE KEY"
rsa.ImportPkcs8PrivateKey(keyDer, out _);
certWithKey = certOnly.CopyWithPrivateKey(rsa);
}
using (certWithKey)
{
Console.WriteLine(certWithKey.HasPrivateKey);
}
RSA private keys can be in three different formats, and you need to call the correct import for each one:
"BEGIN PRIVATE KEY": ImportPkcs8PrivateKey
"BEGIN ENCRYPTED PRIVATE KEY": ImportEncryptedPkcs8PrivateKey
"BEGIN RSA PRIVATE KEY": ImportRSAPrivateKey
In .NET 5 this got as easy as can be:
var certificate =
X509Certificate2.CreateFromPemFile(
crtFile,
Path.ChangeExtension(crtFile, "key"));

Converting PGP public keys to RSA Public key XML Format

I am faced with a problem on converting PGP Public keys to RSA Public Key format. Following public key in PGP format that need to be converted to XML format.
Please help me, if any of you had encountered such scenarios.
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.1 (MingW32)
mQELBE2kxxwBCADjWSW/F/h9VkV9JizFwKXPzg1YlXyEmN/dGKTVBidOI3ZcrhQd
v3qhcWtNmBXz1BpO+YLy41AfJzysKNHrs23sMh8nKWlMSmKZZqVpimXCcCrKQ8Hi
uNNxcRN/hBdy5R4MN/N62hbFTgoZpGmrQjOXDSPozDeckQE7DjkGYvQAT63dT3jC
F99im/KC2GfHDxVh70+881ot+vUxKC6GRZAz6wth90M/5FzbKQt0GmFrq/XapqxK
ZQI5LLMzahdGzJTGEbfs8SlH6OAqZsgjgEKYaW3EooQVbrvGrdqna6PBkLbroCOl
EH/h6W9h42dFLb2OSV9OES7oidQATqzNC+oXAAYptE9CdWNrIENvbnN1bHRhbnRz
IC0gTXkgTWVkaWNhcmUgQWR2b2NhdGUgKE1NQSkgPHByb2RzdXBwb3J0QGJ1Y2tj
b25zdWx0YW50cy5jb20+iQE0BBMBAgAeBQJNpMccAhsDBgsJCAcDAgMVAgMDFgIB
Ah4BAheAAAoJEJrpFtACLTdGtKEH/3G4pB4UeiEsMwDYDO1YF4jkz2lqbItmCGek
WuVg7G1LGHgZm2cJVH0xSuTKY4WpaX1RX5iWhVgz0dSrDNkjOi3hlZJ49VzXZi8T
abQ+Uiqfu7QIWvSOmjhxdrl8FuC2ZpZLRWB93VFNGKM1qYrjphTW1uMCnU4WtkD0
e8d9M3VCC/uL72a0+0R2El80W55yZxvHD6JHlp82Gdn+sqsAEnUcg5c+USLcehgc
v931SE4VfcxW4PELcOp0gKb00Q4bWp09Is/NVR+kVUax5EaT2Wo5oe7cx8OgrzMs
5daypwibGoFCloFE4LH59hbahoM5s8Ya59YAYEBWA1u68DCBYqS5AQsETaTH1gEI
AMCvsxsZcm5kIdbXATuPJQ1PKDejeeF0t4f27vDqR9SOZKas2ExpWbmjSeF9EdJ5
e1QKm88/WSXeUuE4Sn2xBfZXfRvhWndQIs6pOi8licuht1WiuYWBlvPefMO1e15G
fHrghhQ4RcTLIG8SWDcVFbsnr92RDy8NV7qZd/N08dZ+IS7z2JV8hp8Ts0a9T01q
p1h6lCbr9CAABQmlihMQ+hM4LO9DjqDeR85I1NWNpODeoks8zx86S9xOlEBxmWg2
G90ahBxm3IsYW9KtDd+oro6s6GQroo+5C0bcHyVL6Dylmhkn4CYQ99WAzMtw3gQ8
IDFrLO7Vw1kMc+vR4LGMPpkABimJAR8EGAECAAkFAk2kx9YCGwwACgkQmukW0AIt
N0ZBxggAqLFdkvVHgsK8wx6WyZalRpimirVQxStPT8WWlGyg8zJIGxbAzC6f4Fml
IuXGQYse+D8LfL7leWCyGu71+IZb22XCQ7ZO2ogWl6txUO5jWgPkoPBi0XMK/tLu
pT1WP8/lzrh1XQgwvLME+dcTMeuqgV5uDUmhn2OX9j8N3iyZeOV84cFrYmgrNfHn
lHlGx2kyXhgUwfK6noIUljprImIjlntGL0sh6lxTiu5bWMpXJVeJdRjNXuf4G+2/
mLU7BfsNP5jDr3+isTTLcOlZR0PHiAywIFPA2VOMTscB6nNEHiu8FLNFnY+QgboT
uC4E2095FYSXRMXc/rif7+d5+NApAg==
=+Q6Q
-----END PGP PUBLIC KEY BLOCK-----
This need to be converted to RSAKeyValue XML format like the one below.
<RSAKeyValue>
<Modulus>151Vw6Kq51bpblahblahblahWCkPmD345ncclzFrmeb9fjpE+B6VsIAqP3kahu00p33BmFpDpXOcgi uRC2En0HoGqyZB9ifvNlPhT7flZ1uVuC9JtywG3FmYRsqbpJuT7VTIAHYExU6T/FyRuyIbhdUVwhue7ARaObsEWszE1IoM7+XcYYs=</Modulus>
<Exponent>AQAB</Exponent>
<P>+7OU6YErvfk84O/2bX5s2753xE+pdiyJl3j8gy4zMh4UKeKVhyxgSJJCvNTl9XVIya6xnCWPkHVy8ylNqyLiHQ==</P>
<Q>20v7/nODe/jKcbsiYEwzGXp0ZyiG17JqzOBKrDFu6bwPiAEHzZln0CYfUn0E hJdCmetaq9WFXk27mbK3Z6aBxw==</Q>
<DP>RpoN6B26dlRGFlZZCkePI2he0esCfsppYdqnAejgpWs4 frM2/C6fZ1vXan5RbrOSUjg1S7ZhecToFHr+dUWYpQ== </DP>
<DQ>ayaHbF7YwfxgPo3nUBIrbamNstH0gPQmhxCYq+ZDgWV oWVXXkDgCp4vZBh8b1AVsf3SmCwv7bIaiwYi3YKfVBQ==</DQ
<InverseQ>YjvieMYPEDdUHqSZRWAsFlEESd3NczFCrbBaS HzbsbOZeDrccvUfoOP2jeIhuMt0qQwMqXY/NJau9Ldwdx7xNw==</InverseQ>
<D>wEg6bM5LVSWoQgWpoldz5dQWfQjqQVc8WzKpDA HQ8ejsm0ug4ZwRcRRnKZs3o7OTsiAJCfjqT9ij1gKMoeLrXIM1 RDOcAYBxI0qwWdRrr3sbar7ntMYmy4tlTTf8/G+ypw+CJi1gz2umswcx4MHhNTUGTUHSmjEjDTFfh3sW64k= </D>
</RSAKeyValue>
While generally it is possible to extract key material from an OpenPGP RSA key (note that OpenPGP also gets use of DSS/Elgamal keys), your example is somewhat inconsistent, as it states public OpenPGP key and private RSA/XML key. So I think it makes sense to sort these things out before you continue (as it is impossible to get private RSA key from a public RSA key).
Finally got what I was looking for from this SO post.
BouncyCastle RSAPrivateKey to .NET RSAPrivateKey

Categories

Resources