无法使用 C 风格的函数调用验证签名

Posted

技术标签:

【中文标题】无法使用 C 风格的函数调用验证签名【英文标题】:Cannot verify signature using C-style function calls 【发布时间】:2018-03-26 15:48:30 【问题描述】:

以下打印失败,我不明白为什么:

#include <cryptopp/eccrypto.h>
#include <cryptopp/oids.h>
#include <cryptopp/osrng.h>
#include <iostream>
using namespace std;
using namespace CryptoPP;
int main() 
    AutoSeededRandomPool prng;
    ECDSA<ECP, SHA256>::PrivateKey private_key;
    ECDSA<ECP, SHA256>::PublicKey public_key;
    private_key.Initialize( prng, ASN1::secp160r1() );
    private_key.MakePublicKey(public_key);
    ECDSA<ECP, SHA256>::Signer signer(private_key);
    ECDSA<ECP, SHA256>::Verifier verifier(public_key);
    signer.AccessKey().Initialize(prng, ASN1::secp160r1());
    string signature(signer.MaxSignatureLength(), 0);
    string message = "asdf";
    auto signature_length = signer.SignMessage(
        prng, (const byte*)message.data(),
        message.size(), (byte*)signature.data());
    signature.resize(signature_length);
    bool verified = verifier.VerifyMessage(
        (const byte*)message.data(), message.size(),
        (const byte*)signature.data(), signature.size());
    if (verified)
        cout << "PASS" << endl;
    else
        cout << "FAIL" << endl;

它遵循 crypto++ wiki 中的说明:https://www.cryptopp.com/wiki/ECDSA#Message_Signing,并使用从用于签署同一消息的私钥派生的公钥进行验证。我应该切换到过滤器吗?

【问题讨论】:

请修改您的源代码以创建更明确的错误消息,然后使用修改后的源代码和错误消息更新您的帖子。 你建议我怎么做? VerifyMessage 返回 bool,所以我不能说验证失败的程度。 我不知道你正在使用这个包,所以我不能给你任何关于获取更具体的错误信息的建议。但是,正如您所看到的,其他人也认为您的帖子缺少任何人提供帮助所需的信息。大多数软件包都有一些机制,用于在发生故障时获取附加信息。 errnoGetLastError() 是一个经典的例子。 如果签名确实不是由相同的密钥(对)创建的,则无法验证加密签名并不表示(使用)软件出现错误,因此缺少错误消息对于这个功能。使用它的功能和设置是否实际上是我在这里要问的。 【参考方案1】:

以下打印失败,我不明白为什么:

你很接近。查看wiki页面有一些问题。首先,这是未定义的行为(已在 wiki 上修复):

auto signature_length = signer.SignMessage(
    prng, (const byte*)message.data(),
    message.size(), (byte*)signature.data());

要获得非常量指针,您需要它(但这不是问题的原因):

auto signature_length = signer.SignMessage(
    prng, (const byte*)&message[0],
    message.size(), (byte*)&signature[0]);

其次,当您调用Initialize 两次时,您会破坏旧配置。 “重击”意味着您生成新参数。实际上,您覆盖了另一个私钥:

private_key.Initialize( prng, ASN1::secp160r1() );
...
signer.AccessKey().Initialize(prng, ASN1::secp160r1());

这不是很明显,但是采用prngInitialize 会生成一个新密钥。你想要一个Initialize接受prng

private_key.Initialize( prng, ASN1::secp160r1() );
...
signer.AccessKey().Initialize(private_key);

第三,页面不清楚如何在Signers/Verifiers和PublicKey/PrivateKey之间移动。为了便于说明,这里有一些其他的方法:

cryptopp $ cat test.cxx
#include "eccrypto.h"
#include "oids.h"
#include "osrng.h"
#include <string>
#include <iostream>

int main()

    using namespace CryptoPP;
    AutoSeededRandomPool prng;

    ECDSA<ECP, SHA256>::Signer signer;
    ECDSA<ECP, SHA256>::Verifier verifier;

    signer.AccessKey().Initialize(prng, ASN1::secp160r1());
    signer.AccessKey().MakePublicKey(verifier.AccessKey());

    std::string signature(signer.MaxSignatureLength(), 0);
    std::string message = "asdf";

    auto signature_length = signer.SignMessage(
        prng, (const byte*)&message[0],
        message.size(), (byte*)&signature[0]);
    signature.resize(signature_length);

    bool verified = verifier.VerifyMessage(
        (const byte*)&message[0], message.size(),
        (const byte*)&signature[0], signature.size());

    if (verified)
        std::cout << "PASS" << std::endl;
    else
        std::cout << "FAIL" << std::endl;

    return 0;

我在 Crypto++ 目录下工作,所以包含和命令行有点不同:

cryptopp$ g++ -I . test.cxx ./libcryptopp.a -o test.exe
cryptopp$ ./test.exe
PASS

如果您想同时使用 Signers/Verifiers 和 PublicKey/PrivateKey,请尝试以下操作:

cryptopp$ cat test.cxx
#include "eccrypto.h"
#include "oids.h"
#include "osrng.h"
#include <string>
#include <iostream>

int main()

    using namespace CryptoPP;
    AutoSeededRandomPool prng;

    ECDSA<ECP, SHA256>::Signer signer;
    ECDSA<ECP, SHA256>::Verifier verifier;

    ECDSA<ECP, SHA256>::PrivateKey& sKey = signer.AccessKey();
    sKey.Initialize(prng, ASN1::secp160r1());
    ECDSA<ECP, SHA256>::PublicKey& pKey = verifier.AccessKey();
    sKey.MakePublicKey(pKey);

    std::string signature(signer.MaxSignatureLength(), 0);
    std::string message = "asdf";

    auto signature_length = signer.SignMessage(
        prng, (const byte*)&message[0],
        message.size(), (byte*)&signature[0]);
    signature.resize(signature_length);

    bool verified = verifier.VerifyMessage(
        (const byte*)&message[0], message.size(),
        (const byte*)&signature[0], signature.size());

    if (verified)
        std::cout << "PASS" << std::endl;
    else
        std::cout << "FAIL" << std::endl;

    return 0;


这看起来有点不寻常:

ECDSA<ECP, SHA256>::Signer signer;
...
signer.AccessKey().Initialize(prng, ASN1::secp160r1());

通常您使用secp160r1, SHA1secp256k1, SHA256。这维护了整个系统的Security Levels。当您使用secp160r1, SHA256 时,由于secp160r1,您将安全级别降低到大约80 位。

【讨论】:

感谢您提供全面的答案和安全提示。看起来 C++17 也允许非常量 data() 访问,因此它不再是未定义的行为:en.cppreference.com/w/cpp/string/basic_string/data

以上是关于无法使用 C 风格的函数调用验证签名的主要内容,如果未能解决你的问题,请参考以下文章

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

签名验证失败。无法匹配“孩子”

验证openssl c ++中的签名,该签名由JAVA DSA签名?

系统重装无法验证数字签名

无法验证 RS256 签名的 JWT

使用 java Event.validateReceivedEvent 验证 webhook 始终无法通过签名验证