C#RSA导入公钥 [英] C# RSA Import Public Key

查看:71
本文介绍了C#RSA导入公钥的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要验证签名数据.我不知道如何使用公钥.

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

RSA.ImportCspBlob 导致错误.我的公钥是字符串类型.看起来像这样:

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

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

如何验证?

此shell脚本可用于验证它:

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


错误

提供商的错误版本.

Bad Version of provider.


更新

所以我根据@Topaco更新了我的代码:


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


更新

因此,即使我在未转换为sha256的情况下更改了数据,验证仍然失败.


UPDATE

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

这是我的代码:

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

公钥是:

-----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-----

这是消息数据:

{"msg_id":1220037023,消息":"hello",缩略图":"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":",}",产品":{"image_url":",名称":",价格":","product_id":0,"product_url":"}}}

{"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":""}}}

签名为:

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

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

这是我用来验证它的完整shell脚本:

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*

推荐答案

以下BouncyCastle/C#代码验证签名消息.使用摘要SHA256作为填充PSS(RSASSA-PSS).公钥为X.509格式,PEM编码.

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.

使用 PemReader 实例WLOG从字符串中加载PEM密钥(或者可以从文件系统中加载).使用BouncyCastle的 DotNetUtilities ,可以创建一个 RSAParameters 实例,可以使用 ImportParameters() RSACng 直接导入该实例. RSACng 还封装了签名/验证.请注意, SignData / VerifyData 需要未散列消息(与 SignHash / VerifyHash 不同):

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

经过.NET Framework 4.8的测试,

Tested with .NET Framework 4.8,

请注意:BouncyCastle本身支持使用 SignerUtilities 类进行签名/验证,因此不必使用 RSACng .

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

第一个OpenSSL语句Base64解码签名,第二个OpenSSL语句验证一条消息,该消息首先使用SHA256进行哈希处理,然后再进行签名(使用PSS作为填充).

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).

如前所述, VerifyData()隐式执行散列 ,即消息必须散列, ConvertToSHA256()方法不是必需的. VerifyData()中的第一个参数应为:

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)

其中 data 是消息.

请尝试这个.

如果您仍然有问题,请编辑您的问题并发布一个可重现的示例,即消息,签名和公钥的具体数据,就像我在示例中所做的一样.

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.

PSS填充具有不同的参数,通常为这些参数分配某些默认值.这些参数之一是盐长.如果盐长度不等于零,则PSS会应用随机生成的盐,这会导致每次生成的签名都不相同(概率).盐长度的默认默认值是摘要输出长度,对于SHA256,它是32个字节,(

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).

可以在OpenSSL中使用附加的 -sigopt rsa_pss_saltlen:< length> 来设置盐的长度.除了具体长度的规范外,还有版本相关的特殊值,例如对于v1.1.1 digest (盐长对应于摘要输出长度,在这种情况下为32字节), auto (盐长由签名确定)和max (盐长对应于可能的最大值),这里.

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.

如果在发布的OpenSSL中用 digest 显式指定了长度(或显式用32字节),则验证失败,即签名时未将摘要输出长度用作盐长度.但是,如果使用 max 指定长度,则验证成功,即在签名期间使用了最大可能的盐长度.
相比之下,BouncyCastle/C#将摘要输出长度作为默认值应用,对于SHA256,摘要输出长度为32字节.
因此,验证失败的原因是盐长度不同:签名时的最大盐长度和使用C#验证时的摘要输出长度.
注意:由于盐长度没有在发布的OpenSSL语句中指定,因此使用OpenSSL默认值.不幸的是,OpenSSL文档中未提供此功能,但是对于成功的验证,只能为 max auto (使用 auto 进行的验证)当然也成功了,因为盐的长度是直接从签名中确定的.

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).

最大盐长度到底是多少?盐不能有任何长度.根据PSS规范,对于4096位密钥/签名和SHA256(此处):

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

可以通过在发布的OpenSSL语句中添加 -sigopt rsa_pss_saltlen:478 来轻松地验证值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.

准确地说,478只是该签名的盐长.对于确定的答案,必须知道用于创建签名的实现.这可能会使用不同的盐长度,这偶然地对应于已张贴签名的最大盐长度.这意味着不同的签名可能使用不同的盐长.但是由于这种逻辑不太可能,因此可以假定用于签名的实现应用了最大盐长度.

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.

问题仍然是如何在C#中指定与默认值不同的盐长.据我所知,这是使用C#板载工具(即 RSACng )无法实现的.但是,答案还是BouncyCastle,它提供了 PssSigner 类:

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;

...

验证成功.

这篇关于C#RSA导入公钥的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