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...