Search code examples
c++crypto++ecdsa

Cannot verify signature using C-style function calls


The following prints FAIL and I cannot understand why:

#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;
}

It follows the instructions in crypto++ wiki: https://www.cryptopp.com/wiki/ECDSA#Message_Signing and verifies with a public key derived from the private used to sign the same message. Should I switch to filters?


Solution

  • The following prints FAIL and I cannot understand why:

    You are close. Looking at the wiki page there are a few problems. First, this is undefined behavior (it has been fixed on the wiki):

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

    To get the non-const pointer you need this instead (but it is not the cause of your issue):

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

    Second, when you call Initialize twice you whack the old configuration. "Whack" means you generate new parameters. Effectively you overwrote the other private key:

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

    It is not readily apparent, but the Initialize that takes a prng generates a new key. You want an Initialize that does not take a prng:

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

    Third, the page is not clear how to move between Signers/Verifiers and PublicKey/PrivateKey. Here are some other ways to do it for illustration purposes:

    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;
    }
    

    I'm working from Crypto++ directory so the includes and command line are a little different:

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

    If you want to use both Signers/Verifiers and PublicKey/PrivateKey then try something like:

    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;
    }
    

    This looks a little unusual:

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

    Typically you use {secp160r1, SHA1} or {secp256k1, SHA256}. That maintains the Security Levels of the system as a whole. When you use {secp160r1, SHA256} you are reducing the security level to about 80-bits because of secp160r1.