How to validate an RSA public key? - c#

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.

Related

Create CSR with Bouncy Castle (C#) inclusive EC Curve Name

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

How does public and primary keys look like in rsa c#?

I am trying to use Asymmetric key algorithm rsa to secure my APIs.
suppose i create a Key pair for a user Who is using my APIs
as
RSA = new RSACryptoServiceProvider();
string PublicKey = RSA.ToXmlString(false);
string PrivateKey = RSA.ToXmlString(true);
now my private key look like
<RSAKeyValue>
<Modulus>tMvvUeHhggKAVex8JzYKXYQ32HVmr05PdtT1KV3kTkKE26jO/9IVmg+bWwxR1vuMzmY7spwguSJQsnjutJXamH0 mblNgYHmWwhyhJMSTtnZp57VDNNedjCFQnLOn211yk/PpCQHEiDDvt84hnmcdXNBlfZfkQzQ+UO/elhP5NH0=</Modulus>
<Exponent>AQAB</Exponent>
<P>22SSx3JpSqYVUWuxiSKwmh/8RDDcgDvq8l+4dMlQ/F+BJhthTQ3UJGupWaxiJyXX95AYAJIJJvWVvmvI7tqbxw==</P><Q>0va2WFy0oUwX4eJtZElRbot9TOrwGcqI64rMAYjvxl/mayCRXf0ypwKofKWOsmjK/pX0xWaqnFWB/NdLFt4Fmw==</Q><DP>VbIYPz2qcRUkmJQnWbiqINnDkONBDfnZkOjgxQVp09p+OONTA2UWa09+a9+Qy1fV3wZyya5BUu10m1fAucO8Ow==</DP>
<DQ>Lm8hOZfGJk6SXySwgUdmBhfrz3dSu8qJkpatSpUyeY54MBIuDOsDMCF0pmLmYryQGbM1+hEb8mcbwmQ84d6iiw==</DQ>
<InverseQ>L67OLIIK+M3OF1nxSHTjZ+Kv/hwOHJPvdHHSuh9VEmw93kPGn6Qt6GudEreFb6xlFsuR6UM19LUIweapgaUagQ==</InverseQ>
<D>YbaGlZ6bHoTzj3zMbPTMDVbUR+zLnpuYXwUhq0XPimxxGbbWiXSlsCoXMNIruSEjLLocMaAoH2bobkzl1jvXdAI30NdZ/rpG5SRl dpaeIundLfHkSnZfHwHuP9OGXJSXmbmLCrO8dq7BjLauS4JiTRgjiXoq8c995MEF+vhw9hE=</D>
</RSAKeyValue>
And this is my public key
<RSAKeyValue>
<Modulus>lF1Yof6FXaY9M/StHwPC1voW5K5JOqSu8BXndS4WH06L9mOfrNI9Et0cRaHjiYd2bQNFZB9+ AZmN4ODc36DgVPqQqYY2tbH+9UY0hcdi+WnDuUZe7sgvWKfGwBKTcy7g8uUAzpTWEP1W0FqeMunmXljD8Iouoqp5oHtTW1GHIlE=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
Now my doubt is here Private key string also contain public key
what part of private or public key string should i send to user as it is in
XmlFormat
and there are multiple tags like modulus ,D,Q in the string what is actually a key in the string which i send to a user as his public , pvt keys
as my user will be on android platform
will he be able to use those keys in xml format ?
like in c# when we have to use keys
i do rsa.FromXmlString(Key);
where public or private key is in XmlFormat
Can that user will able to do this on android as well

Tripledes from hardware

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.

Convert from RSACryptoServiceProvider to RSACng

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"))

Extractig public key value via PKCS#11

I am using Pkcs11 library to retreive key from device:
I am able to retreive ObjectHandle of public key. I try to extract public key value:
var publicKeyHandle = GetPublicKeyByLabel(Session, "KEY1_QAL_PUB");
var objectAttribute = Session.GetAttributeValue(publicKeyHandle, new List<CKA> { CKA.CKA_VALUE }).Single();
var keyVal = objectAttribute.GetValueAsByteArray();
Unfortunately, objectAttribute has CannotBeRead set to true and value cannot be read. Even when I am logged as user or SO.
I understand the case, when the key is private - due to security reasons I cannot get value outside HSM device. But why I cannot extract public key value?
CKA_VALUE is not a valid attribute for RSA public key objects. If you want to extract RSA public key value then you need to read CKA_PUBLIC_EXPONENT and CKA_MODULUS attributes. See PKCS#11 v2.20 for more details.

Categories