[fix]修正UdpServer在接收广播时连续启动接收的错误,在StarAgent中,此时可能收到广播包,SocketFlags是Broadcast,需要清空,否则报错“参考的对象类型不支持尝试的操作”; 无需设置SocketOptionName.PacketInformation,在ReceiveMessageFromAsync时会自动设置,并且支持ipv6;
石头 编写于 2024-10-10 00:36:00 石头 提交于 2024-10-10 00:45:43
X
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;

namespace NewLife.Security
{
    /// <summary>椭圆曲线数字签名算法 (ECDSA) </summary>
    public static class ECDsaHelper
    {
        #region 生成密钥
        /// <summary>产生非对称密钥对</summary>
        /// <param name="keySize">密钥长度,默认521位强密钥</param>
        /// <returns></returns>
        public static String[] GenerateKey(Int32 keySize = 521)
        {
            var dsa = new ECDsaCng(keySize);

            var ss = new String[2];
            ss[0] = dsa.Key.Export(CngKeyBlobFormat.EccPrivateBlob).ToBase64();
            ss[1] = dsa.Key.Export(CngKeyBlobFormat.EccPublicBlob).ToBase64();

            return ss;
        }

        /// <summary>创建ECDsa对象,支持Base64密钥和Pem密钥</summary>
        /// <param name="key"></param>
        /// <param name="privateKey"></param>
        /// <returns></returns>
        public static ECDsaCng Create(String key, Boolean? privateKey = null)
        {
            key = key?.Trim();
            if (key.IsNullOrEmpty()) return null;

            if (key.StartsWith("-----") && key.EndsWith("-----"))
            {
                var ek = ReadPem(key);

                // netcore下优先使用ExportParameters,CngKey.Import有兼容问题
                var ec = new ECDsaCng();
                ec.ImportParameters(ek.ExportParameters());

                return ec;

                //var buf = ek.ToArray();
                //var ckey = CngKey.Import(buf, ek.D == null ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob);
                //return new ECDsaCng(ckey);
            }
            else
            {
                var buf = key.ToBase64();
                var ckey =
                    privateKey != null ?
                    CngKey.Import(buf, !privateKey.Value ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob) :
                    CngKey.Import(buf, buf.Length < 100 ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob);

                return new ECDsaCng(ckey);
            }
        }
        #endregion

        #region 数字签名
        /// <summary>签名,MD5散列</summary>
        /// <param name="data"></param>
        /// <param name="priKey"></param>
        /// <returns></returns>
        public static Byte[] Sign(Byte[] data, String priKey)
        {
            var ecc = Create(priKey, true);
            ecc.HashAlgorithm = CngAlgorithm.MD5;

            return ecc.SignData(data);
        }

        /// <summary>验证,MD5散列</summary>
        /// <param name="data"></param>
        /// <param name="pukKey"></param>
        /// <param name="rgbSignature"></param>
        /// <returns></returns>
        public static Boolean Verify(Byte[] data, String pukKey, Byte[] rgbSignature)
        {
            var ecc = Create(pukKey, false);
            ecc.HashAlgorithm = CngAlgorithm.MD5;

            return ecc.VerifyData(data, rgbSignature);
        }

        /// <summary>Sha256</summary>
        /// <param name="data"></param>
        /// <param name="priKey"></param>
        /// <returns></returns>
        public static Byte[] SignSha256(this Byte[] data, String priKey)
        {
            var ecc = Create(priKey, true);
            ecc.HashAlgorithm = CngAlgorithm.Sha256;

            return ecc.SignData(data);
        }

        /// <summary>Sha256</summary>
        /// <param name="data"></param>
        /// <param name="pukKey"></param>
        /// <param name="rgbSignature"></param>
        /// <returns></returns>
        public static Boolean VerifySha256(this Byte[] data, String pukKey, Byte[] rgbSignature)
        {
            var ecc = Create(pukKey, false);
            ecc.HashAlgorithm = CngAlgorithm.Sha256;

            return ecc.VerifyData(data, rgbSignature);
        }

        /// <summary>Sha384</summary>
        /// <param name="data"></param>
        /// <param name="priKey"></param>
        /// <returns></returns>
        public static Byte[] SignSha384(this Byte[] data, String priKey)
        {
            var ecc = Create(priKey, true);
            ecc.HashAlgorithm = CngAlgorithm.Sha384;

            return ecc.SignData(data);
        }

