Get public key from private key in Bouncy Castle C# - c#

So I have an encrypted private key PEM. I can read it and get the private key with the following:
AsymmetricKeyParameter key;
using (var sr = new StringReader(pem))
using (var pf = new PassowrdFinder { Password = password })
{
var reader = new PemReader(sr, pf);
key = (AsymmetricKeyParameter)reader.ReadObject();
}
I also need the public key, to create the SPKI later on. I tried
var keyPair = new AsymmetricCipherKeyPair(key, key);
Which fails with System.ArgumentException: Expected a public key Parameter name: publicParameter.
My question is: How to get the public key from a private key?

Thanks to help from James K Polk, here is what I came up with
AsymmetricCipherKeyPair GetKeyPairFromPrivateKey(AsymmetricKeyParameter privateKey)
{
AsymmetricCipherKeyPair keyPair = null;
if (privateKey is RsaPrivateCrtKeyParameters rsa)
{
var pub = new RsaKeyParameters(false, rsa.Modulus, rsa.PublicExponent);
keyPair = new AsymmetricCipherKeyPair(pub, privateKey);
}
else if (privateKey is Ed25519PrivateKeyParameters ed)
{
var pub = ed.GeneratePublicKey();
keyPair = new AsymmetricCipherKeyPair(pub, privateKey);
}
else if (privateKey is ECPrivateKeyParameters ec)
{
var q = ec.Parameters.G.Multiply(ec.D);
var pub = new ECPublicKeyParameters(ec.AlgorithmName, q, ec.PublicKeyParamSet);
keyPair = new AsymmetricCipherKeyPair(pub, ec);
}
if (keyPair == null)
throw new NotSupportedException($"The key type {privateKey.GetType().Name} is not supported.");
return keyPair;
}

It should be very simple:
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)reader.ReadObject();
Then:
var pubKey = KeyPair.public;

I'm a little clumsy with the Bouncycastle C# library but I think the way to do this is to explicitly make a new key object using the appropriate components of the private key. Example
// Make an rsa keypair for testing
var rand = new SecureRandom();
var keyGenParams = new RsaKeyGenerationParameters(
new BigInteger("65537"), rand, 1024, 64
);
var rsaKeyGen = new RsaKeyPairGenerator();
rsaKeyGen.Init(keyGenParams);
var rsaKeyPair = rsaKeyGen.GenerateKeyPair();
var rsaPriv = (RsaPrivateCrtKeyParameters)rsaKeyPair.Private;
// Make a public from the private
var rsaPub = new RsaKeyParameters(false, rsaPriv.Modulus, rsaPriv.PublicExponent);
// Try it out
var rsaKeyPair2 = new AsymmetricCipherKeyPair(rsaPub, rsaPriv);
The downside of this approach is that it requires a concrete instance of a specific kind of asymmetric key; it does not work with the abstract asymmetric key classes.

Related

Converting from a Public Microsoft.IdentityModel.Tokens.JsonWebKey.JsonWebKey to RSAParameters (Public Key)

Essentially, I'm looking to convert a Public Microsoft.IdentityModel.Tokens.JsonWebKey.JsonWebKey to RSAParameters to then use in an RSA Instance. After this, i'm creating an Azure KeyVault JsonWebKey so I can import this key into my vault. I currently tried this, however have not gotten it to work. Any recommendations/shortcuts?
var jwk = new JsonWebKey(someStr); // IdentityModel.Tokens...
var rsaParams = new RSAParameters
{
Modulus = WebEncoders.Base64UrlDecode(jwk.N),
Exponent = WebEncoders.Base64UrlDecode(jwk.E)
};
var rsa = RSA.Create(rsaParams);
var key = new JsonWebKey(rsa); // Azure.Security.KeyVault.Keys
....
var kvKey = keyClient.ImportKey(keyName, key); // keyClient = KeyClient class
The error I am receiving from this request is:
RSA key is not valid - cannot instantiate crypto service
Try adding other rsa parameters and then importing into an RSACryptoServiceProvider. Then you can recover the SecurityKey.
The code below is an example of how to recover a SecurityKey from a previous RSASecurityKey stored in a JsonWebKey
using RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048);
JsonWebKey key = JsonConvert.DeserializeObject<JsonWebKey>(jsonwebkeystringcontent);
RSAParameters rsaParameters = new()
{
Modulus = WebEncoders.Base64UrlDecode(key.N),
Exponent = WebEncoders.Base64UrlDecode(key.E),
D = WebEncoders.Base64UrlDecode(key.D),
DP = WebEncoders.Base64UrlDecode(key.DP),
DQ = WebEncoders.Base64UrlDecode(key.DQ),
P = WebEncoders.Base64UrlDecode(key.P),
Q = WebEncoders.Base64UrlDecode(key.Q),
InverseQ = WebEncoders.Base64UrlDecode(key.QI)
};
provider.ImportParameters(rsaParameters);
SecurityKey Key = new RsaSecurityKey(provider.ExportParameters(true));

