我应该使用 MessageDigest 来验证用 C# 签名的数字签名吗?

Should I use MessageDigest to verify a digital signature that signed in C#?

我是 Java 的新手,想在 c# 中签署一些数据并在 Java 中使用 RSA 和 SHA512 进行验证。

C#:

static string SignData(string message, RSAParameters privateKey)
    {
        byte[] signedBytes;
        using (var rsa = new RSACryptoServiceProvider())
        {
            var encoder = new UTF8Encoding();
            byte[] originalData = encoder.GetBytes(message);
                rsa.ImportParameters(privateKey);
               
                signedBytes = rsa.SignData(originalData, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);

                rsa.PersistKeyInCsp = false;
        }
        return Convert.ToBase64String(signedBytes);
    }

Java:

 static boolean verifySignature512(String message, String sign, PublicKey publicKey) throws Exception {
    MessageDigest digest = MessageDigest.getInstance("SHA-512");
    byte[] contentDigest = digest.digest(message.getBytes(Charset.forName("UTF-8")));

    Signature signature = Signature.getInstance("Sha512withRSA");
    signature.initVerify(publicKey);
    signature.update(contentDigest);
    return signature.verify(Base64.getDecoder().decode(sign));
}

我使用了 RSA 和 public 以及私钥。 Java 函数总是 return false,没有错误或异常。 如果我像下面那样删除 MessageDigest,它就会开始工作,是否安全?

static boolean verifySignature512(String message, String sign, PublicKey publicKey) throws Exception {
   // MessageDigest digest = MessageDigest.getInstance("SHA-512");
   // byte[] contentDigest = digest.digest(message.getBytes(Charset.forName("UTF-8")));

    Signature signature = Signature.getInstance("Sha512withRSA");
    signature.initVerify(publicKey);
    signature.update(message.getBytes(Charset.forName("UTF-8")));
    return signature.verify(Base64.getDecoder().decode(sign));
}

Java-side 上的签名验证必须失败,因为您在双方使用不同的哈希算法。 在 C# 中,您使用的是 SHA1 ('HashAlgorithmName.SHA1'),而 Java-part 使用的是 SHA512 ('Signature signature = Signature.getInstance("Sha512withRSA");')。

以下代码使用 SHA1 作为哈希算法,但您可以轻松地将其(在所有代码行上:-) 更改为 SHA256 或 SHA512。 C# 代码使用 public 密钥验证签名,在 Java-code 中使用相同的 public 密钥(编码为 PEM) 仅供验证。

安全警告:我的示例代码使用不安全的 512 位 RSA-keys,不应在生产中使用。 没有正确的异常处理,您使用的填充 'RSASignaturePadding.Pkcs1' 应该 也不再使用了。

这是我的 C# 代码的输出:

Should I use MessageDigest to verify a digital signature that signed in C#?
signedData: mU2bcCMEhG13xG9sKwhaA//dnw2+rbLkwz2737cNU5kb2EBenJIEJ+bA596XccCVKUKPanxMUFoVw2fl8HhCNw==
The data was verified.

那是 Java-output:

RSA instance: SHA1withRSA
The data was verified.

C# 代码:

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

