使用rsa在c#中验证来自节点的签名数据
Verifying signed data from node in c# using rsa
我有以下代码在 .js 脚本中签署一些数据:
const { RSA_PKCS1_PSS_PADDING } = require('constants');
const crypto = require('crypto');
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
});
const fs = require('fs');
const keys = fs.createWriteStream('keys.txt');
keys.write(`${publicKey}\n`);
keys.write(`${privateKey}\n`);
function signature(verifyData) {
return crypto.createSign('sha256').sign({
keyLike: Buffer.from(verifyData),
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
}).toString('base64');
}
该脚本将使用我的 public 和私钥创建一个 txt 文件,如下所示:
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
我尝试了几种方法来为相同的输入生成与 .js 脚本相同的散列,但没有成功。它也无法验证 .js 脚本创建的任何哈希值。以下是我的实现:
private RsaKeyParameters readPrivateKey(string privateKeyFileName)
{
RsaKeyParameters keyPair;
using (var reader = File.OpenText(privateKeyFileName))
keyPair = (RsaKeyParameters)new PemReader(reader).ReadObject();
return keyPair;
}
bool VerifyDataBouncyCastle(string bodyData, string signature)
{
var data = bodyData;
var signatureBytes = Convert.FromBase64String(signature);
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
signer.Init(false, readPrivateKey($"{DiretorioBase}\public.txt"));
signer.BlockUpdate(Encoding.UTF8.GetBytes(data), 0, data.Length);
var success = signer.VerifySignature(signatureBytes);
return success;
}
string SignDataBouncyCastle(string data)
{
// Verify using the public key
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
signer.Init(true, readPrivateKey($"{DiretorioBase}\private.txt"));
signer.BlockUpdate(Encoding.UTF8.GetBytes(data), 0, data.Length);
return Convert.ToBase64String(signer.GenerateSignature());
}
public byte[] SignDataNetCore(byte[] data)
{
// privateKey does not have the ---BEGIN and ---END headers.
var privateKey = File.ReadAllText($"{DiretorioBase}\private.txt");
var rsaPrivateKey = RSA.Create();
rsaPrivateKey.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out _);
return rsaPrivateKey.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public bool VerifyDataNetCore(byte[] data, byte[] signature)
{
var publicKey = File.ReadAllText($"{DiretorioBase}\public.txt");
var rsaPublicKey = RSA.Create();
rsaPublicKey.ImportFromPem(publicKey);
return rsaPublicKey.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
None 以上方法将使用 .js 脚本生成的相同输入和相同密钥生成相同的签名。
我错过了什么?
--编辑--
我把.js签名方法改成这样:
function signature(verifyData) {
var cSign = crypto.createSign('sha256');
cSign.update(verifyData);
return cSign.sign({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
});
}
C#验证代码为:
bool isVerified()
{
string x509Pem = @"-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----";
byte[] message = Encoding.UTF8.GetBytes(validar);
byte[] signature = Convert.FromBase64String(hash64);
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);
return verified;
}
它仍然 returns 错误。
PSS 有很多参数,包括盐长度。 RFC8017, A.2.3. RSASSA-PSS 定义与摘要输出长度相对应的默认盐长度,即 SHA256 为 32 字节。
您最近的 C# 代码应用了使用此默认加盐长度的 C# 内置 类。无法指定不同的盐长度!
另一方面,NodeJS 代码默认为最大可能的加盐长度 (crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
),它由以下公式给出:
<keysize> - <digest output length> - 2 = 256 - 32 - 2 = 222
.
因此,这两个代码是不兼容的!
不同于 C# 内置 类,BouncyCastle 允许配置盐长度:
string x509Pem = @"-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----";
string validar = "...";
string hash64 = "...";
byte[] message = Encoding.UTF8.GetBytes(validar);
byte[] signature = Convert.FromBase64String(hash64);
PemReader pr = new PemReader(new StringReader(x509Pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
PssSigner pssSigner = new PssSigner(new RsaEngine(), new Sha256Digest(), 256 - 32 - 2);
pssSigner.Init(false, publicKey);
pssSigner.BlockUpdate(message, 0, message.Length);
bool valid = pssSigner.VerifySignature(signature); // succeeds when the maximum possible salt length is used
至此,验证成功
请注意,在 NodeJS 代码中,您可以显式 将加盐长度更改为摘要的输出长度 (crypto.constants.RSA_PSS_SALTLEN_DIGEST
)。然后验证也将使用内置的 C# 类.
我有以下代码在 .js 脚本中签署一些数据:
const { RSA_PKCS1_PSS_PADDING } = require('constants');
const crypto = require('crypto');
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
});
const fs = require('fs');
const keys = fs.createWriteStream('keys.txt');
keys.write(`${publicKey}\n`);
keys.write(`${privateKey}\n`);
function signature(verifyData) {
return crypto.createSign('sha256').sign({
keyLike: Buffer.from(verifyData),
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
}).toString('base64');
}
该脚本将使用我的 public 和私钥创建一个 txt 文件,如下所示:
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
我尝试了几种方法来为相同的输入生成与 .js 脚本相同的散列,但没有成功。它也无法验证 .js 脚本创建的任何哈希值。以下是我的实现:
private RsaKeyParameters readPrivateKey(string privateKeyFileName)
{
RsaKeyParameters keyPair;
using (var reader = File.OpenText(privateKeyFileName))
keyPair = (RsaKeyParameters)new PemReader(reader).ReadObject();
return keyPair;
}
bool VerifyDataBouncyCastle(string bodyData, string signature)
{
var data = bodyData;
var signatureBytes = Convert.FromBase64String(signature);
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
signer.Init(false, readPrivateKey($"{DiretorioBase}\public.txt"));
signer.BlockUpdate(Encoding.UTF8.GetBytes(data), 0, data.Length);
var success = signer.VerifySignature(signatureBytes);
return success;
}
string SignDataBouncyCastle(string data)
{
// Verify using the public key
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
signer.Init(true, readPrivateKey($"{DiretorioBase}\private.txt"));
signer.BlockUpdate(Encoding.UTF8.GetBytes(data), 0, data.Length);
return Convert.ToBase64String(signer.GenerateSignature());
}
public byte[] SignDataNetCore(byte[] data)
{
// privateKey does not have the ---BEGIN and ---END headers.
var privateKey = File.ReadAllText($"{DiretorioBase}\private.txt");
var rsaPrivateKey = RSA.Create();
rsaPrivateKey.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out _);
return rsaPrivateKey.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public bool VerifyDataNetCore(byte[] data, byte[] signature)
{
var publicKey = File.ReadAllText($"{DiretorioBase}\public.txt");
var rsaPublicKey = RSA.Create();
rsaPublicKey.ImportFromPem(publicKey);
return rsaPublicKey.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
None 以上方法将使用 .js 脚本生成的相同输入和相同密钥生成相同的签名。 我错过了什么?
--编辑--
我把.js签名方法改成这样:
function signature(verifyData) {
var cSign = crypto.createSign('sha256');
cSign.update(verifyData);
return cSign.sign({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
});
}
C#验证代码为:
bool isVerified()
{
string x509Pem = @"-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----";
byte[] message = Encoding.UTF8.GetBytes(validar);
byte[] signature = Convert.FromBase64String(hash64);
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);
return verified;
}
它仍然 returns 错误。
PSS 有很多参数,包括盐长度。 RFC8017, A.2.3. RSASSA-PSS 定义与摘要输出长度相对应的默认盐长度,即 SHA256 为 32 字节。
您最近的 C# 代码应用了使用此默认加盐长度的 C# 内置 类。无法指定不同的盐长度!
另一方面,NodeJS 代码默认为最大可能的加盐长度 (crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
),它由以下公式给出:
<keysize> - <digest output length> - 2 = 256 - 32 - 2 = 222
.
因此,这两个代码是不兼容的!
不同于 C# 内置 类,BouncyCastle 允许配置盐长度:
string x509Pem = @"-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----";
string validar = "...";
string hash64 = "...";
byte[] message = Encoding.UTF8.GetBytes(validar);
byte[] signature = Convert.FromBase64String(hash64);
PemReader pr = new PemReader(new StringReader(x509Pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
PssSigner pssSigner = new PssSigner(new RsaEngine(), new Sha256Digest(), 256 - 32 - 2);
pssSigner.Init(false, publicKey);
pssSigner.BlockUpdate(message, 0, message.Length);
bool valid = pssSigner.VerifySignature(signature); // succeeds when the maximum possible salt length is used
至此,验证成功
请注意,在 NodeJS 代码中,您可以显式 将加盐长度更改为摘要的输出长度 (crypto.constants.RSA_PSS_SALTLEN_DIGEST
)。然后验证也将使用内置的 C# 类.