2

I'm trying to verify an ECDSA signature for a hash using a public key. I've written a small Go program that successfully does this but I've been unable to port it to C++.

Here is my input data:

  • Public key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==
  • Hash: 562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469
  • Signature: 304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7

Here is my working go program:

package main

import (
    "crypto/ecdsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/hex"
)

func main() {
    key_, _ := base64.StdEncoding.DecodeString("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==")
    key, _ := x509.ParsePKIXPublicKey(pkey_)
    msg, _ := hex.DecodeString("562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469")
    sig, _ := hex.DecodeString("304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7")

    valid := ecdsa.VerifyASN1(key.(*ecdsa.PublicKey), msg[:], sig)

    if !valid {
      panic("key invalid")
    }
}

And here is my broken C++ program that fails at the very last assertion. I'd like to know why that is:

#include <cassert>
#include <cstdlib>
#include <string>
#include <stdexcept>

#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

std::string decode_hex_str(std::string const &hex) {
  std::string str;
  for (std::size_t i { 0 }; i < hex.size(); i += 2) {
    auto byte { hex.substr(i,2) };
    str.push_back(std::strtol(byte.c_str(), nullptr, 16));
  }

  return str;
}

int main() {
  std::string key_ {
R"(
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==
-----END PUBLIC KEY-----
)"
  };

  std::string msg { decode_hex_str("562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469") };
  std::string sig { decode_hex_str("304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7") };

  auto bio { BIO_new_mem_buf(key_.data(), key_.length()) };
  assert(bio);

  auto key { PEM_read_bio_EC_PUBKEY(bio, nullptr, nullptr, nullptr) };

  EVP_MD_CTX *mdctx { nullptr };
  EVP_PKEY *pkey { nullptr };

  int status;
  bool verified;

  mdctx = EVP_MD_CTX_new();
  assert(mdctx);

  pkey = EVP_PKEY_new();
  assert(pkey);

  assert(EVP_PKEY_assign_EC_KEY(pkey, key) == 1);

  assert(EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) == 1);

  assert(EVP_DigestVerifyUpdate(mdctx, msg.data(), msg.length()) == 1);

  status = EVP_DigestVerifyFinal(mdctx, reinterpret_cast<unsigned char const *>(sig.data()), sig.length());

  switch (status) {
    case 0:
      verified = false;
      break;
    case 1:
      verified = true;
      break;
    default:
      assert(false);
  }

  EVP_MD_CTX_free(mdctx);
  EVP_PKEY_free(pkey);

  assert(verified);
}
Peter
  • 2,919
  • 1
  • 16
  • 35
  • 2
    There's no problem statement in your question—I mean "I have written a program which does not do what I wanted it to" [is not one](https://stackoverflow.com/help/on-topic). Please work on improving your question. – kostix Feb 03 '22 at 16:02
  • 2
    Unlike `VerifyASN1()`, `EVP_DigestVerifyUpdate()` expects the data and not the hash of the data. If you use the data instead of the data hash in the C/C++ code, verification is successful. – Topaco Feb 03 '22 at 17:00
  • @Topaco Ah that makes sense, but what if I don't have access to the data itself? – Peter Feb 04 '22 at 07:41
  • Since a hash is not reversible and therefore the data cannot be reconstructed from the hash, you need a function on the C/C++ side that also expects the hash and not the data, see e.g. [here](https://security.stackexchange.com/a/239376). – Topaco Feb 04 '22 at 08:42

1 Answers1

2

EVP_DigestVerify{Init/Update/Final} do two things:

  • They hash the input data
  • Then they verify the computed hash against the signature

ecdsa.VerifyASN1 on the other hand does only the second step from the above, already taking in the hash as input.

The OpenSSL function that does that is ECDSA_verify.

So you can do something like:

BIO* bio = BIO_new_mem_buf(key_.data(), key_.length());

EC_KEY* key = PEM_read_bio_EC_PUBKEY(bio, nullptr, nullptr, nullptr);

int verified = ECDSA_verify(
    0,
    (unsigned char const*)msghash.data(), msghash.length(),
    (unsigned char const*)sig.data(), sig.length(),
    key
);

std::cout << verified << std::endl;

Should print 1.


Note that the variable naming in your code is confusing - the variable msg actually contains the message digest. So msghash or digest would be a better name for it. It is important to be precise in variable naming when doing crypto.

rustyx
  • 80,671
  • 25
  • 200
  • 267