C# RSA 导入 Public 密钥
C# RSA Import Public Key
我需要验证签名数据。我不知道如何使用 public 键。
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
导致错误。我的 public 键是字符串类型。它看起来像这样:
-----BEGIN PUBLIC KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXXX
-----END PUBLIC KEY-----
如何验证?
备注
这个shell脚本可以用来验证:
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更新了我的代码:
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();
}
}
不知道是我之前的步骤有误,还是验证有误
如果我运行脚本,验证成功。这个脚本:
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就更改了数据,验证仍然失败。
这是我的代码:
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;
}
public键是:
-----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,"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=
这是我用来验证它的完整 shell 脚本:
#!/bin/bash
body=
publickey=
signature=
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)。 public 密钥采用 PEM 编码的 X.509 格式。
PEM 密钥使用 PemReader
实例 WLOG 从字符串加载(或者它可以从文件系统加载)。使用 BouncyCastle 中的 DotNetUtilities
创建了一个 RSAParameters
实例,可以使用 ImportParameters()
直接从 RSACng
导入。 RSACng
还封装了方法
Signing/Verifying。请注意,SignData
/VerifyData
需要 未散列的 消息(不同于 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 进行测试,
附带说明:BouncyCastle 本身支持 signing/verifying 和 SignerUtilities
class,因此不一定要使用 RSACng
。
编辑:
第一个 OpenSSL 语句 Base64 解码签名,第二个 OpenSSL 语句验证首先使用 SHA256 散列然后签名(使用 PSS 作为填充)的消息。
如前所述,VerifyData()
执行散列 隐式 ,即消息必须 不 散列并且 ConvertToSHA256()
方法不是必需的。 VerifyData()
中的第一个参数应该是:
Encoding.UTF8.GetBytes(data)
其中 data
是消息。
请试试这个。
如果您仍有问题,请编辑您的问题和post一个可重现的示例,即消息、签名和public密钥的具体数据,就像我在示例中所做的那样。
编辑:
PSS padding有不同的参数,这些参数通常被赋予一定的默认值。这些参数之一是盐长度。如果盐长度不为零,PSS 会应用随机生成的盐,这会导致每次生成不同的签名(概率)。盐长度的常见默认值是摘要输出长度,对于 SHA256 为 32 字节,(RFC 8017, A.2.3. RSASSA-PSS).
加盐长度可以在 OpenSSL 中设置 -sigopt rsa_pss_saltlen:<length>
。除了具体长度的规范外,还有与版本相关的特殊值,例如对于 v1.1.1 digest
(salt 长度对应于摘要输出长度,在本例中为 32 字节),auto
(salt 长度由签名确定)和 max
(salt 长度对应到最大可能值), here.
如果在 posted OpenSSL 中用 digest
(或明确用 32 字节)明确指定长度,则验证失败,即当签署。
但是,如果长度指定为max
,则验证成功,即在签名时使用了最大可能的盐长度。
相反,BouncyCastle/C#默认应用摘要输出长度,对于 SHA256 为 32 字节。
因此,验证失败的原因是盐长度不同:签名时的最大盐长度和使用 C# 验证时的摘要输出长度。
注意:由于在 posted OpenSSL 语句中未指定盐长度,因此使用 OpenSSL 默认值。不幸的是OpenSSL文档中没有提供,但是关于验证成功只能是max
或auto
(用auto
验证当然也成功了,因为salt然后直接根据签名确定长度)。
最大盐长度到底是多少?盐不能有任何长度。根据 PSS 规范,针对 4096 位 key/signature 和 SHA256 (here) 得出以下最大可能盐长度(以字节为单位):
signature length - digest output length - 2 = 512 - 32 - 2 = 478
可以通过将 -sigopt rsa_pss_saltlen:478
添加到 posted OpenSSL 语句来轻松验证值 478。验证成功,确认了这个值。
准确的说,478只是这个签名的salt长度。要获得明确的答案,必须知道创建签名的实现方式。这可以使用不同的盐长度,这意外地对应于 posted 签名的最大盐长度。这意味着不同的签名可以使用不同的盐长度。但是由于这种逻辑不太可能,可以假设用于签名的实现应用了最大盐长度。
问题仍然是如何在 C# 中指定不同于默认值的加盐长度。据我所知,这对于 C# 板载方式是不可能的,即 RSACng
。但同样,答案是 BouncyCastle,它提供 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;
...
至此验证成功
我需要验证签名数据。我不知道如何使用 public 键。
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
导致错误。我的 public 键是字符串类型。它看起来像这样:
-----BEGIN PUBLIC KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXXX
-----END PUBLIC KEY-----
如何验证?
备注
这个shell脚本可以用来验证:
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更新了我的代码:
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();
}
}
不知道是我之前的步骤有误,还是验证有误
如果我运行脚本,验证成功。这个脚本:
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就更改了数据,验证仍然失败。
这是我的代码:
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;
}
public键是:
-----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,"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=
这是我用来验证它的完整 shell 脚本:
#!/bin/bash
body=
publickey=
signature=
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)。 public 密钥采用 PEM 编码的 X.509 格式。
PEM 密钥使用 PemReader
实例 WLOG 从字符串加载(或者它可以从文件系统加载)。使用 BouncyCastle 中的 DotNetUtilities
创建了一个 RSAParameters
实例,可以使用 ImportParameters()
直接从 RSACng
导入。 RSACng
还封装了方法
Signing/Verifying。请注意,SignData
/VerifyData
需要 未散列的 消息(不同于 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 进行测试,
附带说明:BouncyCastle 本身支持 signing/verifying 和 SignerUtilities
class,因此不一定要使用 RSACng
。
编辑:
第一个 OpenSSL 语句 Base64 解码签名,第二个 OpenSSL 语句验证首先使用 SHA256 散列然后签名(使用 PSS 作为填充)的消息。
如前所述,VerifyData()
执行散列 隐式 ,即消息必须 不 散列并且 ConvertToSHA256()
方法不是必需的。 VerifyData()
中的第一个参数应该是:
Encoding.UTF8.GetBytes(data)
其中 data
是消息。
请试试这个。
如果您仍有问题,请编辑您的问题和post一个可重现的示例,即消息、签名和public密钥的具体数据,就像我在示例中所做的那样。
编辑:
PSS padding有不同的参数,这些参数通常被赋予一定的默认值。这些参数之一是盐长度。如果盐长度不为零,PSS 会应用随机生成的盐,这会导致每次生成不同的签名(概率)。盐长度的常见默认值是摘要输出长度,对于 SHA256 为 32 字节,(RFC 8017, A.2.3. RSASSA-PSS).
加盐长度可以在 OpenSSL 中设置 -sigopt rsa_pss_saltlen:<length>
。除了具体长度的规范外,还有与版本相关的特殊值,例如对于 v1.1.1 digest
(salt 长度对应于摘要输出长度,在本例中为 32 字节),auto
(salt 长度由签名确定)和 max
(salt 长度对应到最大可能值), here.
如果在 posted OpenSSL 中用 digest
(或明确用 32 字节)明确指定长度,则验证失败,即当签署。
但是,如果长度指定为max
,则验证成功,即在签名时使用了最大可能的盐长度。
相反,BouncyCastle/C#默认应用摘要输出长度,对于 SHA256 为 32 字节。
因此,验证失败的原因是盐长度不同:签名时的最大盐长度和使用 C# 验证时的摘要输出长度。
注意:由于在 posted OpenSSL 语句中未指定盐长度,因此使用 OpenSSL 默认值。不幸的是OpenSSL文档中没有提供,但是关于验证成功只能是max
或auto
(用auto
验证当然也成功了,因为salt然后直接根据签名确定长度)。
最大盐长度到底是多少?盐不能有任何长度。根据 PSS 规范,针对 4096 位 key/signature 和 SHA256 (here) 得出以下最大可能盐长度(以字节为单位):
signature length - digest output length - 2 = 512 - 32 - 2 = 478
可以通过将 -sigopt rsa_pss_saltlen:478
添加到 posted OpenSSL 语句来轻松验证值 478。验证成功,确认了这个值。
准确的说,478只是这个签名的salt长度。要获得明确的答案,必须知道创建签名的实现方式。这可以使用不同的盐长度,这意外地对应于 posted 签名的最大盐长度。这意味着不同的签名可以使用不同的盐长度。但是由于这种逻辑不太可能,可以假设用于签名的实现应用了最大盐长度。
问题仍然是如何在 C# 中指定不同于默认值的加盐长度。据我所知,这对于 C# 板载方式是不可能的,即 RSACng
。但同样,答案是 BouncyCastle,它提供 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;
...
至此验证成功