简体   繁体   中英

Certificate signing produces different signature when on server

I am trying to sign some data using a certificate private key. The issue I'm finding is that the signature is different depending on if I'm executing it locally or on a server.

I'm using the following code as a test, running under the same user both locally and on the server:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace TestSignature
{
    class Program
    {
        static void Main(string[] args)
        {

            var key = SigningKeyFromCertificate(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, "thumbprint");
            var alg = CryptoConfig.MapNameToOID("SHA256");
            var data = Encoding.UTF8.GetBytes("test");
            var sig = key.SignData(data, alg);

            Console.WriteLine(Convert.ToBase64String(sig));

        }

        private static RSACryptoServiceProvider SigningKeyFromCertificate(StoreName storeName, StoreLocation storeLocation, X509FindType findType, string findValue)
        {
            X509Store store = new X509Store(storeName, storeLocation);
            store.Open(OpenFlags.ReadOnly);

            var certs = store.Certificates.Find(findType, findValue, false);
            if (certs?.Count > 0)
            {
                var cert = certs[0];
                if (cert.HasPrivateKey)
                {
                    // Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
                    var key = cert.PrivateKey as RSACryptoServiceProvider;
                    var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;
                    var parameters = new CspParameters(enhanced.ProviderType, enhanced.ProviderName, key.CspKeyContainerInfo.UniqueKeyContainerName);
                    return new RSACryptoServiceProvider(parameters);
                }
                else
                {
                    throw new Exception($"No private key access to cert '{findValue}.'");
                }
            }
            else
            {
                throw new Exception($"Cert '{findValue}' not found!");
            }
        }
    }
}

Locally, I get the following signature:

YUjspKhLl7v3u5VQkh1PfHytMTpEtbAftxOA5v4lmph3B4ssVlZp7KedO5NW9K5L222Kz9Ik9/55NirS0cNCz/cDhEFRtD4daJ9qLRuM8oD5hCj6Jt9Vc6WeS2he+Cqfoylnv4V9plfi1xw8y7EyAf4C77BGkXOdyP5wyz2Xubo=

On the server, I get this one instead:

u1RUDwbBlUpOgNNkAjXhYEWfVLGpMOa0vEfm6PUkB4y9PYBk1lDmCAp+488ta+ipbTdSDLM9btRqsQfZ7JlIn/dIBw9t5K63Y7dcDcc7gDLE1+umLJ7EincMcdwUv3YQ0zCvzc9RrP0jKJManV1ptQNnODpMktGYAq1KmJb9aTY=

Any idea of what could be different? I would think, with the same certificate, the same code, and the same data, the signature should be the same.

(The example is written in C# 4.5.2.)

You have some code to reopen the CAPI key handle under PROV_RSA_AES :

// Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
var key = cert.PrivateKey as RSACryptoServiceProvider;
var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;

var parameters = new CspParameters(
    enhanced.ProviderType,
    enhanced.ProviderName,
    key.CspKeyContainerInfo.UniqueKeyContainerName);

return new RSACryptoServiceProvider(parameters);

But key.CspKeyContainerInfo.UniqueKeyContainerName isn't the name of the key (it's the name of the file on disk where the key lives), so you're opening a brand new key (you're also generating a new ephemeral key just to ask what the default provider is). Since it's a named key it persists, and subsequent application executions resolve to the same key -- but a different "same" key on each computer.

A more stable way of reopening the key is

var cspParameters = new CspParameters
{
    KeyContainerName = foo.CspKeyContainerInfo.KeyContainerName,
    Flags = CspProviderFlags.UseExistingKey,
};

(since the provider type and name aren't specified they will use the defaults, and by saying UseExistingKey you get an exception if you reference a key that doesn't exist).


That said, the easiest fix is to stop using RSACryptoServiceProvider . .NET Framework 4.6 (and .NET Core 1.0) have a(n extension) method on X509Certificate2 , GetRSAPrivateKey() , it returns an RSA (which you should avoid casting) which is usually RSACng (on Windows), but may be RSACryptoServiceProvider if only CAPI had a driver required for a HSM, and may be some other RSA in the future. Since RSACng handles SHA-2 better there's almost never a need to "reopen" the return object (even if it's RSACryptoServiceProvider , and even if the type isn't PROV_RSA_AES (24), that doesn't mean the HSM will fail to do SHA-2).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM