Get Private Key from BouncyCastle X509 Certificate? C# - c#

Normally when I grab an X509Certificate2 out of my keystore I can call .PrivateKey to retrieve the cert's private key as an AsymmetricAlgorithm. However I have decided to use Bouncy Castle and its instance of X509Certificate only has a getPublicKey(); I cannot see a way to get the private key out of the cert. Any ideas?
I get the an X509Certificate2 from my Windows-MY keystore then use:
//mycert is an X509Certificate2 retrieved from Windows-MY Keystore
X509CertificateParser certParser = new X509CertificateParser();
X509Certificate privateCertBouncy = certParser.ReadCertificate(mycert.GetRawCertData());
AsymmetricKeyParameter pubKey = privateCertBouncy.GetPublicKey();
//how do i now get the private key to make a keypair?
Is there anyway to convert a AsymmetricAlgorithm(C# private key) to a AsymmetricKeyParameter(bouncycastle private key)?

Akp = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(this.Certificate.PrivateKey).Private;

Don't know BouncyCastle that much but it seems to me that the simple thing to do is to recreate the key based on the key parameters.
public static AsymmetricKeyParameter TransformRSAPrivateKey(
AsymmetricAlgorithm privateKey)
{
RSACryptoServiceProvider prov = privateKey as RSACryptoServiceProvider;
RSAParameters parameters = prov.ExportParameters(true);
return new RsaPrivateCrtKeyParameters(
new BigInteger(1,parameters.Modulus),
new BigInteger(1,parameters.Exponent),
new BigInteger(1,parameters.D),
new BigInteger(1,parameters.P),
new BigInteger(1,parameters.Q),
new BigInteger(1,parameters.DP),
new BigInteger(1,parameters.DQ),
new BigInteger(1,parameters.InverseQ));
}
You can call the code by using
AsymmetricKeyParameter bouncyCastlePrivateKey =
TransformRSAPrivateKey(mycert.PrivateKey);
Obviously this assumes that the certificate includes a RSA Key but the same result can be achieved for DSA with DSACryptoServiceProvider and DSAParameters.

Find .NET X509Certificate2:
X509Certificate2 cert = this.FindCertificate(certificateFriendlyName);
Parse it to BouncyCastle certificate and use X509Certificate2Signature to get signature:
var parser = new X509CertificateParser();
var bouncyCertificate = parser.ReadCertificate(cert.RawData);
var algorithm = DigestAlgorithms.GetDigest(bouncyCertificate.SigAlgOid);
var signature = new X509Certificate2Signature(cert, algorithm);

Related

I want to create a p12 file based on a cert file and a key file

Both the cert and the key file are of type string.
I tried using this:
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
rsaKey.ImportParameters(keyfile);
X509Certificate2 cert = new X509Certificate2(certfile);
cert.PrivateKey = rsaKey;
cert.Export(X509ContentType.Pkcs12, "xyz");
RestAsynchronicClient client = new RestAsynchronicClient(url, RestDataStandard.JSON, null, cert, logger);
Here I am getting conversion error in the second line. Conversion error from string to RSAParameter.
There's no automatic conversion from a file path, or file contents, to an RSAParameters; the RSACryptoServiceProvider is out of date and not recommended for new code, and the PrivateKey property on certificates is fully [Obsolete] in new versions of .NET.
With .NET 5+, this is easy:
byte[] pfxBytes;
using (X509Certificate2 cert = X509Certificate2.CreateFromPemFile(certFile, keyFile))
{
pfxBytes = cert.Export(X509ContentType.Pkcs12, pfxPwd);
}
Or, in the style closer to the code you've written:
byte[] pfxBytes;
using (X509Certificate2 cert = new X509Certificate2(certFile))
using (RSA key = RSA.Create())
{
key.ImportFromPem(File.ReadAllText(keyFile));
using (X509Certificate2 certWithKey = cert.CopyWithPrivateKey(key))
{
pfxBytes = certWithKey.Export(X509ContentType.Pkcs12, pfxPwd);
}
}
Your reference snippet then goes on to ignore the PFX/PKCS12 output and pass the cert to RestAsynchronicClient. Because of some idiosyncrasies on Windows, that generally won't work if you load the cert from this style. But, if you load the PFX into a new X509Certificate2 object, that'll be in a slightly different state and everything'll be happy.
RestAsynchronicClient client = new RestAsynchronicClient(
url,
RestDataStandard.JSON,
null,
new X509Certificate2(pfxBytes, pfxPwd),
logger);

RSACryptoServiceProvider in Node js

I have to sign a string in node js, in the same way of a C# application, which uses RSACryptoServiceProvider. In fact it uses
X509Certificate2 certificate = new X509Certificate2("file.pfx", "aPassword",
X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)RSACryptoServiceProvider.Create();
rsa.FromXmlString(certificate.PrivateKey.ToXmlString(true));
signer = new RSAPKCS1SignatureFormatter(rsa);
signer.SetHashAlgorithm("SHA256");
byte[] signedData = signer.CreateSignature("a string");
string signedString = Convert.ToBase64String(signedData);
I've tried to do the same in node js, so far, using jsonwebtoken in this way
var kdrPrivateKey = fs.readFileSync('private_key.pem');
var authorizationSigned = jwt.sign("a string", kdrPrivateKey, { algorithm: 'RS256'});
var authorizationBase64 = Base64.encode(authorizationSigned);
I also got the private_key from file.pfx in this way
openssl pkcs12 -in file.pfx -nocerts -out private_key.pem -nodes
My question, finally is, is the node js code equivalent to the C# code? If not, how can I make the C# code in a node js way?
Thanks!
Use RSA XML Decrypter for Node. Decrypt a string by private RSA XML C# key in Node.js.
var RSAXML = require('rsa-xml'),
// base64 encoded private rsa xml key
var privateKey1 = 'PFJTQUtleVZhbHVlPjxNb2R1bHVzPjUxbCtyaHRzRmQvQ3NOb0U5VW9kdWorS0Vqd0F2YWZUZmI1N3Zldit3b3ZRbjdoVURrdzlCbVVMOTdSSC9zaC9udVN2Qkl3RGRlVVZTZzJDaXo4bE5McmY0WTVlMmI1NUtNZVBzR3lIV29abXhpbkdQUzd1cjRLSkhPZmVCYStHeGRDOC80cFdCSjZFK3BCajNkQ2JQRFBLWVZ6N0RRTUhkWGNRWjRCcTR2OD08L01vZHVsdXM+PEV4cG9uZW50PkFRQUI8L0V4cG9uZW50PjxQPitRdkdsTE14anZMaHJFZi9ab011VklBR05xMXh6YUptcGZCa2E0dDNsY0RZR2xoUVI1OXZzWWFORGwzVTQzaVVZZ3JYa0NtbFVnRXB5QXBUS05hL3RRPT08L1A+PFE+N2M4NDNFZXRrM0pqeEFRRDFKUGg0QzFONkNyeCtkWDVjSXk1Z2xkY2Q3ODlYenJFU2dTUDhEWDd5U2puT2VXZmxEdmlyR0h6YVNXdmZWaTNKNXZBWXc9PTwvUT48RFA+T1JEYXN1NFFtQW5OYmpXZEx6YzE0WVRvWjVUOHM3clh2SVJGN21LcHh6WEdEdHRYb2VIRnJTOEFtVjhremU2dVNYemtnaE1ZMzU2R25XRElSMTVWMVE9PTwvRFA+PERRPkh5VC9kbUh3eXBtMWxTdE5jUjY0KzBvVHBPOVM1M3h0Z1o3OGdLUitXTFIwRGkrOUcxQ0RwVnI4a2JqSXA1MTZDOGpZQSttRUhtWXdHSU53NFVBVnJ3PT08L0RRPjxJbnZlcnNlUT5SVXowVDA4eDZSaHg4U2RMQ2dQVXNNenJKb29qQjZDTmR2MkpOcHNaN2Nnc1k1MDhEQjAwd29kQlFrem90SGJ0QVhTa1VsN2d0QXI0TGlFejVORU56Zz09PC9JbnZlcnNlUT48RD5JVmVPRmluMTZyUjIwREIrVjNCVGxzODlKeGRHWkxtbWF0c1prQXZPTkhGSERoc3RqaFAzRlpBRVBnZXUrcGdnZ0hZUDNVQVA2RWdDODBzUzB6TzB1T2h0UGIzNDllOSs2WnhlMjJhaWV0WTFabFlQT201di9YbE5HZlhOZWQvbjhUYUJZRHB3YnZTVUw0T2M1eFJOeWFnU2x4Mi9GN1h3NHBkQmw0cG9LZ1U9PC9EPjwvUlNBS2V5VmFsdWU+';
var encBase64_1 = '4TkhLlVNK27LQXSRIftGCbwOI2qnBGD0eR61g53KAdw5J+BTpczLpdWGn/9v3I6xGPQYvTB1F2cmbcuBboNGa18b+8gigwbat9vTEuLjD/OKl2V+jpqKf3xxwYYuz1s05HyV8KWxbS01M/iwjWPrcNRWh3vWff9pRAu8Z6KQAQc=';
var privateKey2 = 'PFJTQUtleVZhbHVlPjxNb2R1bHVzPjUxbCtyaHRzRmQvQ3NOb0U5VW9kdWorS0Vqd0F2YWZUZmI1N3Zldit3b3ZRbjdoVURrdzlCbVVMOTdSSC9zaC9udVN2Qkl3RGRlVVZTZzJDaXo4bE5McmY0WTVlMmI1NUtNZVBzR3lIV29abXhpbkdQUzd1cjRLSkhPZmVCYStHeGRDOC80cFdCSjZFK3BCajNkQ2JQRFBLWVZ6N0RRTUhkWGNRWjRCcTR2OD08L01vZHVsdXM+PEV4cG9uZW50PkFRQUI8L0V4cG9uZW50PjxQPitRdkdsTE14anZMaHJFZi9ab011VklBR05xMXh6YUptcGZCa2E0dDNsY0RZR2xoUVI1OXZzWWFORGwzVTQzaVVZZ3JYa0NtbFVnRXB5QXBUS05hL3RRPT08L1A+PFE+N2M4NDNFZXRrM0pqeEFRRDFKUGg0QzFONkNyeCtkWDVjSXk1Z2xkY2Q3ODlYenJFU2dTUDhEWDd5U2puT2VXZmxEdmlyR0h6YVNXdmZWaTNKNXZBWXc9PTwvUT48RFA+T1JEYXN1NFFtQW5OYmpXZEx6YzE0WVRvWjVUOHM3clh2SVJGN21LcHh6WEdEdHRYb2VIRnJTOEFtVjhremU2dVNYemtnaE1ZMzU2R25XRElSMTVWMVE9PTwvRFA+PERRPkh5VC9kbUh3eXBtMWxTdE5jUjY0KzBvVHBPOVM1M3h0Z1o3OGdLUitXTFIwRGkrOUcxQ0RwVnI4a2JqSXA1MTZDOGpZQSttRUhtWXdHSU53NFVBVnJ3PT08L0RRPjxJbnZlcnNlUT5SVXowVDA4eDZSaHg4U2RMQ2dQVXNNenJKb29qQjZDTmR2MkpOcHNaN2Nnc1k1MDhEQjAwd29kQlFrem90SGJ0QVhTa1VsN2d0QXI0TGlFejVORU56Zz09PC9JbnZlcnNlUT48RD5JVmVPRmluMTZyUjIwREIrVjNCVGxzODlKeGRHWkxtbWF0c1prQXZPTkhGSERoc3RqaFAzRlpBRVBnZXUrcGdnZ0hZUDNVQVA2RWdDODBzUzB6TzB1T2h0UGIzNDllOSs2WnhlMjJhaWV0WTFabFlQT201di9YbE5HZlhOZWQvbjhUYUJZRHB3YnZTVUw0T2M1eFJOeWFnU2x4Mi9GN1h3NHBkQmw0cG9LZ1U9PC9EPjwvUlNBS2V5VmFsdWU+';
var encBase64_2 = '4TkhLlVNK27LQXSRIftGCbwOI2qnBGD0eR61g53KAdw5J+BTpczLpdWGn/9v3I6xGPQYvTB1F2cmbcuBboNGa18b+8gigwbat9vTEuLjD/OKl2V+jpqKf3xxwYYuz1s05HyV8KWxbS01M/iwjWPrcNRWh3vWff9pRAu8Z6KQAQc=';
var rsa = new RSAXML();
// a private key
rsa.importKey(privateKey1, 'pKey1');
var decrypted1 = rsa.decrypt(encBase64_1, 'pKey1');
console.log('decrypted:', decrypted1);
// another private key
rsa.importKey(privateKey2, 'pKey2');
var decrypted2 = rsa.decrypt(encBase64_2, 'pKey2');
console.log('decrypted:', decrypted2);

How to create RSACryptoServiceProvide using string private key

I only have a private key. i want to use it for signing data in RSA algorithm.
i know how to use certificate to sign data. but i don't any way to use private key in string format to create and use RSACryptoServiceProvider
My code looks something like this.
string privateKey = "-----BEGIN PRIVATE KEY-----\nsometext\nsometext\nsometext\.....\n-----END PRIVATE KEY-----\n"
var rasAlg = new RSACryptoServiceProvider()// how can i use my private key here.
var signature = rasAlg.SignData(bytesArray, new SHA256CryptoServiceProvider());
EDIT
if any one can help me create X509Certificate then also it is fine.
i found bouncy castle library useful. i used bouncy castle pem reader to read it from string.
AsymmetricKeyParameter keyPair;
using (var reader = new StringReader(privateKey))
keyPair = (AsymmetricKeyParameter)new PemReader(reader).ReadObject();

signing with x509certificate that has signature algorithm sha256 [duplicate]

My application will take a set of files and sign them. (I'm not trying to sign an assembly.) There is a .p12 file that I get the private key from.
This is the code I was trying to use, but I get a System.Security.Cryptography.CryptographicException "Invalid algorithm specified.".
X509Certificate pXCert = new X509Certificate2(#"keyStore.p12", "password");
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)pXCert.PrivateKey;
string id = CryptoConfig.MapNameToOID("SHA256");
return csp.SignData(File.ReadAllBytes(filePath), id);
According to this answer it can't be done (the RSACryptoServiceProvider does not support SHA-256), but I was hoping that it might be possible using a different library, like Bouncy Castle.
I'm new to this stuff and I'm finding Bouncy Castle to be very confusing. I'm porting a Java app to C# and I have to use the same type of encryption to sign the files, so I am stuck with RSA + SHA256.
How can I do this using Bouncy Castle, OpenSSL.NET, Security.Cryptography, or another 3rd party library I haven't heard of? I'm assuming, if it can be done in Java then it can be done in C#.
UPDATE:
this is what I got from the link in poupou's anwser
X509Certificate2 cert = new X509Certificate2(KeyStoreFile, password");
RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PrivateKey;
CspParameters cspParam = new CspParameters();
cspParam.KeyContainerName = rsacsp.CspKeyContainerInfo.KeyContainerName;
cspParam.KeyNumber = rsacsp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2;
RSACryptoServiceProvider aescsp = new RSACryptoServiceProvider(cspParam);
aescsp.PersistKeyInCsp = false;
byte[] signed = aescsp.SignData(File.ReadAllBytes(file), "SHA256");
bool isValid = aescsp.VerifyData(File.ReadAllBytes(file), "SHA256", signed);
The problem is that I'm not getting the same results as I got with the original tool. As far as I can tell from reading the code the CryptoServiceProvider that does the actual signing is not using the PrivateKey from key store file. Is that Correct?
RSA + SHA256 can and will work...
Your later example may not work all the time, it should use the hash algorithm's OID, rather than it's name. As per your first example, this is obtained from a call to CryptoConfig.MapNameToOID(AlgorithmName) where AlgorithmName is what you are providing (i.e. "SHA256").
First you are going to need is the certificate with the private key. I normally read mine from the LocalMachine or CurrentUser store by using a public key file (.cer) to identify the private key, and then enumerate the certificates and match on the hash...
X509Certificate2 publicCert = new X509Certificate2(#"C:\mycertificate.cer");
//Fetch private key from the local machine store
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach( X509Certificate2 cert in store.Certificates)
{
if (cert.GetCertHashString() == publicCert.GetCertHashString())
privateCert = cert;
}
However you get there, once you've obtained a certificate with a private key we need to reconstruct it. This may be required due to the way the certificate creates it's private key, but I'm not really sure why. Anyway, we do this by first exporting the key and then re-importing it using whatever intermediate format you like, the easiest is xml:
//Round-trip the key to XML and back, there might be a better way but this works
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
Once that is done we can now sign a piece of data as follows:
//Create some data to sign
byte[] data = new byte[1024];
//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));
Lastly, the verification can be done directly with the certificate's public key without need for the reconstruction as we did with the private key:
key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
throw new CryptographicException();
The use of privateKey.toXMLString(true) or privateKey.exportParameters(true) aren't usable in a secure environment, since they require your private key to be exportable, which is NOT a good practice.
A better solution is to explicitly load the "Enhanced" crypto provider as such:
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName);
privKey = new RSACryptoServiceProvider(cspparams);
This is how I dealt with that problem:
X509Certificate2 privateCert = new X509Certificate2("certificate.pfx", password, X509KeyStorageFlags.Exportable);
// This instance can not sign and verify with SHA256:
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
// This one can:
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(privateKey.ExportParameters(true));
byte[] data = Encoding.UTF8.GetBytes("Data to be signed");
byte[] signature = privateKey1.SignData(data, "SHA256");
bool isValid = privateKey1.VerifyData(data, "SHA256", signature);
I settled on changing the key file to specify the appropriate Crypto Service Provider, avoiding the issue in .NET altogether.
So when I create a PFX file out of a PEM private key and a CRT public certificate, I do it as follows:
openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx
The key part being -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider".
(-inkey specifies the private key file and -in specifies the public certificate to incorporate.)
You may need to tweak this for the file formats you have on hand. The command line examples on this page can help with that:
https://www.sslshopper.com/ssl-converter.html
I found this solution here:
http://hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/
Use can use this on more recent frameworks.
public byte[] GetSignature(byte[] inputData)
{
using (var rsa = this.signingCertificate.GetRSAPrivateKey())
{
return rsa.SignData(inputData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
public bool ValidateSignature(byte[] inputData, byte[] signature)
{
using (var rsa = this.signingCertificate.GetRSAPublicKey())
{
return rsa.VerifyData(inputData, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
The signingCertificate above is a X509Certificate2 with a private key. This method does not require you to import any existing keys and works in a secure environment.
When you use a certificate to get your RSACryptoServiceProvider it really matters what's the underlying CryptoAPI provider. By default, when you create a certificate with 'makecert', it's "RSA-FULL" which only supports SHA1 hashes for signature. You need the new "RSA-AES" one that supports SHA2.
So, you can create your certificate with an additional option: -sp "Microsoft Enhanced RSA and AES Cryptographic Provider" (or an equivalent -sy 24) and then your code would work without the key juggling stuff.
Here is how I signed a string without having to modify the certificate (to a Microsoft Enhanced RSA and AES Cryptographic provider).
byte[] certificate = File.ReadAllBytes(#"C:\Users\AwesomeUser\Desktop\Test\ServerCertificate.pfx");
X509Certificate2 cert2 = new X509Certificate2(certificate, string.Empty, X509KeyStorageFlags.Exportable);
string stringToBeSigned = "This is a string to be signed";
SHA256Managed shHash = new SHA256Managed();
byte[] computedHash = shHash.ComputeHash(Encoding.Default.GetBytes(stringToBeSigned));
var certifiedRSACryptoServiceProvider = cert2.PrivateKey as RSACryptoServiceProvider;
RSACryptoServiceProvider defaultRSACryptoServiceProvider = new RSACryptoServiceProvider();
defaultRSACryptoServiceProvider.ImportParameters(certifiedRSACryptoServiceProvider.ExportParameters(true));
byte[] signedHashValue = defaultRSACryptoServiceProvider.SignData(computedHash, "SHA256");
string signature = Convert.ToBase64String(signedHashValue);
Console.WriteLine("Signature : {0}", signature);
RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = cert2.PublicKey.Key as RSACryptoServiceProvider;
bool verify = publicCertifiedRSACryptoServiceProvider.VerifyData(computedHash, "SHA256", signedHashValue);
Console.WriteLine("Verification result : {0}", verify);
According to this blog it should work with FX 3.5 (see note below). However it's important to recall that most of .NET cryptography is based on CryptoAPI (even if CNG is being more and more exposed in recent FX releases).
The key point is that CryptoAPI algorithm support depends on the Crypto Service Provider (CSP) being used and that varies a bit between Windows versions (i.e. what's working on Windows 7 might not work on Windows 2000).
Read the comments (from the blog entry) to see a possible workaround where you specify the AES CSP (instead of the default one) when creating your RSACCryptoServiceProvider instance. That seems to work for some people, YMMV.
Note: this is confusing to many people because all the released .NET frameworks includes a managed implementation of SHA256 which cannot be used by CryptoAPI. FWIW Mono does not suffer from such issues ;-)
I know this is an old thread but for those still stuck in the past and looking for an answer, the following worked for me based off #BKibler's answer. The comments stated it's not using the correct key and it's because the solution is missing a couple key settings.
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
if (!Enum.TryParse<KeyNumber>(privKey.CspKeyContainerInfo.KeyNumber.ToString(), out var keyNumber))
throw new Exception($"Unknown key number {privKey.CspKeyContainerInfo.KeyNumber}");
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName)
{
KeyNumber = (int)keyNumber,
Flags = CspProviderFlags.UseExistingKey
};
privKey = new RSACryptoServiceProvider(cspparams);
You need to set both "KeyNumber" and "Flags" so the existing (non-exportable) key is used and you can use the public key from the certificate to verify.
I have noticed similar issues in .NET with the wrong private key being used (or was it flat-out errors? I do not recall) when the certificate I am working with is not in the user/computer certificate store. Installing it into the stored fixed the problem for my scenario and things started working as expected - perhaps you can try that.

Bouncy Castle C# - Password Protect key

I can decrypt a password protected PKCS8 DER key with the following code:
MemoryStream ms = new MemoryStream(privateKey);
AsymmetricKeyParameter keyparams = Org.BouncyCastle.Security.PrivateKeyFactory.DecryptKey(password.ToCharArray(), ms);
RSAParameters rsaparams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyparams);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaparams);
return rsa;
Now, I have to recreate the same type of key when it is given to me in a different format (in this example it was given to me as a PFX file). So I have to create a password protected PKCS8 DER key from the PFX private key. After reading the Bouncy Castle source code, I managed to find the PrivateKeyFactory.EncryptKey function, but I can't get it to work. The code I have is the following:
X509Certificate2 cert = new X509Certificate2(pfx_bytes, password,X509KeyStorageFlags.Exportable);
var pkey = cert.PrivateKey;
var bcCert = DotNetUtilities.FromX509Certificate(cert);
var bcPkey = DotNetUtilities.GetKeyPair(pkey).Private;
return PrivateKeyFactory.EncryptKey(Org.BouncyCastle.Asn1.DerObjectIdentifier.Der, password.ToCharArray(), Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()), 10, bcPkey);
When I run the previous code, I get the exception "System.ArgumentException: attempt to use non-PBE algorithm with PBE EncryptedPrivateKeyInfo generation".
Google searches reveal nothing except the source code for the function, and though I've tried to follow it to find the solution I haven't been able to.
Can someone please point me in the right direction as to how I could use the function to create a password protected PKCS8 DER key from a standard .net Private key?
The first argument to PrivateKeyFactory.EncryptKey is supposed to identify an algorithm to encrypt with. The simplest way is to give the ObjectIdentifier (OID) of a standard PBE algorithm e.g. PKCSObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc instead of DerObjectIdentifier.Der . You could take a look at PbeUtilities class if you want to see what other algorithms are available.
PBE algorithms supported by PBEUtil:
PBEwithMD2andDES-CBC, PBEwithMD2andRC2-CBC, PBEwithMD5andDES-CBC, PBEwithMD5andRC2-CBC, PBEwithSHA1andDES-CBC, PBEwithSHA1andRC2-CBC, PBEwithSHA-1and128bitRC4, PBEwithSHA-1and40bitRC4, PBEwithSHA-1and3-keyDESEDE-CBC, PBEwithSHA-1and2-keyDESEDE-CBC, PBEwithSHA-1and128bitRC2-CBC, PBEwithSHA-1and40bitRC2-CBC, PBEwithHmacSHA-1, PBEwithHmacSHA-224, PBEwithHmacSHA-256, PBEwithHmacRIPEMD128, PBEwithHmacRIPEMD160, and PBEwithHmacRIPEMD256.
Example:
private static string EncryptPrivateKey(AsymmetricKeyParameter privateKey)
{
var encKey = PrivateKeyFactory.EncryptKey("PBEwithSHA1andDES-CBC", "test".ToCharArray(),
new byte[256], 1, privateKey);
return Convert.ToBase64String(encKey);
}

Categories