从 Java 到 C# 的 RSA 签名端口

RSA Signature port from Java to C#

我在 Java 中有这个方法,在 C# 中是相同的代码。我正在为执行此操作的 C# 代码而苦苦挣扎

private String signSHA256RSA(String input) throws Exception 
{
    byte[] b1 = Base64.getDecoder().decode(privKey);
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b1);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    Signature privateSignature = Signature.getInstance("SHA256withRSA");
    privateSignature.initSign(kf.generatePrivate(spec));
    privateSignature.update(input.getBytes(StandardCharsets.UTF_8));
    return byteArrayToHex(privateSignature.sign());
}

使用 .NET 标准 2.1,您需要一个小帮手来从 Base64 编码的 DER 文件中解码私钥。

using System;
using System.Security.Cryptography;
using System.Buffers.Binary;
using System.Text;

namespace Demo
{
    class Program
    {
        const string DerPrivateKey = "MIIBywIBAAJhALkPdKoBJJg8t0Qg7VhyomS+PpKNMzn0NQ/P3zt55uAmLKenUV9xbMhW1SQRUbTEDdDUlfIiBMCzNAxB5od2IrhP4+/nKmUNsIoxOdwL0j//X74xalv9137T+y4ubLzVhwIDAQABAmAScdvq5dpD4ilR/QYq/qH48I1EBhbI+/Id9VYGk4vTY3qn6yFNJfz1qtHrml5OagvbBQLyPwjwxSumkzGelauqr4NvpOirK18v3xzhlsSmys6JZ5nILG16JByXxJjvziECMQDluHUNOCElxIbIrFOTVBaiqs4Iw9b/UJ7Wf7GglFk4pS00wjSnuDMDqGjyb4tgQ3UCMQDOOxdcSs2CPLrppT463NMqDoGide33X4s5y67E9v44IMTKuOwIXoDzgTcoGmeJwYsCMQDWUZRrA93xFXxGRngmsMH5e2+Dv+qbAsVeC35V+XGQJpKZcUKc4348wGdBIA4hfm0CMQCllxDkzDNDFZxHKqVTAiiTpl40olhWvmK+H2vPPztUugsJc34iIi+MVf6BtuHX3I0CMEDuG1uewuwcgHxWTGMnvSqQjkwtTUI0It6c8PTf8URGtoHx7HNl/wKoGGgXLTRY1w==";

        static void Main(string[] args)
        {
            var signature = SignSHA256RSA(base64Key: DerPrivateKey, input: "Hello world!");
            System.Console.WriteLine("Signature (HEX): " + signature);
        }

        private static string SignSHA256RSA(string base64Key, string input)
        {
            // Load key with the little DER helper
            var rsa = LoadRSAKey(derPrivateKey: Convert.FromBase64String(DerPrivateKey));

            // Sign
            var inputBytes = Encoding.UTF8.GetBytes(input);
            var signatureBytes = rsa.SignData(inputBytes, 0, inputBytes.Length, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

            // Convert signature to hex
            var signatureHex = BitConverter.ToString(signatureBytes).Replace("-", string.Empty);
            return signatureHex;
        }

        private static RSA LoadRSAKey(ReadOnlySpan<byte> derPrivateKey)
        {
            // Expect sequence
            if (AsnHelper.GetTag(derPrivateKey[0]) != 16)
                throw new FormatException($"unexpected tag");

            // Read sequence length
            derPrivateKey = derPrivateKey.Slice(1);
            if (!AsnHelper.TryReadLength(derPrivateKey, out var length, out var bytesRead))
                throw new FormatException($"unexpected length");

            var sequence = AsnHelper.ReadSequence(derPrivateKey.Slice(bytesRead, length));

            // https://www.hanselman.com/blog/DecodingAnSSHKeyFromPEMToBASE64ToHEXToASN1ToPrimeDecimalNumbers.aspx
            var rsaParameters = new RSAParameters
            {
                Modulus = sequence[1].RawData,
                Exponent = sequence[2].RawData,
                D = sequence[3].RawData,
                P = sequence[4].RawData,
                Q = sequence[5].RawData,
                DP = sequence[6].RawData,
                DQ = sequence[7].RawData,
                InverseQ = sequence[8].RawData,
            };

            var rsa = RSA.Create();
            rsa.ImportParameters(rsaParameters);

            return rsa;
        }
    }

    /// <summary>
    /// Quick helper: only works on specific key format
    /// 
    /// https://en.wikipedia.org/wiki/X.690#BER_encoding
    /// </summary>
    internal static class AsnHelper
    {
        public static int GetTag(byte value) => value & 0b11111;

        public static AsnEncodedDataCollection ReadSequence(ReadOnlySpan<byte> source)
        {
            var sequence = new AsnEncodedDataCollection();
            while (!source.IsEmpty)
            {
                var tag = GetTag(source[0]);
                if (tag != 2)
                    throw new FormatException("only support integer");

                source = source.Slice(1);
                if (!TryReadLength(source, out var length, out var bytesRead))
                    throw new FormatException("invalid length");

                source = source.Slice(bytesRead);
                var value = new AsnEncodedData(source.Slice(0, length).ToArray());
                source = source.Slice(length);
                sequence.Add(value);
            }

            return sequence;
        }

        public static bool TryReadLength(ReadOnlySpan<byte> source, out int length, out int bytesRead)
        {
            length = 0;
            bytesRead = 0;
            const byte MultiByteMarker = 0x80;

            bytesRead = 1;
            if ((source[0] & MultiByteMarker) == 0)
            {
                length = source[0];
                return true;
            }
            int lengthLength = source[0] & 0x7F;
            bytesRead += lengthLength;
            if (lengthLength == 2)
            {
                length = BinaryPrimitives.ReadInt16BigEndian(source.Slice(1));
                return true;
            }

            return false;
        }
    }
}

为了测试,我使用以下内容生成了一个密钥:

openssl genrsa 768 | openssl rsa -outform der | base64 -w 0

最后我就是这样解决的。

    private static string SignSHA256RSA(string itemToSign)
    {
        var bytes = Encoding.UTF8.GetBytes(itemToSign);
        using (var stream = File.OpenRead(@"C:\PrivateKey.pem"))
        using (var reader = new PemReader(stream))
        {
            var rsaParameters = reader.ReadRsaKey();
            byte[] hv = SHA256.Create().ComputeHash(bytes);
            RSACryptoServiceProvider prov = new RSACryptoServiceProvider();
            RSAParameters rsp = new RSAParameters();
            prov.ImportParameters(rsaParameters);
            RSAPKCS1SignatureFormatter rf = new RSAPKCS1SignatureFormatter(prov);
            rf.SetHashAlgorithm("SHA256");
            byte[] signature = rf.CreateSignature(hv);
            var finalHex = BitConverter.ToString(signature).Replace("-", string.Empty).ToLowerInvariant();
            return finalHex;
        }
    }

C#.Net代码框架:4.6, 它对我有用。

private string SignSHA256RSA(string itemToSign)
{      
    string filePath = Server.MapPath("Merchant_private_key_test.pem");
    var bytes = System.Text.Encoding.GetEncoding("UTF-8").GetBytes(itemToSign);
    var sha256 = new SHA256CryptoServiceProvider();
    byte[] rgbHash = sha256.ComputeHash(bytes);
    StreamReader sr = new StreamReader(filePath);
    PemReader pr = new PemReader(sr);
    RsaPrivateCrtKeyParameters KeyPair = (RsaPrivateCrtKeyParameters)pr.ReadObject();
    RSA rsa = DotNetUtilities.ToRSA(KeyPair);
    string xmlRsa = rsa.ToXmlString(true);
    RSACryptoServiceProvider key = new RSACryptoServiceProvider();
    key.FromXmlString(xmlRsa);
    RSAPKCS1SignatureFormatter formatter = new RSAPKCS1SignatureFormatter(key);
    formatter.SetHashAlgorithm("SHA256");
    byte[] inArray = formatter.CreateSignature(rgbHash);

    return Convert.ToBase64String(inArray);
}