C#验证数字签名

时间:2019-12-03 15:32:40

标签: c# rsa digital-signature

我正在尝试在API后面生成Xml文档,对其进行签名,并在Console应用程序中验证签名。 Xml和签名的创建似乎可以正常工作,但是签名未在客户端应用程序中得到验证。

在我的API控制器中,我发布了一个客户,该代码创建并保留了一个新的RSA公钥/私钥。该方法返回PublicKeyXml。我将客户ID和PublicKeyXml存储在我的客户测试应用中。客户ID位于appsettings.json中,而PublicKeyXml存储在文件中。

当客户端请求许可证时,将使用附加到端点URI中指定的客户的私钥对输出的Xml文档进行签名。

我的API:

=SUMIFS(T4:T50,[Helper Column],TRUE)

持久性函数如下:

namespace License.HostedApi.Controllers
{
    [ApiController]
    [Route("api")]
    public class LicenseController : ControllerBase
    {
        private readonly ILogger<LicenseController> _logger;
        private const string customerDataFileName = @"Data\Customers.json";
        private readonly Dictionary<string, string> customerDictionary;

        public LicenseController(ILogger<LicenseController> logger)
        {
            _logger = logger;

            if (System.IO.File.Exists(customerDataFileName))
            {
                Monitor.Enter(lockObject);
                string customerContent = System.IO.File.ReadAllText(customerDataFileName);
                Monitor.Exit(lockObject);
                if (string.IsNullOrWhiteSpace(customerContent))
                {
                    customerDictionary = new Dictionary<string, string>();
                }
                else
                {
                    customerDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(customerContent);
                }
            }
            else
            {
                customerDictionary = new Dictionary<string, string>();
            }
        }

        [HttpGet("ping")]
        public ActionResult Ping()
        {
            return Ok(true);
        }

        [HttpPost("customer")]
        public ActionResult<string> CreateCustomer([FromBody] string customerName)
        {
            if (string.IsNullOrWhiteSpace(customerName)) { return BadRequest(); }

            Customer customer = new Customer(customerName);

            AsymmetricEncryptionService asymmetricEncryptionService = new AsymmetricEncryptionService();
            int keySizeBits = 2048;

            AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = asymmetricEncryptionService.PersistNewAsymmetricKeyPair(keySizeBits);

            if (asymmetricKeyPairPersistenceResult.Success)
            {
                customerDictionary.Add(customer.GlobalId.ToString(), asymmetricKeyPairPersistenceResult.KeyContainerName);
                SaveCustomerDictionary();
                return Ok(asymmetricKeyPairPersistenceResult.PublicKeyXml);
            }
            else
            {
                return StatusCode(500, "Persistence of asymmetric key failed.");
            }
        }

        [HttpGet("license/{customerId}")]
        public ActionResult<string> Get(string customerId)
        {
            if (customerDictionary.ContainsKey(customerId))
            {
                CspParameters cspParams = new CspParameters();
                cspParams.KeyContainerName = customerDictionary[customerId];

                RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.PreserveWhitespace = false;
                xmlDoc.LoadXml("<license><mode>Trial</mode><features><feature>All</feature></features></license>");

                SignedXml signedXml = new SignedXml(xmlDoc);
                signedXml.SigningKey = rsaKey;
                Reference reference = new Reference();
                reference.Uri = string.Empty;
                XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
                reference.AddTransform(env);
                signedXml.AddReference(reference);
                signedXml.ComputeSignature();
                XmlElement xmlDigitalSignature = signedXml.GetXml();
                xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));

                MemoryStream stream = new MemoryStream();
                XmlWriterSettings settings = new XmlWriterSettings
                {
                    Async = false,
                    Indent = true
                };
                stream.Position = 0;
                var xWriter = XmlWriter.Create(stream, settings);
                xmlDoc.WriteContentTo(xWriter);
                xWriter.Flush();
                stream.Position = 0;