        /// <summary>Sha384</summary>
        /// <param name="data"></param>
        /// <param name="pukKey"></param>
        /// <param name="rgbSignature"></param>
        /// <returns></returns>
        public static Boolean VerifySha384(this Byte[] data, String pukKey, Byte[] rgbSignature)
        {
            var ecc = Create(pukKey, false);
            ecc.HashAlgorithm = CngAlgorithm.Sha384;

            return ecc.VerifyData(data, rgbSignature);
        }

        /// <summary>Sha512</summary>
        /// <param name="data"></param>
        /// <param name="priKey"></param>
        /// <returns></returns>
        public static Byte[] SignSha512(this Byte[] data, String priKey)
        {
            var ecc = Create(priKey, true);
            ecc.HashAlgorithm = CngAlgorithm.Sha512;

            return ecc.SignData(data);
        }

        /// <summary>Sha512</summary>
        /// <param name="data"></param>
        /// <param name="pukKey"></param>
        /// <param name="rgbSignature"></param>
        /// <returns></returns>
        public static Boolean VerifySha512(this Byte[] data, String pukKey, Byte[] rgbSignature)
        {
            var ecc = Create(pukKey, false);
            ecc.HashAlgorithm = CngAlgorithm.Sha512;

            return ecc.VerifyData(data, rgbSignature);
        }
        #endregion

        #region PEM
        /// <summary>读取PEM文件到RSA参数</summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public static ECKey ReadPem(String content)
        {
            if (String.IsNullOrEmpty(content)) throw new ArgumentNullException(nameof(content));

            // 公钥私钥分别处理
            content = content.Trim();
            if (content.StartsWithIgnoreCase("-----BEGIN RSA PRIVATE KEY-----", "-----BEGIN PRIVATE KEY-----"))
            {
                var content2 = content.TrimStart("-----BEGIN RSA PRIVATE KEY-----")
                     .TrimEnd("-----END RSA PRIVATE KEY-----")
                     .TrimStart("-----BEGIN PRIVATE KEY-----")
                     .TrimEnd("-----END PRIVATE KEY-----")
                     .Replace("\n", null).Replace("\r", null);

                var data = Convert.FromBase64String(content2);

                // PrivateKeyInfo: version + Algorithm(algorithm + parameters) + privateKey
                var asn = Asn1.Read(data);
                var keys = asn.Value as Asn1[];

                // Algorithm(algorithm + parameters)
                var oids = asn.GetOids();
                var algorithm = oids[0];
                var parameters = oids[1];

                if (algorithm.FriendlyName != "ECC") throw new InvalidDataException($"Invalid key {algorithm}");

                keys = Asn1.Read(keys[2].Value as Byte[]).Value as Asn1[];

                // 里面是一个字节前缀,后面X+Y
                var k2 = Asn1.Read(keys[2].Value as Byte[]).Value as Byte[];
                var len = (k2.Length - 1) / 2;

                // 参数
                var ek = new ECKey
                {
                    D = keys[1].Value as Byte[],
                    X = k2.ReadBytes(1, len),
                    Y = k2.ReadBytes(1 + len, len),
                };
                ek.SetAlgorithm(parameters, true);

                return ek;
            }
            else
            {
                content = content.Replace("-----BEGIN PUBLIC KEY-----", null)
                    .Replace("-----END PUBLIC KEY-----", null)
                    .Replace("\n", null).Replace("\r", null);

                var data = Convert.FromBase64String(content);

                var asn = Asn1.Read(data);
                var keys = asn.Value as Asn1[];

                // Algorithm(algorithm + parameters)
                var oids = asn.GetOids();
                var algorithm = oids[0];
                var parameters = oids[1];

                if (algorithm.FriendlyName != "ECC") throw new InvalidDataException($"Invalid key {algorithm}");

                // 里面是一个字节前缀,后面X+Y
                var k2 = keys[1].Value as Byte[];
                var len = (k2.Length - 1) / 2;

                // 参数
                var ek = new ECKey
                {
                    X = k2.ReadBytes(1, len),
                    Y = k2.ReadBytes(1 + len, len),
                };
                ek.SetAlgorithm(parameters, false);

                return ek;
            }
        }
        #endregion
    }
}