using System.Security.Cryptography;

namespace ThingsGateway.NewLife.Security;

/// <summary>RSA算法</summary>
/// <remarks>
/// RSA加密或签名小数据块时，密文长度128，速度也很快。
/// </remarks>
public static class RSAHelper
{
    #region 加密解密
    /// <summary>产生非对称密钥对</summary>
    /// <remarks>
    /// RSAParameters的各个字段采用大端字节序，转为BigInteger的之前一定要倒序。
    /// RSA加密后密文最小长度就是密钥长度，所以1024密钥最小密文长度是128字节。
    /// </remarks>
    /// <param name="keySize">密钥长度，默认1024位强密钥</param>
    /// <returns></returns>
    public static String[] GenerateKey(Int32 keySize = 2048)
    {
        using var rsa = new RSACryptoServiceProvider(keySize);

        var ss = new String[2];
        ss[0] = rsa.ToXmlString(true);
        ss[1] = rsa.ToXmlString(false);

        return ss;
    }

    /// <summary>产生非对称密钥对</summary>
    /// <remarks>
    /// RSAParameters的各个字段采用大端字节序，转为BigInteger的之前一定要倒序。
    /// RSA加密后密文最小长度就是密钥长度，所以1024密钥最小密文长度是128字节。
    /// </remarks>
    /// <param name="keySize">密钥长度，默认1024位强密钥</param>
    /// <returns></returns>
    public static String[] GenerateParameters(Int32 keySize = 2048)
    {
        using var rsa = new RSACryptoServiceProvider(keySize);

        var ss = new String[2];
        ss[0] = WriteParameters(rsa.ExportParameters(true));
        ss[1] = WriteParameters(rsa.ExportParameters(false));

        return ss;
    }

    /// <summary>RSA参数转为Base64密钥</summary>
    /// <param name="p"></param>
    /// <returns></returns>
    public static String WriteParameters(RSAParameters p)
    {
        // 判断参数p的每一个成员是否为空，如果为空则抛出异常
        if (p.Modulus == null || p.Exponent == null) throw new ArgumentNullException(nameof(p));
        var ms = new MemoryStream();
        ms.WriteArray(p.Modulus);
        ms.WriteArray(p.Exponent);

        if (p.D != null && p.D.Length > 0)
        {
            if (p.D == null || p.P == null || p.Q == null ||
                p.DP == null || p.DQ == null || p.InverseQ == null) throw new ArgumentNullException(nameof(p));

            ms.WriteArray(p.D);
            ms.WriteArray(p.P);
            ms.WriteArray(p.Q);
            ms.WriteArray(p.DP);
            ms.WriteArray(p.DQ);
            ms.WriteArray(p.InverseQ);
        }

        return ms.ToArray().ToUrlBase64();
    }

    /// <summary>根据Base64密钥创建RSA参数</summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public static RSAParameters ReadParameters(String key)
    {
        using var ms = new MemoryStream(key.ToBase64());

        var p = new RSAParameters
        {
            Modulus = ms.ReadArray(),
            Exponent = ms.ReadArray(),
        };

        if (ms.Position < ms.Length)
        {
            p.D = ms.ReadArray();
            p.P = ms.ReadArray();
            p.Q = ms.ReadArray();
            p.DP = ms.ReadArray();
            p.DQ = ms.ReadArray();
            p.InverseQ = ms.ReadArray();
        }

        return p;
    }

    /// <summary>创建RSA对象，支持Xml密钥和Pem密钥</summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public static RSACryptoServiceProvider Create(String key)
    {
        key = key.Trim();
        if (key.IsNullOrEmpty()) throw new ArgumentNullException(nameof(key));

        var rsa = new RSACryptoServiceProvider();
        if (key.StartsWith("<RSAKeyValue>") && key.EndsWith("</RSAKeyValue>"))
            rsa.FromXmlString(key);
        else if (key.StartsWith("--") || key.Contains('\r') || key.Contains('\n'))
            rsa.ImportParameters(ReadPem(key));
        else
            rsa.ImportParameters(ReadParameters(key));

        return rsa;
    }

    /// <summary>RSA公钥加密。仅用于加密少量数据</summary>
    /// <remarks>
    /// (PKCS # 1 v2) 的 OAEP 填充	模数大小-2-2 * hLen，其中 hLen 是哈希的大小。
    /// 直接加密 (PKCS # 1 1.5 版)	模数大小-11。 (11 个字节是可能的最小填充。 )
    /// </remarks>
    /// <param name="data">数据明文</param>
    /// <param name="pubKey">公钥</param>
    /// <param name="fOAEP">如果为 true，则使用 OAEP 填充（仅可用于运行 Windows XP 及更高版本的计算机）执行直接 System.Security.Cryptography.RSA加密；否则，如果为 false，则使用 PKCS#1 v1.5 填充。</param>
    /// <returns></returns>
    public static Byte[] Encrypt(Byte[] data, String pubKey, Boolean fOAEP = true)
    {
        using var rsa = Create(pubKey);

        return rsa.Encrypt(data, fOAEP);
    }

