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

问题描述

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

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以及公共和私有密钥。 Java函数始终返回false,没有错误或异常。 如果我像下面这样删除MessageDigest,它便开始工作,是否可以安全使用?

static boolean verifySignature512(String message,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方面的签名验证必须失败。 在C#中,您正在使用SHA1('HashAlgorithmName.SHA1'),而Java部分正在使用SHA512('Signature signature = Signature.getInstance(“ Sha512withRSA”);')。

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

安全警告:我的示例代码使用的是不应该在生产中使用的UNSECURE 512位RSA密钥。 没有适当的异常处理,您正在使用填充'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输出:

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,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代码:

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);
    }
}