C# RSA Import Public Key

谁都会走 提交于 2021-02-11 14:32:05

问题


I need to verify the signed data. I dont know how to use the public key.

    public bool VerifyData(string data, string signature)
    {
        //decode signature from base 64
        byte[] signatureByte = System.Convert.FromBase64String(signature);

        //hash data to sha256
        string hashedData = ConvertToSHA256(data);
        byte[] hashedDataByte = System.Convert.FromBase64String(hashedData);

        //verify with RSA PSS
        string absPath = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/TP/public");
        string publicKeyString = File.ReadAllText(absPath);
        publicKeyString = RemoveRSAHeaderAndFooter(publicKeyString);

        RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
        //This causes error 
        RSA.ImportCspBlob(System.Convert.FromBase64String(publicKeyString));

        RSAParameters rsaParams = RSA.ExportParameters(true);
        RSACng RSACng = new RSACng();
        RSACng.ImportParameters(rsaParams);

        return RSACng.VerifyData(hashedDataByte, signatureByte, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
    }

The RSA.ImportCspBlob causes error. My public key is string type. It looks something like this:

-----BEGIN PUBLIC KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXXX
-----END PUBLIC KEY-----

How can I verify it?


Note

This shell script can be used to verify it:

openssl base64 -A -d -in $temp.sign -out $temp.sha256
openssl dgst -sha256 -sigopt rsa_padding_mode:pss -verify $publickey -signature $temp.sha256 $temp.key

ERROR

Bad Version of provider.


UPDATE

So I updated my code according to @Topaco:

    private async Task<bool> IsContentValid(string data)
    {
        bool valid = false;
        string signature = Request.Headers.GetValues("tkpd-signature").FirstOrDefault();

        //decode signature from base 64
        byte[] signatureByte = System.Convert.FromBase64String(signature);

        //hash data to sha256
        string hashedData = ConvertToSHA256(data);
        byte[] hashedDataByte = System.Convert.FromBase64String(hashedData);

        //verify with RSA PSS
        string absPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Keys/tppublic");
        string publicKeyString = File.ReadAllText(absPath);

        PemReader pr = new PemReader(new StringReader(publicKeyString));
        AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();

        RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
        RSACng rsaCng = new RSACng();
        rsaCng.ImportParameters(rsaParams);

        valid = rsaCng.VerifyData(hashedDataByte, signatureByte, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
        return valid;
    }

    private string ConvertToSHA256(string data)
    {
        using (SHA256 mySHA256 = SHA256.Create())
        {
            var crypt = new System.Security.Cryptography.SHA256Managed();
            var hash = new System.Text.StringBuilder();
            byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(data));
            foreach (byte theByte in crypto)
            {
                hash.Append(theByte.ToString("x2"));
            }
            return hash.ToString();
        }
    }

I do not know if my previous steps are wrong, or the verification wrong.

If I run the script, it is verified successfully. This script:

openssl base64 -A -d -in $temp.sign -out $temp.sha256
openssl dgst -sha256 -sigopt rsa_padding_mode:pss -verify $publickey -signature $temp.sha256 $temp.key

UPDATE

So the verification is still failing even when I changed the data without converting to sha256.

This is my code:

    private async Task<bool> IsContentValid(string data)
    {
        bool valid = false;
        string signature = Request.Headers.GetValues("tkpd-signature").FirstOrDefault();

        //decode signature from base 64
        byte[] signatureByte = System.Convert.FromBase64String(signature);

        //hash data to sha256
        //string hashedData = ConvertToSHA256(data);
        byte[] hashedDataByte = Encoding.UTF8.GetBytes(data);

        //verify with RSA PSS
        string absPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Keys/tppublic");
        string publicKeyString = File.ReadAllText(absPath);

        PemReader pr = new PemReader(new StringReader(publicKeyString));
        AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();

        RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
        RSACng rsaCng = new RSACng();
        rsaCng.ImportParameters(rsaParams);

        valid = rsaCng.VerifyData(hashedDataByte, signatureByte, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
        return valid;
    }

The public key is:

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxYVf6wVycEygE2VTu4q6
fb7eqkDWikliprXeSazUygHXPlbBqDkHmggylaq5M3C8RwExPyvPhtKNZZ7CzuSH
BfW/TxF1SH+htNvr5Tk/kPPQ/S575gmF9KzXXwJq255qfcwNDiiZZDb1tt4IKa4n
bNK1z2GHtX3CasAJDjTH1aFLZHhUStH9mSo4RaXzl5ZUtPJNg+wXVqiTKfDHz3eS
xHxUtpbvenQwlg3uimT+cBmhzYP87WvwM48IaXrqWBTB082z0COacg4FSuovaYeN
uw+UNeGHWPucZ9ZencxyljcXHjeVi+k2oNJqMfbxfOhzJNPua35Tq0MDmP3qaGKF
2YOnRKKaNNaS7XMSx1f64v0HBrUZftcKxQYdKdhXBd1IJoC00ygt0pOx2gNrNq2f
olJIeV9o/8V24ddvgPHWNKLGBtUCMwFV6lPyXWuJm7FlKDvRF5tiUbFKpAwcXZ84
QqIUSNFPfnJSVdxLutIk2o4TRtLPhFABSu3n+aYxWGQ/lOF3E4ZJb7qdbj6gbMNH
1cSa3gGOfMHl3kDipUUUAvRdMPOox9GzC/JFXpVDWtYajaQIF0JwEZ03Nbg41WXw
5t9MpiDWVpfQJZb4zUiZsdHLdcOSF8QZZvGyAl/rq7Bhs2Q6zLYTnpD1sbUHe8sl
kZMzVPgTz12NOn/sz1hCQLMCAwEAAQ==
-----END PUBLIC KEY-----

This is the message data:

{"msg_id":1220037023,"message":"hello","thumbnail":"https://accounts.tokopedia.com/image/v1/u/25088898/user_thumbnail/desktop","full_name":"Alvin","shop_id":858157,"user_id":25088898,"payload":{"attachment_type":0,"image":{"image_thumbnail":"","image_url":""},"product":{"image_url":"","name":"","price":"","product_id":0,"product_url":""}}}

Signature is:

nWjyoCpDZfYbhDcVyVMlJfu/3A1gMOgUyPosuLrtycQWSkvLNPhFGtFHW7kI3ByMXLzMiZRIyc6mg5p4AVrozey8+XIv2A3lAyynfGy10LiJXUlDttP/lDTPmg4VdXoIOnNEm283dCzVEsiiGhWcxuwx0XD0fD0CLIwwLN4nwOJiRroY6zyWzRCavv8q5zRHWNJnNRN6t6g6SpqZ4HQPOqRlUgMDH2mqLoiZbngnoOvkG1HgvJ1oySL+45rI/ZBLYUE/rZ4N5abI4oTxJ7K8REya1WxX6YVo0B9Gll2+xI+Z1G9QZCvZQRVYsYf8f0FmbmqDWQebbSm+UlsC6T69yBXvDIA17+TK/fZhlGCjuHClyZbJlpYYUJSIv1Sac8zTGj9rlStiSFR4a96p33SjqPlkbYXT9akDTMH4ao1SIUKNjVRSw8lN7pBZoLNyQwSR6yYqSIxAu6vbiS/DyLsfFDfheK3s8MkzdM7t0U4eqkbHsHbnJFEhXIAPwjgxd3a3uEfD47A0YpJMWQ1ve9WpPJWWSxApRMP80HzQIute86XNGNedLOhxBF9OeO4o82PCxJ9JGS4nRK+AGPAxQzgZq08jp5C2TdFXwwW3uAYViNE3u2Pdi17MDDhZ8fDAvhGWn1l8tbiZM/FN9HMR1mXO/jV/PhqDeJ80E6/R1O2POHM=

Here is the full shell script that I use to verify it:

#!/bin/bash

body=$1
publickey=$2
signature=$3
temp="./tmp"

if [[ $# -lt 3 ]] ; then
  echo "Usage: verify <request_body> <public_key> <signature>"
  exit 1
fi

echo -n $body > $temp.key
echo -n $signature > $temp.sign
openssl base64 -A -d -in $temp.sign -out $temp.sha256
openssl dgst -sha256 -sigopt rsa_padding_mode:pss -verify $publickey -signature $temp.sha256 $temp.key

rm $temp*

回答1:


The following BouncyCastle/C# code verifies a signed message. As digest SHA256 is used, as padding PSS (RSASSA-PSS). The public key has the X.509 format, PEM encoded.

The PEM key is loaded using a PemReader instance WLOG from a string (alternatively it can be loaded e.g. from the file system). Using the DotNetUtilities from BouncyCastle an RSAParameters instance is created which can be imported directly from RSACng with ImportParameters(). RSACng also encapsulates the methods for Signing/Verifying. Note that SignData/VerifyData expects the unhashed message (unlike SignHash/VerifyHash):

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
...
string x509Pem = @"-----BEGIN PUBLIC KEY-----
                MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEaA9DZlPzykcUY3aaqeT8Cmcx
                2qUNvB7/QqQQvkPjk+sxeFLqkppuvbbinN3FHMspPhJlOGZf+gRjmwiOoMEkZAHv
                nVfX7gtMxLyUAcXBXFx36t2QE5/45TZ4lzI3udvhAPj7uB1sUKDk5trB8EoX1sVA
                kKC9ynrKTPDnyNRDAwIDAQAB
                -----END PUBLIC KEY-----";

byte[] message = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
byte[] signature = Convert.FromBase64String(@"rsyqqY1bkGJJkZ4DOfXF+IOpYRdDETvy//PCYGbs70N5Vm8O0P5yqnnxuO5PT9hsOUgJMZeyWxeQITrvXu8buYyx4cah+DfYhOMUzrmyZbzjciTyqWGVYAcZEJNfS0fP8t0XSp5DjKXd1nmaMbB4LuBNwvuEdboFCtN6KRNPzFY=");
PemReader pr = new PemReader(new StringReader(x509Pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();

RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParams);

bool verified = rsaCng.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
Console.WriteLine(verified); // True

Tested with .NET Framework 4.8,

As a side note: BouncyCastle itself supports signing/verifying with the SignerUtilities class, so that RSACng does not have to be used necessarily.


Edit:

The first OpenSSL statement Base64 decodes the signature, the second OpenSSL statement verifies a message that was first hashed with SHA256 and then signed (with PSS as padding).

As already mentioned VerifyData() performs the hashing implicitly, i.e. the message must not be hashed and the ConvertToSHA256() method is not required. The first parameter in VerifyData() should be:

Encoding.UTF8.GetBytes(data)

where data is the message.

Please try this.

If you still have problems, please edit your question and post a reproducible example, i.e. concrete data for the message, signature and public key, like I've done it in my example.


Edit:

The PSS padding has different parameters which are usually assigned certain default values. One of these parameters is the salt length. In case of a salt length that is not equal to zero, PSS applies a randomly generated salt, which results in a different signature being generated each time (probabilistic). The common default value for the salt length is the digest output length, which is 32 bytes for SHA256, (RFC 8017, A.2.3. RSASSA-PSS).

The salt length can be set in OpenSSL with the addition -sigopt rsa_pss_saltlen:<length>. Besides the specification of a concrete length, there are version dependent special values, e.g. for v1.1.1 digest (salt length corresponds to the digest output length, in this case 32 bytes), auto (salt length is determined from the signature) and max (salt length corresponds to the maximum possible value), here.

If the length is explicitly specified with digest (or explicitly with 32 bytes) in the posted OpenSSL, the verification fails, i.e. the digest output length was not applied as salt length when signing. However, if the length is specified with max, the verification is successful, i.e. the maximum possible salt length was used during signing.
In contrast, BouncyCastle/C# applies the digest output length as default, which for SHA256 is 32 bytes.
Therefore the reason for the failed verification is a different salt length: Maximum salt length when signing and digest output length when verifying with C#.
Note: Since the salt length is not specified in the posted OpenSSL statement, the OpenSSL default value is used. This is unfortunately not provided in the OpenSSL documentation, but with regard to the successful verification it can only be max or auto (the verification with auto is of course also successful, because the salt length is then determined directly from the signature).

What exactly is the maximum salt length? The salt cannot be of any length. From the PSS specification the following maximum possible salt length (in bytes) is derived for a 4096 bits key/signature and SHA256 (here):

signature length - digest output length - 2 = 512 - 32 - 2 = 478

The value 478 can be easily verified by adding -sigopt rsa_pss_saltlen:478 to the posted OpenSSL statement. The verification is successful, which confirms this value.

To be precise, 478 is only the salt length for this one signature. For a definitive answer, the implementation with which the signature was created would have to be known. This could use varying salt lengths, which accidentally correspond to the maximum salt length for the posted signature. This means that a different signature could use a different salt length. But since such a logic is rather unlikely, it can be assumed that the implementation used for signing applies the maximum salt length.

The question remains how to specify a salt length in C# that differs from the default value. To my knowledge this is not possible with C# onboard means, i.e. with RSACng. But again, the answer is BouncyCastle, which provides the PssSigner class:

using Org.BouncyCastle.Crypto; 
using Org.BouncyCastle.Crypto.Digests; 
using Org.BouncyCastle.Crypto.Engines; 
using Org.BouncyCastle.Crypto.Signers; 
using Org.BouncyCastle.OpenSsl; 

...

PemReader pr = new PemReader(new StringReader(publicKeyString));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();

PssSigner pssSigner = new PssSigner(new RsaEngine(), new Sha256Digest(), 512 - 32 - 2);
pssSigner.Init(false, publicKey);
byte[] dataByte = Encoding.UTF8.GetBytes(data);
pssSigner.BlockUpdate(dataByte, 0, dataByte.Length);
valid = pssSigner.VerifySignature(signatureByte);
return valid;

...

With this the verification is successful.



来源:https://stackoverflow.com/questions/64940541/c-sharp-rsa-import-public-key

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!