使用 mbedtls 生成的 RSA 签名,无法使用 C#bouncycastle应用程序进行验证

问题描述

我正在使用 mbedtls 与 RSA 签署一个 32 字节的质询。

C 代码的相关行如下所示;我正在使用私钥在 32 字节“挑战”数组的“签名”中创建签名:

mbedtls_rsa_context rsa;

mbedtls_rsa_init(&rsa,MbedTLS_RSA_PKCS_V21,MbedTLS_MD_SHA256);

mbedtls_rsa_rsassa_pss_sign(&rsa,f_rng,&prng,MbedTLS_RSA_PRIVATE,MbedTLS_MD_SHA256,32,challenge,signature)

这行得通,我可以用 mbedtls 验证签名。 'rsa' 是另一个实例,这个实例使用公钥:

mbedtls_rsa_rsassa_pss_verify(&rsa,NULL,MbedTLS_RSA_PUBLIC,sizeof(challenge),signature);

到目前为止,一切都很好。我无法开始工作的是在 C# 应用程序中验证此签名。我从 RSACryptoServiceProvider 类开始,无法让它工作。最终发现了充气城堡的东西。看起来比 RSACryptoServiceProvider 好,但我也不能让它工作。

我正在加载 mbedtls 中使用的公钥、质询和签名。

RsaKeyParameters key = new RsaKeyParameters(false,new Org.BouncyCastle.Math.BigInteger(Nstring,16),new Org.BouncyCastle.Math.BigInteger(Estring,16));

ISigner sig = SignerUtilities.GetSigner("SHA256WITHRSA/PSS");

sig.Init(false,key);

sig.BlockUpdate(challenge,challenge.Length);
Console.WriteLine("result: " + sig.VerifySignature(signature));

