0

I am trying (in vain) to verify, in a C++ program, a signature that has been generated in a Python program. I've got a hunch it comes from the format of the signature, because I need it to be an ASCII string, but I have tried every combination of base64 encoding/decoding + str() etc. that I could think of, the verification always fails.

Here is the Python program that generates the signature:

from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
import base64

RSA_BITS = 2048

PRIV_PATH = 'priv.pem'
PUB_PATH= 'pub.txt' 

def gen_key_pair():
    random_generator = Random.new().read
    key = RSA.generate(RSA_BITS, random_generator)
    
    priv = key.exportKey()
    pub = str(key.publickey().exportKey())
    
    with open(PRIV_PATH, 'wb') as file:
        file.write(priv)
    with open(PUB_PATH, 'w') as file:
        file.write(pub)
    
    print("Public key: "+pub+'\n')

def sign_message(message):
    key = RSA.importKey(open(PRIV_PATH, 'rb').read())
    signer = PKCS1_v1_5.new(key)
    h = SHA256.new(message.encode('ascii'))
    
    print('Signature: '+base64.b64encode(signer.sign(h)).decode('ascii'))

gen_key_pair()
sign_message('hello')

And here is the C++ side:

#include <iostream>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <assert.h>
#include <QCoreApplication>

//Copied and pasted from pycryptodome, works because have successfully matched it to a private key in this very program
//(the program returned Authentic by signing with the private key returned by pycryptodome)
std::string PUBLIC_KEY ="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dwgfS3AciOd1yZ2U1HY\n0/sneN6ciARRzKtgHz7EOymoFANuF8MpYWNx8l+SqUfr/Ii2bAOC+94L3hD4Dqw3\nfwDH8vDOjOvCEzgrMdAXZ+Nv8cwRmVlYW8qu5OqXaN7Dv1Scf9vBtCSO59WJ2V4l\nk3VnpjrBqpjJAfW+JNIqsWXBLErOkTtcljBxBADXFVQIBCyTz3QbN+UP7g0eoBQI\n8x8tzmHg7L5vM36Iepn3EIdBBWD942CfqeA0emcFixBtAD7wsg96f9e2Z1tBLskN\nJHRNLgAX50OSuZzMebC9aWV0Z2VhyfCiZIfugODe6X8CXNNTzMqa10eGtXx65kcn\nvwIDAQAB\n-----END PUBLIC KEY-----\n\0";

//I'm not sure it's the right format, but it's copied and pasted from the output of base64.b64encode(signer.sign(h)) where h is h = SHA256.new(MESSAGE.encode('utf-8'))
std::string SIGNATURE = "a+lpWth2RQG21cODvF0SNtpHuQZKPzCB1geo67EywOPtR2bYVkAcQ99+a8thmzfosXgRVQWjCMDnGJmSwnQUywhMxahZri0WRp/NhEEBrbXV1BxGZcL6uhfGJ4aAds9vWO+fIrE86rUQI9YZlU3griq1DRrzgsRWMviMwflqTskY1F85FpmcCLD1UCMJ9pYHYtRCsHBtNX4PnqR5dzfhkSSmVruOXyV0h4foK/m4+Ad2Xkz8VYZxVjq/64k2vqSuPPBhv3Saizvczq4/wP5IKYUjei/UcMau2g0TUzMXzPOXrVaKsXVDGjUlil6uwYGQ3zQFUIL95rpRYCKsAf8AGQ==";

//Same message as used when hashing in pycryptodome
std::string MESSAGE = "hello";

RSA* createPublicRSA(std::string key) {
  RSA *rsa = NULL;
  BIO *keybio;
  const char* c_string = key.c_str();
  keybio = BIO_new_mem_buf((void*)c_string, -1);
  if (keybio==NULL) {
      return 0;
  }
  rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa,NULL, NULL);
  return rsa;
}

bool RSAVerifySignature( RSA* rsa,
                         unsigned char* MsgHash,
                         size_t MsgHashLen,
                         const char* Msg,
                         size_t MsgLen,
                         bool* Authentic) {
  *Authentic = false;
  EVP_PKEY* pubKey  = EVP_PKEY_new();
  EVP_PKEY_assign_RSA(pubKey, rsa);
  EVP_MD_CTX* m_RSAVerifyCtx = EVP_MD_CTX_create();

  if (EVP_DigestVerifyInit(m_RSAVerifyCtx,NULL, EVP_sha256(),NULL,pubKey)<=0) {
    return false;
  }
  if (EVP_DigestVerifyUpdate(m_RSAVerifyCtx, Msg, MsgLen) <= 0) {
    return false;
  }
  int AuthStatus = EVP_DigestVerifyFinal(m_RSAVerifyCtx, MsgHash, MsgHashLen);
  if (AuthStatus==1) {
    *Authentic = true;
    EVP_MD_CTX_free(m_RSAVerifyCtx);
    return true;
  } else if(AuthStatus==0){
    *Authentic = false;
    EVP_MD_CTX_free(m_RSAVerifyCtx);
    return true;
  } else{
    *Authentic = false;
    EVP_MD_CTX_free(m_RSAVerifyCtx);
    return false;
  }
}