                return Ok(Encoding.UTF8.GetString(stream.ToArray()));
            }
            else
            {
                return NotFound($"Customer {customerId} not found");
            }
        }

        private object lockObject = new object();
        private void SaveCustomerDictionary()
        {
            Monitor.Enter(lockObject);
            string customerJson = JsonConvert.SerializeObject(customerDictionary, Newtonsoft.Json.Formatting.Indented);
            System.IO.File.WriteAllText(customerDataFileName, customerJson);
            Monitor.Exit(lockObject);
        }
    }
}

测试客户端:

public AsymmetricKeyPairPersistenceResult PersistNewAsymmetricKeyPair(int keySizeBits)
{
    AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = new AsymmetricKeyPairPersistenceResult();
    try
    {
        CspParameters cspParams = new CspParameters();
        string containerName = Guid.NewGuid().ToString();
        cspParams.KeyContainerName = containerName;
        cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
        RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(keySizeBits, cspParams)
        { PersistKeyInCsp = true };
        CspKeyContainerInfo keyContainerInfo = new CspKeyContainerInfo(cspParams);
        string pathToMachineLevelAsymmetricKeysFolder = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
            @"Microsoft\Crypto\RSA\MachineKeys");
        asymmetricKeyPairPersistenceResult.KeyContainerName = keyContainerInfo.KeyContainerName;
        asymmetricKeyPairPersistenceResult.KeyStorageTopFolder = pathToMachineLevelAsymmetricKeysFolder;
        asymmetricKeyPairPersistenceResult.KeyStorageFileFullPath =
            Path.Combine(pathToMachineLevelAsymmetricKeysFolder, keyContainerInfo.UniqueKeyContainerName);
        asymmetricKeyPairPersistenceResult.Success = true;
        asymmetricKeyPairPersistenceResult.PublicKeyXml = rsaProvider.ToXmlString(false);
        asymmetricKeyPairPersistenceResult.PublicPrivateKeyPairXml = rsaProvider.ToXmlString(true);
    }
    catch (Exception ex)
    {
        asymmetricKeyPairPersistenceResult.ExceptionMessage = ex.Message;
    }
    return asymmetricKeyPairPersistenceResult;
}

公共RSA密钥,与客户端一起存储在文件中:

using Microsoft.Extensions.Configuration;
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace TestClient
{
    class Program
    {
        static readonly HttpClient client = new HttpClient();
        static IConfiguration configuration;

        static async Task Main(string[] args)
        {
            configuration = new ConfigurationBuilder()
                .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
                .Build();

            string customerId = configuration.GetSection("CustomerId").Value;

            Console.WriteLine($"Customer ID = {customerId}");

            var response = await client.GetAsync($"https://localhost:58579/api/license/{customerId}");

            if (response.IsSuccessStatusCode)
            {
                Console.WriteLine("License fetched.");

                XmlDocument publicKeyDoc = new XmlDocument();
                publicKeyDoc.Load("test1_public_key.xml");

                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine(content);

                RSAParameters parms = new RSAParameters();
                parms.Modulus = Encoding.UTF8.GetBytes(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Modulus").InnerText);
                parms.Exponent = Encoding.UTF8.GetBytes(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Exponent").InnerText);

                RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
                rsaKey.ImportParameters(parms);

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(content);
                SignedXml signedXml = new SignedXml(xmlDoc);
                XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
                signedXml.LoadXml((XmlElement)nodeList[0]);

                bool isValid = signedXml.CheckSignature(rsaKey);

                if (isValid)
                {
                    Console.WriteLine("Doc signature is valid");
                }
                else
                {
                    Console.WriteLine("Doc signature is INVALID");
                }
            }
            else
            {
                Console.WriteLine($"Error fetching license: {await response.Content.ReadAsStringAsync()}");
            }
        }
    }
}

程序的输出:

<?xml version="1.0" encoding="utf-8" ?>
<RSAKeyValue>
  <Modulus>xQIe438pmPoGKGkZoDkxrSaj1+DQH6g4tqZaZrTe5yXlD4BZZEWimWaQpuRarD3oOJPmS7GLLqBNWXepWubRkck3GyqyNY5Lraiarm7Jzp4tQ/0H0SPOcbDu6CBlmbET1tIXb0e461VmKupP35/2GSgsmSYEpDZkIF24wf+zt+wzOe7aEQiHQ2Y085yd7JbtkHWbmK8v+85a5RDvNJ75eLUgvmBiwi5RgHQEiIJkLR10IUAq5N/u4EcxvQgGa2rGlWTXMayeQJSvgv0cAMF6kQcTy9sc3MlGEa0qGplhB5FxLcq0uJN0QYQcxMNkYtLXVfrzFCbVDuptUptdNv278Q==</Modulus>
  <Exponent>AQAB</Exponent>
</RSAKeyValue>

我想我缺少一些简单的东西,但是我不知道它是什么。编码吗?

感谢您的帮助。我在做什么错了?

-V

1 个答案:

答案 0 :(得分:0)

能否请您检查下面根据原始代码编写的程序?当然,由于简化了代码以使其能够在在线编译器上运行,因此它不会在存储器上存储任何文件,也不会从远程位置读取数据,但是我认为该示例将提供指导,因为这种方法是相似的。

主要区别包括使用 Convert.FromBase64String 小心创建的许可证XML中的UTF8-BOM字符,因为如果BOM会导致LoadXml()方法出错,角色存在。另外,不使用存储是有区别的,但是由于最终文件在原始代码中被读取为字符串,所以这种区别应该不是很重要。

using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace TestClient
{
    abstract class OperationResult
    {
        public bool Success { get; set; }
        public string ExceptionMessage { get; set; }
    }

    class AsymmetricKeyPairPersistenceResult : OperationResult
    {
        public string KeyContainerName { get; set; }
        public string KeyStorageFileFullPath { get; set; }
        public string KeyStorageTopFolder { get; set; }
        public string PublicKeyXml { get; set; }
        public string PublicPrivateKeyPairXml { get; set; }
    }

    class AsymmetricEncryptionService
    {
        public AsymmetricKeyPairPersistenceResult PersistNewAsymmetricKeyPair(int keySizeBits)
        {
            AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = new AsymmetricKeyPairPersistenceResult();
            try
            {           
                RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(keySizeBits);

                asymmetricKeyPairPersistenceResult.Success = true;
                asymmetricKeyPairPersistenceResult.PublicKeyXml = rsaProvider.ToXmlString(false);
                asymmetricKeyPairPersistenceResult.PublicPrivateKeyPairXml = rsaProvider.ToXmlString(true);
            }
            catch (Exception ex)
            {
                asymmetricKeyPairPersistenceResult.ExceptionMessage = ex.Message;
            }

            return asymmetricKeyPairPersistenceResult;
        }
    }

    class Customer{
        public Customer(string Name) { this.Name = Name; this.GlobalId = Guid.NewGuid().ToString(); }
        public string Name { get; set; }    
        public string GlobalId { get; set; }
    }

    class Program
    {
        private static readonly Dictionary<string, string> customerDictionary = new Dictionary<string, string>();
        private static readonly HttpClient client = new HttpClient();
        //static IConfiguration configuration;

        public static bool StartProcess()
        {
            /*configuration = new ConfigurationBuilder()
                .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
                .Build();
            */
            string customerId = createCustomer("Test User").GlobalId;

            Console.WriteLine("Customer ID = " + customerId + "\n");

            XmlDocument publicKeyDoc = new XmlDocument();
            publicKeyDoc.LoadXml(customerDictionary[customerId]);

            string content = CreateAndSignXML(customerId, publicKeyDoc);
            Console.WriteLine("License fetched.");
            Console.WriteLine("-----------------");
            Console.WriteLine(content + "\n");

            return VerifySignature(content, publicKeyDoc);
        }

        private static bool VerifySignature(string content, XmlDocument publicKeyDoc){
            RSAParameters parameters = new RSAParameters();
            parameters.Modulus = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Modulus").InnerText);
            parameters.Exponent = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Exponent").InnerText);
            RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
            rsaKey.ImportParameters(parameters);

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = false;
            xmlDoc.LoadXml(content);
            SignedXml signedXml = new SignedXml(xmlDoc);
            XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
            signedXml.LoadXml((XmlElement)nodeList[0]);

            return signedXml.CheckSignature(rsaKey);
        }

        private static string CreateAndSignXML(string customerId, XmlDocument publicKeyDoc){
            Console.WriteLine("Started creating XML..");

            RSAParameters parameters = new RSAParameters();
            // Convert.FromBase64String is used instead of Encoding.UTF8.GetBytes
            parameters.Modulus = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Modulus").InnerText);
            parameters.Exponent = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Exponent").InnerText);
            parameters.D = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/D").InnerText);
            parameters.P = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/P").InnerText);
            parameters.Q = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Q").InnerText);
            parameters.InverseQ = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/InverseQ").InnerText);
            parameters.DP = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/DP").InnerText);
            parameters.DQ = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/DQ").InnerText);
            RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
            rsaKey.ImportParameters(parameters);

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = false;
            xmlDoc.LoadXml("<license><mode>Trial</mode><features><feature>All</feature></features></license>");

            Console.WriteLine("Started signing XML..");

            SignedXml signedXml = new SignedXml(xmlDoc);
            signedXml.SigningKey = rsaKey;
            Reference reference = new Reference();
            reference.Uri = string.Empty;
            XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);
            signedXml.AddReference(reference);
            signedXml.ComputeSignature();
            XmlElement xmlDigitalSignature = signedXml.GetXml();
            xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));

            MemoryStream stream = new MemoryStream();
            XmlWriterSettings settings = new XmlWriterSettings
            {
                Async = false,
                Indent = true
            };
            stream.Position = 0;
            var xWriter = XmlWriter.Create(stream, settings);
            xmlDoc.WriteContentTo(xWriter);
            xWriter.Flush();
            stream.Position = 0;

            Console.WriteLine("Completed creating and signing XML.." + "\n");

            // Removing BOM character
            string finalXmlContent = Encoding.UTF8.GetString(stream.ToArray());
            string byteOrderMarkUTF8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
            if (finalXmlContent.StartsWith(byteOrderMarkUTF8))
            {
                finalXmlContent = finalXmlContent.Remove(0, byteOrderMarkUTF8.Length);
            }

            return finalXmlContent; 
        }

        private static Customer createCustomer(string customerName){
            Customer customer = new Customer(customerName);

            AsymmetricEncryptionService asymmetricEncryptionService = new AsymmetricEncryptionService();
            int keySizeBits = 2048;

            AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = asymmetricEncryptionService.PersistNewAsymmetricKeyPair(keySizeBits);

            if (asymmetricKeyPairPersistenceResult.Success)
            {
                customerDictionary.Add(customer.GlobalId.ToString(), asymmetricKeyPairPersistenceResult.PublicPrivateKeyPairXml);

                Console.WriteLine("Public/Private Key Pair");
                Console.WriteLine("------------------------");
                Console.WriteLine(JsonConvert.SerializeObject(customerDictionary) + "\n");
            }   
            else{
                Console.WriteLine(asymmetricKeyPairPersistenceResult.ExceptionMessage);
            }

            Console.WriteLine("Customer is created successfully");

            return customer;
        }

        public static void Main(string[] args){
            bool result = StartProcess();

            Console.WriteLine("RESULT");
            Console.WriteLine("-------");
            if (result)
            {
                Console.WriteLine("Doc signature is VALID");
            }
            else
            {
                Console.WriteLine("Doc signature is INVALID");
            }
        }
    }
}

实时/可运行的版本位于:https://dotnetfiddle.net/Uap80i

示例输出: Sample Output

注意:此示例仅用于测试目的,目的是确定该方法是否成功。因此,该过程不是相同而是相似。