    /// <summary>RSA私钥解密。仅用于加密少量数据</summary>
    /// <remarks>
    /// (PKCS # 1 v2) 的 OAEP 填充	模数大小-2-2 * hLen，其中 hLen 是哈希的大小。
    /// 直接加密 (PKCS # 1 1.5 版)	模数大小-11。 (11 个字节是可能的最小填充。 )
    /// </remarks>
    /// <param name="data">数据密文</param>
    /// <param name="priKey">私钥</param>
    /// <param name="fOAEP">如果为 true，则使用 OAEP 填充（仅可用于运行 Microsoft Windows XP 及更高版本的计算机）执行直接 System.Security.Cryptography.RSA解密；否则，如果为 false 则使用 PKCS#1 v1.5 填充。</param>
    /// <returns></returns>
    public static Byte[] Decrypt(Byte[] data, String priKey, Boolean fOAEP = true)
    {
        using var rsa = Create(priKey);

        return rsa.Decrypt(data, fOAEP);
    }
    #endregion

    #region 数字签名
    /// <summary>签名，MD5散列</summary>
    /// <param name="data"></param>
    /// <param name="priKey"></param>
    /// <returns></returns>
    public static Byte[] Sign(Byte[] data, String priKey)
    {
        using var rsa = Create(priKey);
        using var value = MD5.Create();
        return rsa.SignData(data, value);
    }

    /// <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)
    {
        using var rsa = Create(pukKey);
        using var value = MD5.Create();
        return rsa.VerifyData(data, value, rgbSignature);
    }

    private static HashAlgorithm _sha256 = SHA256.Create();
    /// <summary>RS256</summary>
    /// <param name="data"></param>
    /// <param name="priKey"></param>
    /// <returns></returns>
    public static Byte[] SignSha256(this Byte[] data, String priKey)
    {
        using var rsa = Create(priKey);
        return rsa.SignData(data, _sha256);
    }

    /// <summary>RS256</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)
    {
        using var rsa = Create(pukKey);
        return rsa.VerifyData(data, _sha256, rgbSignature);
    }

    private static HashAlgorithm _sha384 = SHA384.Create();
    /// <summary>RS384</summary>
    /// <param name="data"></param>
    /// <param name="priKey"></param>
    /// <returns></returns>
    public static Byte[] SignSha384(this Byte[] data, String priKey)
    {
        using var rsa = Create(priKey);
        return rsa.SignData(data, _sha384);
    }

    /// <summary>RS384</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)
    {
        using var rsa = Create(pukKey);
        return rsa.VerifyData(data, _sha384, rgbSignature);
    }

    private static HashAlgorithm _sha512 = SHA512.Create();
    /// <summary>RS512</summary>
    /// <param name="data"></param>
    /// <param name="priKey"></param>
    /// <returns></returns>
    public static Byte[] SignSha512(this Byte[] data, String priKey)
    {
        using var rsa = Create(priKey);
        return rsa.SignData(data, _sha512);
    }

    /// <summary>RS512</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)
    {
        using var rsa = Create(pukKey);
        return rsa.VerifyData(data, _sha512, rgbSignature);
    }
    #endregion

    #region PEM
    /// <summary>读取PEM文件到RSA参数</summary>
    /// <param name="content"></param>
    /// <returns></returns>
    public static RSAParameters 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) ?? throw new InvalidDataException();
            var keys = asn.Value as Asn1[] ?? throw new InvalidDataException();

            // 可能直接key，也可能有Oid包装
            var oids = asn.GetOids();
            if (oids.Any(e => e.FriendlyName == "RSA"))
            {
                var buf = keys[2].Value as Byte[];
                if (buf != null) keys = Asn1.Read(buf)?.Value as Asn1[];
            }

            if (keys == null) throw new InvalidDataException();

            // 参数数据
            return new RSAParameters
            {
                Modulus = keys[1].GetByteArray(true),
                Exponent = keys[2].GetByteArray(false),
                D = keys[3].GetByteArray(true),
                P = keys[4].GetByteArray(true),
                Q = keys[5].GetByteArray(true),
                DP = keys[6].GetByteArray(true),
                DQ = keys[7].GetByteArray(true),
                InverseQ = keys[8].GetByteArray(true)
            };
        }
        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) ?? throw new InvalidDataException();
            var keys = asn.Value as Asn1[] ?? throw new InvalidDataException();

            // 可能直接key，也可能有Oid包装
            var oids = asn.GetOids();
            if (oids.Any(e => e.FriendlyName == "RSA"))
            {
                var buf = keys.FirstOrDefault(e => e.Tag == Asn1Tags.BitString)?.Value as Byte[];
                if (buf != null) keys = Asn1.Read(buf)?.Value as Asn1[];
            }

            if (keys == null) throw new InvalidDataException();

            // 参数数据
            return new RSAParameters
            {
                Modulus = keys[0].GetByteArray(true),
                Exponent = keys[1].GetByteArray(false),
            };
        }
    }
    #endregion
}