VerifySignature 失败 ("result: false").... :(

我想我的数据格式正确。这是C端的公钥指数和模数:

unsigned char E[] = { 0x01,0x00,0x01 };
unsigned char N[] = { 0xC2,0x7E,0xC0,0xCD,0x1B,0xEA,0xE1,0x2E,0x5F,0x15,0xE3,0x9A,0xA3,0x5C,0xF2,0x0A,0xB5,0xAE,0x7F,0x22,0xE0,0x8A,0xA8,0xA7,0x44,0x8E,0xDD,0x1F,0x3C,0xDA,0xE5,0xBB,0x23,0x8F,0xED,0xFA,0xDF,0xC4,0x95,0x72,0x67,0x49,0xCF,0xE7,0x35,0x56,0x16,0xCC,0x5A,0x37,0x0C,0x6B,0x79,0xA5,0x5B,0x4C,0x48,0x62,0x59,0xFD,0x60,0x4A,0xFF,0xB6,0xD2,0xEB,0x41,0xF3,0xB1,0x2F,0x61,0x55,0x68,0xD1,0x77,0x7C,0xCA,0xF9,0x6D,0x03,0x89,0x9D,0x1E,0x4D,0x90,0x80,0x75,0xAA,0x0D,0xA1,0xD8,0xA0,0x97,0x4B,0x39,0x7D };

这就是我在 C# 端使用它们的方式:

Estring = "010001";
Nstring = "C27EC0CD1BEAE12E5F15E39AA35CF20AB5AE7F22E08AA8A7448EDD1F3CDDDAE5BB238FF2EDFADFC49572670049CFCDE7355649E716CC5A9A37FA0C6B79A55B5A4C1F48E76259FD604ACCFFB662D2EB41F3B1DA2F615568D1777C16CA622FF96D03EDCF899D1E5BA84D90DF801B75E06DAA0D8ACCA15BE5D8A097FF754BDA397D";

'challenge' 和 'signature' 是两边的 8 位(无符号字符/字节)数组,包含相同的数据。

--编辑(添加一些实际的挑战/签名值)--

unsigned char challenge[32] = {
0x1E,0x36,0x82,0x2A,0xDE,0x7D,0x92,0x5E,0x25,0xE9,0x3A,0x30,0x9B,0xB0,0x26,0x3D,0x2E
};

unsigned char signature[128] = {
0xA7,0x64,0x07,0xD0,0x06,0x0E,0x3F,0x6C,0xFB,0xB8,0xDC,0xC5,0x65,0xD5,0x7A,0x74,0xFC,0x96,0x01,0x53,0xBC,0x40,0xC8,0xC9,0xC2,0x84,0xC6,0x51,0x29,0xBF,0x4E,0x81,0x08,0x6A,0x43,0x14,0xF6,0x2D,0xE8,0x94,0x86,0x19,0xD3,0x17,0xAB,0xE6,0x7B,0x11,0xD7,0x50,0xF8,0xB4,0xF5,0xCB,0x70,0xA9,0xC3,0x54,0xF4,0x32,0x6E,0x78,0x98,0x69,0x7F
};

在这里遗漏了什么?

--编辑(添加一些复制/粘贴就绪代码)--

mbedtls/c++ 项目,有效(输出“验证成功”)

#include <stdio.h>
#include "mbedtls/config.h"
#include "mbedtls/platform.h"
#include "mbedtls/error.h"
#include "mbedtls/rsa.h"
#include "mbedtls/error.h"
#include "mbedtls/bignum.h"

int main(int argc,char* argv[])
{
    int ret;
    char errbuf[100];
    mbedtls_rsa_context ctx;

    /* Key */
    unsigned char E[] = { 0x01,0x01 };
    unsigned char N[] = { 0xC2,0x7D };

    /* Challenge */
    unsigned char challenge[] = {
        0x1E,0x2E
    };

    /* Response */
    unsigned char responseGiven[] = {
        0xA7,0x7F
    };

    mbedtls_rsa_init(&ctx,MbedTLS_MD_SHA256);

    /* Load public key */
    if ((ret = mbedtls_rsa_import_raw(&ctx,N,sizeof(N),E,sizeof(E))) != 0) {
        mbedtls_strerror(ret,errbuf,sizeof(errbuf));
        mbedtls_printf("mbedtls_rsa_import_raw Failed,returned %d,%s\n\n",ret,errbuf);
        return 0;
    }
    if ((ret = mbedtls_rsa_complete(&ctx)) != 0) {
        mbedtls_strerror(ret,sizeof(errbuf));
        mbedtls_printf("mbedtls_rsa_complete Failed,errbuf);
        return 0;
    }

    /* Verify response */
    if ((ret = mbedtls_rsa_rsassa_pss_verify(&ctx,responseGiven))) {
        mbedtls_strerror(ret,sizeof(errbuf));
        mbedtls_printf("mbedtls_rsa_rsassa_pss_verify Failed,returned %d - %s\n\n",errbuf);
        return 0;
    }
    else {
        mbedtls_printf("Verification success\n");
    }

    getchar();
    return 1;
}

Bouncycastle/C# 项目,失败(输出“VerifySignature Failed”)

using System;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Digests;

namespace cryptotest
{
    class Program
    {
        static void Main(string[] args)
        {
            /* Key */
            byte[] E = new byte[] { 0x01,0x01 };
            byte[] N = new byte[] { 0xC2,0x7D };

            /* Challenge */
            byte[] challenge = new byte[] {
                0x1E,0x2E
            };

            /* Response */
            byte[] responseGiven = new byte[]{
                0xA7,0x7F
            };

            /* Load public key. Modulus(N),exponent (E) */
            string Nstring = "";
            for (int i = 0; i < N.Length; i++) {
                Nstring += N[i].ToString("X2");
            }
            string Estring = "";
            for (int i = 0; i < E.Length; i++) {
                Estring += E[i].ToString("X2");
            }
            RsaKeyParameters key = new RsaKeyParameters(false,16));
            PssSigner pss = new PssSigner(new RSAEngine(),new Sha256Digest(),0xBC);
            pss.Init(false,key);

            /* Verify response */
            pss.BlockUpdate(challenge,challenge.Length);
            if(pss.VerifySignature(responseGiven) == false) {
                Console.WriteLine("VerifySignature Failed");
            }
            else {
                Console.WriteLine("Verification success of given response");
            }

            Console.ReadKey();
        }
    }
}

解决方法

C/C++ 代码在验证时需要消息的散列值,因此在验证前不会散列值。另一方面,C# 代码在验证时需要消息本身,并在验证自身之前执行散列。

因此,如果将消息的哈希值传递给 C# 代码而不是消息,则使用单个哈希值消息的签名验证双哈希值消息,但会失败。

如果在验证之前在类似于C/C++代码的C#代码中不进行散列,则可以解决该问题。为此,例如在 .NET Core 下,可以使用 RSACng#VerifyHash() 执行验证,与 RSACng#VerifyData() 相比,它需要类似于 C/C++ 代码的消息哈希。

一个可能的实现是:

using System;
using System.Security.Cryptography;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
...
/* Key */
byte[] E = new byte[] { 0x01,0x00,0x01 };
byte[] N = new byte[] { 0xC2,0x7E,0xC0,0xCD,0x1B,0xEA,0xE1,0x2E,0x5F,0x15,0xE3,0x9A,0xA3,0x5C,0xF2,0x0A,0xB5,0xAE,0x7F,0x22,0xE0,0x8A,0xA8,0xA7,0x44,0x8E,0xDD,0x1F,0x3C,0xDA,0xE5,0xBB,0x23,0x8F,0xED,0xFA,0xDF,0xC4,0x95,0x72,0x67,0x49,0xCF,0xE7,0x35,0x56,0x16,0xCC,0x5A,0x37,0x0C,0x6B,0x79,0xA5,0x5B,0x4C,0x48,0x62,0x59,0xFD,0x60,0x4A,0xFF,0xB6,0xD2,0xEB,0x41,0xF3,0xB1,0x2F,0x61,0x55,0x68,0xD1,0x77,0x7C,0xCA,0xF9,0x6D,0x03,0x89,0x9D,0x1E,0x4D,0x90,0x80,0x75,0xAA,0x0D,0xA1,0xD8,0xA0,0x97,0x4B,0x39,0x7D };

/* Challenge */
byte[] challenge = new byte[] {
    0x1E,0x36,0x82,0x2A,0xDE,0x7D,0x92,0x5E,0x25,0xE9,0x3A,0x30,0x9B,0xB0,0x26,0x3D,0x2E
};

/* Response */
byte[] responseGiven = new byte[]{
    0xA7,0x64,0x07,0xD0,0x06,0x0E,0x3F,0x6C,0xFB,0xB8,0xDC,0xC5,0x65,0xD5,0x7A,0x74,0xFC,0x96,0x01,0x53,0xBC,0x40,0xC8,0xC9,0xC2,0x84,0xC6,0x51,0x29,0xBF,0x4E,0x81,0x08,0x6A,0x43,0x14,0xF6,0x2D,0xE8,0x94,0x86,0x19,0xD3,0x17,0xAB,0xE6,0x7B,0x11,0xD7,0x50,0xF8,0xB4,0xF5,0xCB,0x70,0xA9,0xC3,0x54,0xF4,0x32,0x6E,0x78,0x98,0x69,0x7F
};

/* Load public key. Modulus(N),exponent (E) */
string Nstring = "";
for (int i = 0; i < N.Length; i++)
{
    Nstring += N[i].ToString("X2");
}
string Estring = "";
for (int i = 0; i < E.Length; i++)
{
    Estring += E[i].ToString("X2");
}
RsaKeyParameters key = new RsaKeyParameters(false,new Org.BouncyCastle.Math.BigInteger(Nstring,16),new Org.BouncyCastle.Math.BigInteger(Estring,16));

/*
PssSigner pss = new PssSigner(new RsaEngine(),new Sha256Digest(),32,0xBC);
pss.Init(false,key);

// Verify response
pss.BlockUpdate(challenge,challenge.Length);
if (pss.VerifySignature(responseGiven) == false)
{
    Console.WriteLine("VerifySignature failed");
}
else
{
    Console.WriteLine("Verification success of given response");
}

Console.ReadKey();
*/

RSA rsa = new RSACng();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)key);
rsa.ImportParameters(rsaParams);
bool verified = rsa.VerifyHash(challenge,responseGiven,HashAlgorithmName.SHA256,RSASignaturePadding.Pss);
Console.WriteLine("Verified: " + verified); // Verified: True

Console.ReadKey();

使用此代码验证成功。

或者,在原始 C# 代码中,可以传递消息本身而不是散列。