How can I compare public keys in .NET?

2020-07-27 23:45发布

问题:

I've got an X509Certificate2 containing a public key. I've got an RSACryptoServiceProvider (which came from calling SignedXml.CheckSignatureReturningKey), also containing a public key.

I want to find out if one came from the other. How can I compare the two?

回答1:

You can compare the PublicKey property of signing certificates in the SignedXml.KeyIfo with signing key output from SignedXml.CheckSignatureReturningKey. This C# extension method does the job for me:

public static bool CheckSignatureReturningCertificate(this SignedXml signedXml, out X509Certificate2 signingCertificate)
{
    signingCertificate = null;
    AsymmetricAlgorithm signingKey;
    bool isValid = signedXml.CheckSignatureReturningKey(out signingKey);
    if (isValid)
    {
        IEnumerable<X509Certificate2> keyInfoCertificates =
            signedXml.KeyInfo.OfType<KeyInfoX509Data>()
                .SelectMany(x => x.Certificates.Cast<X509Certificate2>());

        signingCertificate = keyInfoCertificates.FirstOrDefault(x => x.PublicKey.Key == signingKey);
        if (signingCertificate == null)
        {
            throw new Exception("Signing certificate not found in KeyInfo.");
        }
    }

    return isValid;
}

Use it like this:

X509Certificate2 signingCertificate = null;
bool isValid = signedXml.CheckSignatureReturningCertificate(out signingCertificate);
if(isValid)
{
    // signingCertificate now contains the certificate used to sign
}


回答2:

The public key parameters for the RSA algorithm are {e, n}, the exponent and the modulus. In .NET, these are available from the RSAParameters struct. The other fields represent the private key.

So, to compare an X509Certificate2 and an RSACryptoServiceProvider for public key equality, you can just grab these parameters:

AsymmetricAlgorithm signingKey;
bool signatureIsVerified = signedXml.CheckSignatureReturningKey(out signingKey);

var certificateParameters =
    ((RSA)certificate.PublicKey.Key).ExportParameters(
        includePrivateParameters: false);
var signingParameters = signingKey.ExportParameters(
        includePrivateParameters: false);
bool areEqual =
    ByteArrayEquals(certificateParameters.Exponent,
                    signingParameters.Exponent)
    && ByteArrayEquals(certificateParameters.Modulus,
                    signingParameters.Modulus);

You'll have to implement ByteArrayEquals, because there's no good way to do it in .NET.

If you're using DSA rather than RSA, the public key is made up of {p, q, g, y}.