size_t calcDecodeLength(const char* b64input) {
  size_t len = strlen(b64input), padding = 0;

  if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
    padding = 2;
  else if (b64input[len-1] == '=') //last char is =
    padding = 1;
  return (len*3)/4 - padding;
}

void Base64Decode(const char* b64message, unsigned char** buffer, size_t* length) {
  BIO *bio, *b64;

  int decodeLen = calcDecodeLength(b64message);
  *buffer = (unsigned char*)malloc(decodeLen + 1);
  (*buffer)[decodeLen] = '\0';

  bio = BIO_new_mem_buf(b64message, -1);
  b64 = BIO_new(BIO_f_base64());
  bio = BIO_push(b64, bio);

  *length = BIO_read(bio, *buffer, strlen(b64message));
  BIO_free_all(bio);
}

bool verifySignature(std::string publicKey, std::string plainText, char* signatureBase64) {
  RSA* publicRSA = createPublicRSA(publicKey);
  unsigned char* encMessage;
  size_t encMessageLength;
  bool authentic;
  Base64Decode(signatureBase64, &encMessage, &encMessageLength);

  bool result = RSAVerifySignature(publicRSA, encMessage, encMessageLength, plainText.c_str(), plainText.length(), &authentic);
  return result & authentic;
}

int main(int argc, char **argv) {
   QCoreApplication app(argc, argv);

  bool authentic = verifySignature(PUBLIC_KEY, MESSAGE, (char*)SIGNATURE.c_str());
  if ( authentic ) {
    std::cout << "Authentic" << std::endl;
  } else {
    std::cout << "Not Authentic" << std::endl;
  }

  return app.exec();
}

My process is: I start the python script, copy the public key (between the single quotes) and paste it in the PUBLIC_KEY std::string, and I do the same for the signature.

I don't know about the signature, but the format of the key must be correct this way because when I also print the private key in ASCII and copy-paste it in the C++ side, and run the signature from the C++ side with that private key (using functions which have been dropped here for simplicity) I get a satisfying authentication.

Bear in mind I know nothing of hash, base64 and the like. I'm a sole new user of the tech who is completely lost in this.

What am I missing?

Note that I use OpenSSL on the C++ side because I have tried and tried and finally given up on Crypto++ that I was never able to compile on QtCreator + Windows (even after compiling successfully the library on MSVC). At the time I also tried PythonRSA and someone on here told me to try with PyCryptoDome. I come before you again with this new pairing because I do not have any more leads to pursue...

user325962
  • 11
  • 2
  • 1
    You must specify in `Base64Decode()` that you do not use linebreaks in and at the end of your Base64 encoded data. To do this, insert before the `BIO_read()` call: `BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL)`. For details see [*BIO_f_base64*](https://www.openssl.org/docs/man1.1.1/man3/BIO_f_base64.html), section *Description*. With this change verification is successful. – Topaco Nov 24 '22 at 09:45
  • Does this answer your question? [base64 decode not working](https://stackoverflow.com/questions/18988570/base64-decode-not-working) – President James K. Polk Nov 24 '22 at 17:17
  • Another, similar [duplicate](https://stackoverflow.com/q/19139926/238704) – President James K. Polk Nov 24 '22 at 17:18
  • Thank you very much @Topaco, it worked! Post an answer here and I'll accept it :) Thanks again for the quick and efficient help! – user325962 Nov 24 '22 at 18:35
  • @PresidentJamesK.Polk: those are useful for reference, but i wouldn't call them duplicates: I had absolutely no idea that the problem had to do with the base 64 decoding not being set up correctly - I wasn't even aware there were several formats. – user325962 Nov 24 '22 at 18:36
  • That's the thing about duplicates, when the core problem is the same but the programmer's view of it is different, the duplicate helps other programmers with *your* point-of-view of the problem find the right solution. – President James K. Polk Nov 24 '22 at 21:18

0 Answers0