Generate key/Encryption/Decryption for RSACryptoProvider and BouncyCastle

Key generated through RSACryptoProvider is work for BouncyCastle Encryption (using publickey) / Decryption (using privatekey) ?
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
var pu = rsa.ToXmlString(false);
var pr = rsa.ToXmlString(true);
}
Also, how to generate key using BouncyCastle ?
Answer to first question, yes, RSA is a standard and it doesn't depends on the libraries used.
Second, try this:
public static void GetRsaKeyPair(out string privateXml, out string publicXml)
{
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
SecureRandom secureRandom = new SecureRandom(randomGenerator);
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, 1024);
var rsaKeyPairGenerator = new RsaKeyPairGenerator();
rsaKeyPairGenerator.Init(keyGenerationParameters);
AsymmetricCipherKeyPair rsaKeyPair = rsaKeyPairGenerator.GenerateKeyPair();
var privateRsaParameters = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)rsaKeyPair.Private);
using (RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(privateRsaParameters);
privateXml = rsaProvider.ToXmlString(true);
publicXml = rsaProvider.ToXmlString(false);
}
}

Diffie Hellman Key Exchange using ECDSA x509 certificates

I am trying to perform a Diffie-Hellman key exchange using 2 ECDSA x509 certificates.
Here is the method where I extract the keys from the certificates for computation of the derived key.
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = publicCertificate.GetECDsaPublicKey())
{
var privateParams = privateKey.ExportParameters(true); //This line is failing
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
I've commented on the line that is failing privateKey.ExportParameters(true) with the error:
System.Security.Cryptography.CryptographicException : The requested operation is not supported.
at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
at System.Security.Cryptography.ECCng.ExportParameters(CngKey key, Boolean includePrivateParameters, ECParameters& ecparams)
at System.Security.Cryptography.ECDsaCng.ExportParameters(Boolean includePrivateParameters)
Because this is a self signed certificate that I am generating, I assume I am doing something wrong.
I first create a root CA certificate and pass in the private key to sign my certificate.
private X509Certificate2 CreateECSDACertificate(string certificateName,
string issuerCertificateName,
TimeSpan lifetime,
AsymmetricKeyParameter issuerPrivateKey,
string certificateFriendlyName = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var signatureFactory = new Asn1SignatureFactory("SHA256WithECDSA", issuerPrivateKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDistinguishedName = new X509Name($"CN={certificateName}");
var issuerDistinguishedName = new X509Name($"CN={issuerCertificateName}");
certificateGenerator.SetSubjectDN(subjectDistinguishedName);
certificateGenerator.SetIssuerDN(issuerDistinguishedName);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.Add(lifetime);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
//key generation
var keyGenerationParameters = new KeyGenerationParameters(random, _keyStrength);
var keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
var certificate = certificateGenerator.Generate(signatureFactory);
var store = new Pkcs12Store();
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(certificateName, certificateEntry);
store.SetKeyEntry(certificateName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
X509Certificate2 x509;
using (var pfxStream = new MemoryStream())
{
store.Save(pfxStream, null, new SecureRandom());
pfxStream.Seek(0, SeekOrigin.Begin);
x509 = new X509Certificate2(pfxStream.ToArray());
}
x509.FriendlyName = certificateFriendlyName;
return x509;
}
The .HasPrivateKey() method returns true, which I've read can return a false positive.
When I add my certificates to the store, I can verify the cert chain.
[Test]
public void CreateSelfSignedCertificate_AfterAddingToStore_CanBuildChain()
{
var result = _target.CreateSelfSignedCertificate(_subject, _issuer, TimeSpan.FromDays(356), _certificateFriendlyName, _issuerFriendlyName);
_store.TryAddCertificateToStore(result.CertificateAuthority, _caStoreName, _location);
_store.TryAddCertificateToStore(result.Certificate, _certStoreName, _location);
var chain = new X509Chain
{
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck
}
};
var chainBuilt = chain.Build(result.Certificate);
if (!chainBuilt)
{
foreach (var status in chain.ChainStatus)
{
Assert.Warn(string.Format("Chain error: {0} {1}", status.Status, status.StatusInformation));
}
}
Assert.IsTrue(chainBuilt, "Chain");
}
I thought at first that maybe the private cert had to come from the cert store, so I imported it and then pulled it back out, but I get the same error, which is another reason I believe I'm not doing something quite right.
EDIT:
I have another class generating RSA x509's using the same code for putting the private key into the certificate. It allows me to export the RSA private key.
The variable _keyStrength is 384 and my signature factory is using "SHA256withECDSA". I have also tried using "SHA384withECDSA" but I get the same error.
OK. It's a blind shot but after looking at your code I noticed two things:
When you create PFX you set null password. But when you load the PFX into X509Certificate2 class you are using wrong constructor. You should use one with a password parameter and give a null into it
When you load PFX into X509Certificate2 class you do not specify, if the private key should be exportable. I think that this is the reason why privateKey.ExportParameters(true) gives you an exception. You should use this constructor and specify null as password
Made it working
I thought it was a bug. It's possible that it is. We clearly stated in X509Constructor that the private key should be exportable. I used X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable flags too. But when I looked at the CngKey it had ExportPolicy set to AllowExport but not AllowPlaintextExport.
It was exportable in some way. privateKey.Key.Export(CngKeyBlobFormat.OpaqueTransportBlob) worked. But privateKey.ExportParameters(true) did not.
I've searched for a solution how to change ExportPolicy of CngKey. I found this SO question that helped me to change it. After that the ExportParameters worked.
The fixed version of your GetDerivedKey method is
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = privateCertificate.GetECDsaPublicKey())
{
var myPrivateKeyToMessWith = privateKey as ECDsaCng;
// start - taken from https://stackoverflow.com/q/48542233/3245057
// make private key exportable:
byte[] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
CngProperty pty = new CngProperty(NCryptExportPolicyProperty, bytes, CngPropertyOptions.Persist);
myPrivateKeyToMessWith.Key.SetProperty(pty);
// end - taken from https://stackoverflow.com/q/48542233/3245057
var privateParams = myPrivateKeyToMessWith.ExportParameters(true); //This line is NOT failing anymore
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
I started using the solution #pepo posted which lead me to discover 'GetECDsaPrivateKey' does not return an ECDsa object but an ECDsaCng. I simplified the key derivation to this.
byte[] derivedKey;
using (var privateKey = (ECDsaCng)certificate.GetECDsaPrivateKey())
using (var publicKey = (ECDsaCng)certificate.GetECDsaPublicKey())
{
var publicParams = publicKey.ExportParameters(false);
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
using (var diffieHellman = new ECDiffieHellmanCng(privateKey.Key))
{
derivedKey = diffieHellman.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;

Bouncycastle - Generate PublicKey from PrivateKey

I have a simple task but I don't know how to do it. I want to generate a public key from a private key with Bouncycastle in C#.
using (var reader = new StringReader(privateKey))
{
var pemReader = new PemReader(reader);
// ...
}
I found a lot of examples that used this:
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
var publicKey = keyPair.Public;
But it throws an exception:
Unable to cast object of type 'Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters' to type 'Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair
What do I need to change to get it working?
Edit:
I used the example from #James K Polk and got it working.
using (var reader = new StringReader(privateKey))
{
var pemReader = new PemReader(reader);
var pemObject = pemReader.ReadObject();
var rsaPrivateCrtKeyParameters = (RsaPrivateCrtKeyParameters)pemObject;
var rsaKeyParameters = new RsaKeyParameters(false, rsaPrivateCrtKeyParameters.Modulus, rsaPrivateCrtKeyParameters.PublicExponent);
}
As stated in the comment by #dlatikay it is not necessarily true that a public key can be derived from a private key, so there is no GetPublic method or property available on an abstract private key. However, most private key classes contain enough information to easily derive the public key. Here is an example for Bouncycastle RSA private keys:
var rand = new SecureRandom();
var keyGenParams = new RsaKeyGenerationParameters( new BigInteger("65537"), rand, 1024, 64 );
var rsaKeyGen = new RsaKeyPairGenerator();
rsaKeyGen.Init(keyGenParams);
var rsaKeyPair = rsaKeyGen.GenerateKeyPair();
var rsaPriv = (RsaPrivateCrtKeyParameters) rsaKeyPair.Private;
// Make a public from the private
var rsaPub = new RsaKeyParameters(false, rsaPriv.Modulus, rsaPriv.PublicExponent);
Also, note that RsaPrivateCrtKeyParameters is a subclass of RsaKeyParameters so, depending on how you use it, it may be possible to use the RsaPrivateCrtKeyParameters instance directly where a public key is expected.

RSA Decryption using BouncyCastle with private PEM file not working

I'm doing some tests with BouncyCastle in C# and I want to encrypt some data and decrypt it later with a pair of keys that I have in my computer stored as PEM files.
public static string RSABouncyEncrypt(string content)
{
var bytesToEncrypt = Encoding.UTF8.GetBytes(content);
AsymmetricKeyParameter keyPair;
using (var reader = File.OpenText(#"C:\Users\Diego\Documents\public.pem")))
keyPair = (AsymmetricKeyParameter)new org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();
var engine = new RsaEngine();
engine.Init(true, keyPair);
var encrypted = engine.ProcessBlock(bytesToEncrypt, 0, bytesToEncrypt.Length);
var cryptMessage = Convert.ToBase64String(encrypted);
Logs.Log.LogMessage("encrypted: " + cryptMessage);
System.Windows.MessageBox.Show(cryptMessage);
//Decrypt before return statement to check that it has been encrypted correctly
RSADecrypt(cryptMessage);
return cryptMessage;
}
public static void RSADecrypt(string string64)
{
var bytesToDecrypt = Convert.FromBase64String(string64); // string to decrypt, base64 encoded
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(#"C:\Users\Diego\Documents\private.pem"))
keyPair = (AsymmetricCipherKeyPair)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();
var decryptEngine = new RsaEngine();
decryptEngine.Init(false, keyPair.Private);
var decrypted = Encoding.UTF8.GetString(decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length));
Logs.Log.LogMessage("decrypted: " + decrypted);
System.Windows.MessageBox.Show(decrypted);
}
The RSADecrypt function shows an error. when I show the message box after decrypting I get this:
���Z��8o>>���;;�/�Z�ב?���#�F��(͌5���o1I�,���4� S�W��)��w��x�4p�$-|А���&��Rv}�G��V�c ��&wU?
�D�� }E���O����7�n��!(e��E��$y�g9ςOأ��P�� �t�d�T�nN��K$�bQ��!�v���-�Hb���1���?����#B�y� r��Le�h=*Yr�w
�l�W|�嘟��|g��EV��#�[��M
which is definitely not what I encrypted. What am I doing wrong?
Actually the answer why it is not working is that there is no information about padding. Correct way how to instantiate RsaEngine is sth. like this
var decryptEngine = new Pkcs1Encoding(RsaEngine())
var bytesToDecrypt = Convert.FromBase64String(string64); // string to decrypt, base64 encoded
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(#"C:\Users\Diego\Documents\private.pem"))
keyPair = (AsymmetricCipherKeyPair)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();
var decryptEngine = new Pkcs1Encoding(RsaEngine());
decryptEngine.Init(false, keyPair.Private);
var decrypted = Encoding.UTF8.GetString(decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length));
Logs.Log.LogMessage("decrypted: " + decrypted);
System.Windows.MessageBox.Show(decrypted);
I reproduced this problem and it happened because you used a private key and a public key that don't match. In other words the message was encrypted with a private key (let's call it private_key_1) that came from one pair (private_key_1/public_key_1) but you tried to decrypt it with a public key (let's call it publick_key_2) that came from a different pair (private_key_2/public_key_2). Try to generate a new key pair and use it in your example e.g.:
var kpgen = new RsaKeyPairGenerator();
kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 1024));
var keyPair = kpgen.GenerateKeyPair();
using (var writer = new StreamWriter(File.OpenWrite(#"C:\Users\Diego\Documents\private2.pem")))
{
new PemWriter(writer).WriteObject(keyPair.Private);
}
using (var writer = new StreamWriter(File.OpenWrite(#"C:\Users\Diego\Documents\public2.pem")))
{
new PemWriter(writer).WriteObject(keyPair.Public);
}

Categories

Resources