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?
CodePudding user response:
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");
}
}
CodePudding user response:
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
.