class RSACSPSample {
    static void Main() {

        try {
            Console.WriteLine("Should I use MessageDigest to verify a digital signature that signed in C#?");
            // Create a UnicodeEncoder to convert between byte array and string.
            ASCIIEncoding ByteConverter = new ASCIIEncoding();

            string message = "this is the important message to sign";

            // get private and public key ### SAMPLE and UNSECURE 512 bit RSA keypair
            var privateKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent><P>8VCRao0hZmIv5gVGFLqOD/7n6TQKlekA96U1QVzimKM=</P><Q>o1bchWA5ddDd59FED37QcrakoTXNoxRspFtsLDKEp1c=</Q><DP>ugF0VUE7wYNlkFP4VPoHjuTZNbRbhHn5uOmrRxqlvyk=</DP><DQ>XoGggC9Hr8pUyo9DIPAP7X+Ny5TU0Vm87m/TK9Ni+2s=</DQ><InverseQ>YqOHEP8dgCvz5Q8nhpQgdrKfdlmjkNAFxKF7j3pm09I=</InverseQ><D>mCpGy/rxS08e5iXn26LRQvvm5UfyLKMNZWmAGk2QF8cRGFB7dds/SI9wGTC9xyOoF4N2kWzYdLx+dYbR9lqwbQ==</D></RSAKeyValue>";
            var publicKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            
            // Create a new instance of the RSACryptoServiceProvider class
            RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider(512);
            RSAalg.PersistKeyInCsp = false;
            RSAalg.FromXmlString(privateKey);
            RSAParameters rsaParameters = RSAalg.ExportParameters(true);

            String signedData = SignData(message, rsaParameters);
            Console.WriteLine("signedData: " + signedData);

            // verify with xml-public key
            RSAalg.FromXmlString(publicKey);
            rsaParameters = RSAalg.ExportParameters(false);
            bool verifiedData = VerifyData(message, signedData, rsaParameters);
            
            // Verify the data and display the result to the
            // console.
            if (VerifyData(message, signedData, rsaParameters)) {
                Console.WriteLine("The data was verified.");
            }
            else {
                Console.WriteLine("The data does not match the signature.");
            }
        }
        catch(ArgumentNullException) {
            Console.WriteLine("The data was not signed or verified");
        }
    }
    
    static string SignData(string message, RSAParameters privateKey)
    {
        byte[] signedBytes;
        using (var rsa = new RSACryptoServiceProvider())
        {
            var encoder = new UTF8Encoding();
            byte[] originalData = encoder.GetBytes(message);
                rsa.ImportParameters(privateKey);
                signedBytes = rsa.SignData(originalData, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
                rsa.PersistKeyInCsp = false;
        }
        return Convert.ToBase64String(signedBytes);
    }
    
    public static bool VerifyData(string message, string signedData, RSAParameters rsaParameters) 
    {
        byte[] messageBytes;
        byte[] signedBytes;
        using (var rsa = new RSACryptoServiceProvider())
        try
        {
            var encoder = new UTF8Encoding();
            messageBytes = encoder.GetBytes(message);
            signedBytes = Convert.FromBase64String(signedData);
            rsa.ImportParameters(rsaParameters);
            return rsa.VerifyData(messageBytes, new SHA1CryptoServiceProvider(), signedBytes);
        }
        catch(CryptographicException e) {
            Console.WriteLine(e.Message);

            return false;
        }
    }
}

Java-code:

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class MainSha1 {
    public static void main(String[] args) throws GeneralSecurityException {
        System.out.println("Should I use MessageDigest to verify a digital signature that signed in C#?");
        String message = "this is the important message to sign";
        // this is a SAMPLE and UNSECURE RSA 512 bit key
        String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" +
                "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJn4LYaoLyuT+pD9NKI8wOvPrRjMIxGn\n" +
                "HbqIxUrpGsyj162fEOV4836oCZg0N8HFnt4Vivdjt8/7ZgLjeOygNGUCAwEAAQ==\n" +
                "-----END PUBLIC KEY-----";
        String signedData = "HS4qvrXpqu97me7yDt9lWXp+QLjKMO8FY4kiUiGhMhi6KmXQXCtmcUWSbg0i+LXv7u5ueRiQNeBnu6UCbPhZLg==";
        String rsaInstanceString = "SHA1withRSA";
        System.out.println("RSA instance: " + rsaInstanceString);
        PublicKey publicKey = getPublicKeyFromString(publicKeyPem);
        boolean verifyData = verifyRsa(publicKey, rsaInstanceString, message.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(signedData));
        if (verifyData = true) {
            System.out.println("The data was verified.");
        } else {
            System.out.println("The data could NOT get verified.");
        }

    }

    public static PublicKey getPublicKeyFromString(String key) throws GeneralSecurityException {
        String publicKeyPEM = key;
        publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----", "");
        publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
        publicKeyPEM = publicKeyPEM.replaceAll("[\r\n]+", "");
        byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
        return pubKey;
    }

    public static Boolean verifyRsa(PublicKey publicKey, String rsaInstanceString, byte[] messageByte,
                                    byte[] signatureByte) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        Signature publicSignature = Signature.getInstance(rsaInstanceString);
        publicSignature.initVerify(publicKey);
        publicSignature.update(messageByte);
        return publicSignature.verify(signatureByte);
    }
}