简体   繁体   English

使用rsa验证来自c#节点的签名数据

[英]Verifying signed data from node in c# using rsa

I have the following code that signs some data in a .js script:我有以下代码在 .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');
}

The script will create a txt file with my public and private keys, such as follows:该脚本将使用我的公钥和私钥创建一个 txt 文件,如下所示:

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----


-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

I tried several ways to generate the same hash as the .js script for the same input and no success.我尝试了几种方法来为相同的输入生成与 .js 脚本相同的哈希值,但没有成功。 It also cannot verify any hashes created by the .js script.它也无法验证 .js 脚本创建的任何哈希值。 Below are my implementations:以下是我的实现:

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 of the above methods will produce the same signature using the same input and same key generated by the .js script.上述方法都不会使用 .js 脚本生成的相同输入和相同密钥生成相同的签名。 What am I missing?我错过了什么?

--Edit-- - 编辑 -

I changed the .js signature method like this:我像这样更改了 .js 签名方法:

function signature(verifyData) {
  var cSign = crypto.createSign('sha256');

  cSign.update(verifyData);

  return cSign.sign({
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
  });
}

And the C# verified code to this:和 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;
        }

It still returns false.它仍然返回false。

PSS has a number of parameters, including the salt length. PSS 有许多参数,包括盐长度。RFC8017, A.2.3.RFC8017,A.2.3。RSASSA-PSS defines a default salt length that corresponds to the output length of the digest, ie 32 bytes for SHA256.RSASSA-PSS定义了与摘要的输出长度相对应的默认盐长度,即 SHA256 的 32 字节。

Your recent C# code applies the C# built-in classes that use this default salt length.您最近的 C# 代码应用了使用此默认盐长度的 C# 内置类。 A different salt length cannot be specified!不能指定不同的盐长度!

The NodeJS code, on the other hand, defaults to the maximum possible salt length ( crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN ), which is given by:另一方面, crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN代码默认为最大可能的盐长度( crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN ),由下式给出:
<keysize> - <digest output length> - 2 = 256 - 32 - 2 = 222 . <keysize> - <digest output length> - 2 = 256 - 32 - 2 = 222

Thus, the two codes are incompatible!因此,这两个代码是不兼容的!


Unlike the C# built-in classes, BouncyCastle allows the salt length to be configured:与 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

With this, verification is successful.至此,验证成功。


Note that in the NodeJS code you can explicitly change the salt length to the output length of the digest ( crypto.constants.RSA_PSS_SALTLEN_DIGEST ).请注意,在 NodeJS 代码中,您可以将盐长度显式更改为摘要的输出长度( crypto.constants.RSA_PSS_SALTLEN_DIGEST )。 Then verification will also work with the built-in C# classes.然后验证也适用于内置的 C# 类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM