Search code examples
c++digital-signaturecrypto++

Sign precomputed hash with ECDSA or DSA


I'm playing around with Crypto++ signers and use the following code, straight out of the wiki:

ECDSA<ECP, SHA256>::PrivateKey privateKey;
const Integer D(string("8964e19c5ae38669db3047f6b460863f5dc6c4510d3427e33545caf9527aafcf").c_str());
privateKey.Initialize(CryptoPP::ASN1::secp256r1(), D);
if (!privateKey.Validate(rng, 3)) {
    cerr << "ECDSA privateKey key validation failed after setting private parameter." << endl;
    return 1;
}

ECDSA<ECP,SHA256>::Signer signer(privateKey);
StringSource ss1(message, true,
    new SignerFilter(rng, signer,
        new HexEncoder(new StringSink(signature), false)
    ) // SignerFilter
); // StringSource
int slen = signature.length() / 2;
// since it's IEEE P1363 format to display r and s:
cout << signature.substr(0, slen) << "\n"
     << signature.substr(slen, slen) << endl;

Now, I'd like to know how I could override the SHA256 there to specify directly the digest value I want to pass to the signature algorithm.

I've digged into the wiki and the doxygen documentation, but had no success doing so. At first I thought maybe the NullHash could help there, but it is really only the zero hash according to the source. I also had some hope with the PK_MessageAccumulator but it does not appear to work as I expected.

So, is there some sort of "identity" function inheriting from the HashTransformation class that I completely missed?
If not, how would you go around building something like that allowing to specify the digest to be signed directly?


Solution

  • H(M)=M might work. Would it be possible to feed such a custom HashTransformation to the ECDSA<ECP,H>::Signer?

    Yes. The program is below. It provides an IdentityHash class that copies input to output. It needs a template parameter to specify the hash size.

    But be careful. The message is formatted after it is hashed. Really what we have is to_sign = MF(H(M)).

    $ cat test.cxx
    #include "cryptlib.h"
    #include "secblock.h"
    #include "eccrypto.h"
    #include "osrng.h"
    #include "oids.h"
    #include "hex.h"
    
    #include <iostream>
    #include <string>
    
    using namespace CryptoPP;
    
    template <unsigned int HASH_SIZE = 32>
    class IdentityHash : public HashTransformation
    {
    public:
        CRYPTOPP_CONSTANT(DIGESTSIZE = HASH_SIZE)
        static const char * StaticAlgorithmName()
        {
            return "IdentityHash";
        }
    
        IdentityHash() : m_digest(HASH_SIZE), m_idx(0) {}
    
        virtual unsigned int DigestSize() const
        {
            return DIGESTSIZE;
        }
    
        virtual void Update(const byte *input, size_t length)
        {
            size_t s = STDMIN(STDMIN<size_t>(DIGESTSIZE, length),
                                             DIGESTSIZE - m_idx);    
            if (s)
                ::memcpy(&m_digest[m_idx], input, s);
            m_idx += s;
        }
    
        virtual void TruncatedFinal(byte *digest, size_t digestSize)
        {
            if (m_idx != DIGESTSIZE)
                throw Exception(Exception::OTHER_ERROR, "Input size must be " + IntToString(DIGESTSIZE));
    
            ThrowIfInvalidTruncatedSize(digestSize);
    
            if (digest)
                ::memcpy(digest, m_digest, digestSize);
    
            m_idx = 0;
        }
    
    private:
        SecByteBlock m_digest;
        size_t m_idx;
    };
    
    int main(int argc, char* argv[])
    {
        AutoSeededRandomPool prng;
    
        ECDSA<ECP, IdentityHash<32> >::PrivateKey privateKey;
        privateKey.Initialize(prng, ASN1::secp256r1());
    
        std::string message;
        message.resize(IdentityHash<32>::DIGESTSIZE);
        ::memset(&message[0], 0xAA, message.size());
    
        ECDSA<ECP, IdentityHash<32> >::Signer signer(privateKey);
        std::string signature;
    
        StringSource ss(message, true,
                            new SignerFilter(prng, signer,
                                new HexEncoder(new StringSink(signature))
                            ) // SignerFilter
                        ); // StringSource
    
        std::cout << "Signature: " << signature << std::endl;
    
        return 0;
    }
    

    I know it compiles and produces output. I have no idea if it is the correct output:

    skylake:cryptopp$ g++ test.cxx ./libcryptopp.a -o test.exe
    skylake:cryptopp$ ./test.exe
    Signature: cafb8fca487c7d5023fbc76ccf96f107f72a07fecca77254e8845a2c8f2ed0ee8b50b
    8ee0702beb7572eaa30c8d250a7b082c79f2f02e58ccfb97d7091755e91
    

    You can test IdentityHash with the following. The class IdentityHash did not change from the previous example. The main function did.

    $ cat test.cxx
    #include "cryptlib.h"
    #include "secblock.h"
    
    #include <iostream>
    #include <string>
    
    using namespace CryptoPP;
    
    template <unsigned int HASH_SIZE = 32>
    class IdentityHash : public HashTransformation
    {
    public:
        CRYPTOPP_CONSTANT(DIGESTSIZE = HASH_SIZE)
        static const char * StaticAlgorithmName()
        {
            return "IdentityHash";
        }
    
        IdentityHash() : m_digest(HASH_SIZE), m_idx(0) {}
    
        virtual unsigned int DigestSize() const
        {
            return DIGESTSIZE;
        }
    
        virtual void Update(const byte *input, size_t length)
        {
            size_t s = STDMIN(STDMIN<size_t>(DIGESTSIZE, length),
                                             DIGESTSIZE - m_idx);    
            if (s)
                ::memcpy(&m_digest[m_idx], input, s);
            m_idx += s;
        }
    
        virtual void TruncatedFinal(byte *digest, size_t digestSize)
        {
            if (m_idx != DIGESTSIZE)
                throw Exception(Exception::OTHER_ERROR, "Input size must be " + IntToString(DIGESTSIZE));
    
            ThrowIfInvalidTruncatedSize(digestSize);
    
            if (digest)
                ::memcpy(digest, m_digest, digestSize);
    
            m_idx = 0;
        }
    
    private:
        SecByteBlock m_digest;
        size_t m_idx;
    };
    
    int main(int argc, char* argv[])
    {
        std::string message;
        message.resize(IdentityHash<32>::DIGESTSIZE);
        ::memset(&message[0], 'A', message.size());
    
        IdentityHash<32> hash;
        hash.Update((const byte*)message.data(), message.size());
    
        std::string digest(32, 0);
        hash.TruncatedFinal((byte*)digest.data(), digest.size());
    
        std::cout << "Message: " << message << std::endl;
        std::cout << " Digest: " << digest << std::endl;
    
        return 0;
    }
    

    It produces:

    skylake:cryptopp$ g++ test.cxx ./libcryptopp.a -o test.exe
    skylake:cryptopp$ ./test.exe
    Message: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
     Digest: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA