I am currently using RSACryptoServiceProvider and I want to change to RSACng. I am using it to sign data. The reason for the change is that I am using Pkcs1 padding and I understand that Pss padding is preferred. We are undergoing security audits.
My question is how do I instantiate RSACng so that it uses the same private / public key each time?
With RSACryptoServiceProvider I am doing:
CspParameters cp = new CspParameters();
cp.KeyContainerName = "ContainerName";
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cp);
passing in the container name means it uses the key that persists in the in the container store on the machine.
With RSACng, I tried this, but I get an exception: "The requested operation is not supported"
RSACng RSA = new RSACng(CngKey.Create(CngAlgorithm.Sha256, ContainerName));
I just need to be able to pass the store key name so it uses the same key each time instead of generating a new key.
If you want to create a named/persisted RSA key with CNG:
private static RSA CreatePersistedRSAKey(string name, int keySizeInBits)
{
CngKeyCreationParameters creationParameters = new CngKeyCreationParameters
{
// This is what an ephemeral key would have had
// (allows ExportParameters(true) to succeed). Adjust as desired.
//
// The default is not exportable (only applies to the private key)
ExportPolicy =
CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport,
};
creationParameters.Parameters.Add(
new CngProperty(
"Length",
BitConverter.GetBytes(keySizeInBits),
CngPropertyOptions.Persist));
// RSACng will extract the data it needs from this key object,
// but doesn't take ownership
using (CngKey key = CngKey.Create(CngAlgorithm.Rsa, name, creationParameters))
{
return new RSACng(key);
}
}
This skips the parts where you would do a try/catch around a call to CngKey.Open, or might want to delete the key (open it with CngKey.Open, and call Delete on the CngKey instance).
(CngAlgorithm.Rsa was added in net46. If you're on an older version then an equivalent would be new CngAlgorithm("RSA"))
Related
I am trying to create a private key and add a certificate(self-signed or signed from a CA) where I should be able to export the certificate only and make the private key non-exportable in C#. That is, if someone tries to export the certificate from certmgr, the export option will be disabled like this picture-
I want the same non-exportable option programmatically in C# while creating it. The private key usually becomes non-exportable when a .pfx/.p12 file is installed using Crypto Shell Extensions when Mark this Key is exportable is unchecked.
I can successfully create key pairs and add certificate entries in the windows key store. But Yes, export the private key option always becomes enabled that is I can't restrict the private key from export. I have tried this -
public void init(){
AsymmetricCipherKeyPair asymmetricCipherKeyPair = GetKeyPair();
X509Name issuer = this.GenerateRelativeDistinguishedName("test org");
X509Name subject = this.GenerateRelativeDistinguishedName("test user1");
Org.BouncyCastle.X509.X509Certificate cert = GenerateCertificate(issuer, subject, asymmetricCipherKeyPair.Private, asymmetricCipherKeyPair.Public);
importSelfSignedCert(asymmetricCipherKeyPair, cert);
}
private AsymmetricCipherKeyPair GetKeyPair()
{
return new Pkcs1xHandler().GenerateKeyPair(Constants.RsaKeyLength.Length2048Bits);
}
protected X509Name GenerateRelativeDistinguishedName(String commonName)
{
IDictionary attributes = new Hashtable();
IList ordering;
attributes.Add(X509Name.CN, commonName);
ordering = new ArrayList(attributes.Keys);
return new X509Name(ordering, attributes);
}
protected void importSelfSignedCert(AsymmetricCipherKeyPair asymmetricCipherKeyPair, Org.BouncyCastle.X509.X509Certificate cert)
{
try
{
int ID =1;
AsymmetricCipherKeyPair ackp = asymmetricCipherKeyPair;
var rsaPriv = Org.BouncyCastle.Security.DotNetUtilities.ToRSA(ackp.Private as RsaPrivateCrtKeyParameters);
// Setup RSACryptoServiceProvider with "KeyContainerName" set to "KeyContainer"+ enrollmentID
var csp = new CspParameters();
csp.KeyContainerName = "TestPrivKey" + ID;
csp.Flags |= CspProviderFlags.UseMachineKeyStore;
var rsaPrivate = new RSACryptoServiceProvider(csp);
// Import private key to windows keystrore, from already generated BouncyCastle rsa privatekey
rsaPrivate.ImportParameters(rsaPriv.ExportParameters(true));
//Console.Write("rsaprivate key:" + rsaPrivate.ToXmlString(true));
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2();
var flags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet;
certificate.Import(cert.GetEncoded(), String.Empty, flags);
certificate.PrivateKey = rsaPrivate;
// opening up the windows cert store because thats where I want to save it.
System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store(System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser);
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.MaxAllowed);
store.Add(certificate);
store.Close();
rsaPrivate.PersistKeyInCsp = true; //persisting the key in container is important to retrieve the key later
///make non exporable
csp.Flags = CspProviderFlags.UseNonExportableKey;
var rsaPrivate2 = new RSACryptoServiceProvider(csp);
rsaPrivate2.ExportParameters(false); //restrict to export
rsaPrivate2.PersistKeyInCsp = true;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("Error : " + e);
Console.WriteLine(e);
Log.Print(LogLevel.High, e.ToString());
}
}
And when I try to export the certificate, I get the option to Yes, export the private key too, like the below image -
Is there any way to make the private-key non-exportable like the first image while creating it programmatically? I would grateful for any hints, references or code samples. Thanks.
To make the private key non-exportable, the CspProviderFlags.UseNonExportableKey must additionally be set when importing the key:
...
var csp = new CspParameters();
csp.KeyContainerName = "TestPrivKey" + ID;
csp.Flags |= CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseNonExportableKey; // Fix
var rsaPrivate = new RSACryptoServiceProvider(csp);
rsaPrivate.ImportParameters(rsaPriv.ExportParameters(true));
...
If this is done, the Yes, export the private key option is disabled in the wizard.
Note that in the posted code that flag is also set, but it is set too late, namely after the certificate has been saved to the store.
If I receive a PEM-encoded key, I can import it like so:
using var rsa = new RSACryptoServiceProvider(2048);
rsa.ImportFromPem(keyString.AsSpan());
// do stuff with it
However, the documentation for ImportFromPem states that it will indiscriminately accept four key types: PUBLIC KEY, PRIVATE KEY, RSA PRIVATE KEY, RSA PUBLIC KEY, and that "Unsupported or malformed PEM-encoded objects will be ignored."
If I want to verify that 1) what I received is a public key and not a private key, and 2) it's a valid public key and not an "Unsupported or malformed PEM-encoded object," how would I go about doing that?
The successful import with the posted code also means a formal validation of the RSA key. The key can be imported:
if it is an RSA key.
if the key contains one of the labels supported for RSA (i.e. PUBLIC KEY (SPKI), RSA PUBLIC KEY (public PKCS#1), PRIVATE KEY (PKCS#8), RSA PRIVTE KEY (private PKCS#1)).
if the body consists of a valid (Base64 encoded) ASN.1/DER.
if the label is consistent with the key contained in the body (both by type (public/private) and format (PKCS#1/PKCS#8/SPKI)).
There is no guarantee that the parameters are validated contentwise for consistency (e.g. in the case of a private key, the modulus might well not be equal to the product of p and q). Among other things, this depends on the platform (e.g. Windows or Unix). Thus, if guaranteed validation of the parameters is required, the parameters must be explicitly validated.
Explicit validation of the parameters is possible by exporting the RSA parameters with ExportParameters() and then validating them. This is relevant for private keys, where the parameters are dependent on each other, so that these consistencies can be tested (e.g. whether the modulus is equal to the product of the two primes). There are no such consistency checks for public keys.
Since this question is about public keys, the checks for private keys are not necessary (and could be omitted).
For a key imported this way, PublicOnly can be used to check whether it is a private or public key.
This makes it relatively easy to create a logic for RSA key validation, e.g.:
using System.Numerics;
using System.Security.Cryptography;
...
private static void ImportPublicKey(string key)
{
using var rsa = new RSACryptoServiceProvider();
try
{
rsa.ImportFromPem(key.AsSpan());
if (rsa.PublicOnly)
{
Console.WriteLine("Public RSA key");
}
else
{
// Explicit check of the consistency of the parameters for private keys, for example N = p*q
var rsaParams = rsa.ExportParameters(true);
BigInteger m = new BigInteger(rsaParams.Modulus, true, true);
BigInteger p = new BigInteger(rsaParams.P, true, true);
BigInteger q = new BigInteger(rsaParams.Q, true, true);
Console.WriteLine("Private RSA key - params " + (p*q==m ? "consistent" : "not consistent"));
}
}
catch
{
Console.WriteLine("Invalid or inconsistent RSA key");
}
}
First of all, it depends on why you want to validate the public key. You should not expect an adversary to deliberately send a bad key. If they can do that they can simply send you the wrong key. To prevent that you need a better way of managing keys, such as using certificates within a Public Key Infrastructure or PKI.
Great, that out of the way, I'd like to indicate that all the defined PEM keys contain a public key. RSA PUBLIC KEY is a PKCS#1 defined public key and RSA PRIVATE KEY is a PKCS#1 private key. However, the definition of the private key also contains the public exponent. Similarly, PUBLIC KEY is a SubjectPublicKeyInfo structure and PRIVATE KEY is a PKCS#8 defined private key. Both contain PKCS#1 key structures inside and therefore the public key.
Maybe you suspect that the public key in the instance is not replaced. That is certainly a possible option if I read the documentation correctly (the quality of the Microsoft documentation is often, uh, questionable though). In that case you might want to validate that the modulus changes value. The modulus is specific for each key pair and it is contained in both public and private keys. You can access the modulus using ExportParameters(false).Modulus.
I create a CSR with Bouncy Castle and sign it on HSM (EC Key). It works fine! But in the CSR content (and in the certificate later) there is no EC curve name. Instead, there are the parameters of the curve. Some clients have problems with this, e.g .: Phyton. On the screenshot you can see how OpenSSL does it. How can I force Bouncy Castle to write the curve name in the CSR?
The code for the Adding the Public Key to CSR:
IList oids = new ArrayList();
IList values = new ArrayList();
..........
SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(publicKey);
X509Extension ski = new X509Extension(true, new DerOctetString(subjectKeyIdentifier));
oids.Add(X509Extensions.SubjectKeyIdentifier);
values.Add(ski);
AttributePkcs attribute = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest,
new DerSet(new X509Extensions(oids, values)));
//End Extensions
// SIGN on HSM
Pkcs10CertificationRequestDelaySigned csr = new Pkcs10CertificationRequestDelaySigned(
signatureAlgorithmStr,
subject,
publicKey,
new DerSet(attribute)
);
......
Screenshot: CSR with Bouncy Castle und OpenSSL
The issue isn't with BouncyCastle, but rather how the public key is being generated / initialized.
You can either opt for named curve or as expanded group parameters, but not both.
Technically they are equivalent, but usage wise, they don't mix well with one another. So you will have to generate both if you want to cater to both of those scenarios.
I don't know how you are initializing the public key, but you can convert to a different public key type before signing.
AsymmetricKeyParameter publicKey = null;
// ... existing public key by some means
var castedPublicKey = (ECPublicKeyParameters)publicKey;
var newPublicKey = new ECPublicKeyParameters(
castedPublicKey.AlgorithmName
,castedPublicKey.Q,
X9ObjectIdentifiers.Prime256v1);
// ... rest of code
// SIGN on HSM
Pkcs10CertificationRequestDelaySigned csr = new Pkcs10CertificationRequestDelaySigned(
signatureAlgorithmStr,
subject,
newPublicKey,
new DerSet(attribute)
);
Change Prime256v1 to whatever curve you are using
using System;
using System.Security.Cryptography;
namespace SmartCardSign
{
class SCSign
{
static void Main(string[] args)
{
// To idendify the Smart Card CryptoGraphic Providers on your
// computer, use the Microsoft Registry Editor (Regedit.exe).
// The available Smart Card CryptoGraphic Providers are listed
// in HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
// Create a new CspParameters object that identifies a
// Smart Card CryptoGraphic Provider.
// The 1st parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider Types.
// The 2nd parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
CspParameters csp = new CspParameters(1, "Schlumberger Cryptographic Service Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
// Initialize an RSACryptoServiceProvider object using
// the CspParameters object.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
// Create some data to sign.
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
Console.WriteLine("Data : " + BitConverter.ToString(data));
// Sign the data using the Smart Card CryptoGraphic Provider.
byte[] sig = rsa.SignData(data, "SHA1");
Console.WriteLine("Signature : " + BitConverter.ToString(sig));
// Verify the data using the Smart Card CryptoGraphic Provider.
bool verified = rsa.VerifyData(data, "SHA1", sig);
Console.WriteLine("Verified : " + verified);
}
}
}
Is it possible to implement same with tripledes? I tried but cant find a solution.
Link: https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-access-hardware-encryption-devices
Generally you cannot. And the reason is relatively simple: symmetric ciphers, such as triple-DES are not very useful for person to person cryptography such as signature generation (or MAC for symmetric algorithms) or encryption.
The advantage of asymmetric ciphers is that you can distribute the encryption key to other persons. To do this the other person needs to be certain that they receive the correct key: the key needs to be trusted. For this a Public Key Infrastructure or PKI needs to be setup. Known PKI's are PGP and PKIX which is based on X.509 certificates - the same that are used for your HTTPS connections.
So triple DES on a smart card only makes sense if other entities can use the same key. This would be just to gain a tiny performance enhancement because you can encrypt with a public key as well, even in software. Then you can decrypt using the private key on the card. Generally a hybrid cryptosystem is used where a symmetric key such as a triple-DES key is established using RSA, after which the message is encrypted with that key.
That's not to say it is impossible - I've actually designed a PKCS#11 compliant smart card, but most PKCS#11 and CSP's implementations won't support 3DES, and then you would need to have a compatible smart card with a 3DES key as well. Also see the answer from BartonJS.
using (Pkcs11 pkcs11 = new Pkcs11("cryptoki.dll", true))
{
// Get list of available slots with token present
List<Slot> slots = pkcs11.GetSlotList(true);
// Find first slot with token present
Slot slot = slots[0];
// Open RO session
using (Session session = slot.OpenSession(true))
{
session.Login(CKU.CKU_USER, "userPin");
// Prepare attribute template that defines search criteria
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "TestKey"));
// Initialize searching
session.FindObjectsInit(objectAttributes);
// Get search results
List<ObjectHandle> foundObjects = session.FindObjects(2);
// Terminate searching
session.FindObjectsFinal();
ObjectHandle objectHandle = foundObjects[0];
byte[] iv = Encoding.UTF8.GetBytes("00000000");
byte[] inputData = Encoding.UTF8.GetBytes("data to encrypt.");
Mechanism mechanism = new Mechanism(CKM.CKM_DES3_CBC, iv);
byte[] result = session.Encrypt(mechanism, objectHandle, inputData);
Console.WriteLine(Convert.ToBase64String(result));
}
}
İs that possbile solution?
If have a TripleDES key on a hardware device and know the CNG KSP and key name, you should be able to use TripleDESCng:
using (TripleDES des3 = new TripleDESCng("YourKeyIdentifier", new CngProvider("Your KSP Name"))
{
// So long as you use CreateEncryptor() or CreateDecryptor() (the 0-argument ones)
// the operation is done on the hardware.
}
This, of course, requires that you have a hardware device that supports DES3-EDE.
Getting such a key onto the device is an exercise left to the reader.
Hi Iam using the OpenSSL .NET wrapper in my c# project. i want to generate an X509 certification but i don't really know the procedure. what should it contain (what parameters)...etc
this is my code, I did it after looking some tests:
OpenSSL.X509.X509Certificate x509 = new OpenSSL.X509.X509Certificate();
OpenSSL.Crypto.RSA rsa = new OpenSSL.Crypto.RSA();
rsa.GenerateKeys(1024, 0x10001, null, null);
OpenSSL.Crypto.CryptoKey key = new OpenSSL.Crypto.CryptoKey(rsa);
OpenSSL.Crypto.MessageDigestContext digest = new OpenSSL.Crypto.MessageDigestContext(
OpenSSL.Crypto.MessageDigest.SHA1);
I suppose that the certificate should take RSA private key and the digest as parameters and i have to configure it (date...and others parameters).
Can any one help me about that ? to finish my code ? thank you.
I use the following routine:
// Initialize the following with your information
var serial = 1234;
var issuer = new X509Name("issuer");
var subject = new X509Name("subject");
// Creates the key pair
var rsa = new RSA();
rsa.GenerateKeys(1024, 0x10001, null, null);
// Creates the certificate
var key = new CryptoKey(rsa);
var cert = new X509Certificate(serial, subject, issuer, key, DateTime.Now, DateTime.Now.AddYears(20));
// Dumps the certificate into a .cer file
var bio = BIO.File("C:/temp/cert.cer", "w");
cert.Write(bio);