1

I have a server written in Python, and a C++ client. The Python server has a private RSA key, and the redistributable C++ client has the paired public key. The C++ client sends a string to the Python server, the server generates a signature by encoding this string with its private key, and sends it to the client in ASCII format. Finally, the C++ client verifies this signature to ensure a) the signature comes from the paired key and no other, and b) the signature was made based on this specific string, and no other.

The Python side looks like this:

import rsa
from base64 import b64encode

str = "message"

pub, priv = rsa.newkeys(2048)

keyB64 = rsa.sign(str.encode('utf-8'), privkey, 'SHA-1')    
signature = b64encode(keyB64).decode('ascii')

with open("public_key.txt", "w") as file:
        file.write(pub.save_pkcs1().decode('utf8'))
        file.close()

With the generated public key file looking like this (just an example):

-----BEGIN RSA PUBLIC KEY----- MIGJAoGBALqrXqb17/TiXmGGbvbFwRMV+mbCqPtvnD0zlvIKxpJ4NSBVZ2Lz87SU Ww69uFILy19G6prThJAzHha9pa3fWRKRv5epMXcP6TFZ3er0h0uaxOKxle+OtpnC xyW+QMzkhuDL1gR1OrgVW6jCV6lmVdca63+m2PfTjQj1Vc64OyWBAgMBAAE= -----END RSA PUBLIC KEY-----

On the client side, I read this file and store the characters between the two tags in a string. Then it looks like this:

#include <./Cryptopp/rsa.h>
#include <./Cryptopp/hex.h>
#include <./Cryptopp/pssr.h>

inline bool RsaVerifyString(const std::string &aPublicKeyStrASCII,
                            const std::string &str,
                            const std::string &aSignatureStrASCII)
{

  // decode and load public key (using pipeline)
  CryptoPP::RSA::PublicKey publicKey;
  publicKey.Load(CryptoPP::StringSource(aPublicKeyStrASCII, true).Ref());

  // decode signature
  std::string decodedSignature;
  CryptoPP::StringSource ss(aSignatureStrASCII, true);

  // verify message
  bool result = false;
  CryptoPP::RSASS<CryptoPP::PSSR, CryptoPP::SHA1>::Verifier verifier(publicKey);
  CryptoPP::StringSource ss2(decodedSignature + str, true,
                             new CryptoPP::SignatureVerificationFilter(verifier,
                               new CryptoPP::ArraySink((unsigned char*)&result,
                                                       sizeof(result))));

  return result;
}

//...

std::string message("message");

if(RsaVerifyString(publicKeyASCII, message, signatureASCII))
{
    std::cout << "OK" << std::endl;
}

But it doesn't work: it always returns false, and CryptoPP's architecture is too complicated for me to debug - whereas I'm sure it's actually very simple and just a matter of adapting parameters.

Anyone with experience in these could tell me what I'm doing wrong?

Update

Trying to port it to PyCryptoDome to increase compatibility upon recommendation of a comment:

from Crypto import Random
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5

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


def gen_key_pair():
    random_generator = Random.new().read
    key = RSA.generate(2048, random_generator)
    print(key.exportKey(), key.publickey().exportKey())
    with open(PRIV_PATH, 'wb') as file:
        file.write(key.exportKey())
    with open(PUB_PATH, 'wb') as file:
        file.write(key.publickey().exportKey())
    return key.exportKey(), key.publickey().exportKey()


def sign_message(message):
    key = RSA.importKey(open(PRIV_PATH, 'rb').read())
    h = SHA.new(message)
    signer = PKCS1_v1_5.new(key)
    signature = signer.sign(h)
    return signature


def verify_sign(message, signature):
    key = RSA.importKey(open(PUB_PATH, 'rb').read())
    h = SHA.new(message)
    verifier = PKCS1_v1_5.new(key)
    if verifier.verify(h, signature):
       print("The signature is authentic.")
    else:
       print("The signature is not authentic.")
       
# TEST CRYPTO
gen_key_pair()
message = 'Hello pycrypto!'.encode('utf-8')
signature = sign_message(message).hex()
print('signature='+signature)
verify_sign(message, bytes.fromhex(signature))

On the client side I expect to have the following changes to make (before cleaning the code):

inline bool RsaVerifyString(const std::string &aPublicKeyStrASCII,
                            const std::string &str,
                            const std::string &aSignatureStrASCII)
{

  // decode and load public key (using pipeline)
  CryptoPP::RSA::PublicKey publicKey;
  publicKey.Load(CryptoPP::StringSource(aPublicKeyStrASCII, true).Ref());

  // decode signature
  std::string decodedSignature;
  CryptoPP::StringSource ss(aSignatureStrASCII, true);

  // verify message
  bool result = false;
  CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier(publicKey);
  CryptoPP::StringSource ss2(decodedSignature + str, true,
                             new CryptoPP::SignatureVerificationFilter(verifier,
                               new CryptoPP::ArraySink((unsigned char*)&result,
                                                       sizeof(result))));

  return result;
}

I can't yet compile Crypto++ (it's throwing failures to make functions inline) but I doubt it would work as-is.

user325962
  • 11
  • 2
  • I don't see any base 64 decoding, and you are using PSS as signature scheme during verification. As the idiots that are maintaining the RSA library for Python still show SHA-1 as data hashing algorithm in their sample code, we can be pretty sure that they will also use PKCS#1 v1.5 padding, not PSS. Pretty please, switch to another library. – Maarten Bodewes Nov 24 '22 at 00:00
  • [Here](https://github.com/sybrenstuvel/python-rsa/pull/204) is somebody doing a pull request for PSS, but the project seems dead. They seem to have started working on OAEP (which offers significant security benefits) in 2016 but then .... nothing. Try PyCryptoDome or any other project that is alive. I'd go for at least 3072 bits for the key size by the way, but the hashing algorithm is way more important. Try SHA-256. – Maarten Bodewes Nov 24 '22 at 00:05
  • Thanks for your comment. I'm guessing I should then use Base64Decoder with StringSource()? It's definitely not straightforward it seems but I'll look into that. I'm also willing to switch to PiCrypto if you think it would solve my issues, but I'm not sure I know what the equivalent code is in PiCrypto since I just extracted the relevant bits from a tutorial and the syntax/architecture of both libraries does not seem to match. – user325962 Nov 24 '22 at 00:08
  • Well, why do you want to keep to a broken hash algorithm anyway? SHA-1 has been [SHAttered](https://shattered.io/). You can keep to PKCS#1 v1.5 padding if you want but really, the hash needs to go. Yes, it's a bit of a rewrite, but I'm afraid that otherwise you're stuck with a badly maintained library. – Maarten Bodewes Nov 24 '22 at 00:10
  • Good one :) I'm willing to do anything that meets the requirements, I don't know anything about hash functions to stick to any in particular. I'm looking into PyCryptoDome, see how I can port my code to it. Would I have any change to make to my C++ code aside from updating the hash function tag and the b64 decoder which I'm still trying to get my head around? – user325962 Nov 24 '22 at 00:12
  • Well, I would definitely make sure that I would catch a result code from things like loading keys. You may want to read [this](https://cryptopp.com/wiki/Keys_and_Formats#PEM_Encoded_Keys) about PEM encoded keys. Things like `ecodedSignature + str` (why?), using `true` as a parameter instead of a constant and the horrid cast of `result` should probably be addressed, but those are just somewhat ugly programming which has nothing to do with the actual problem ;) – Maarten Bodewes Nov 24 '22 at 00:19
  • Why? Well, I merely adapted code I've found online but I would assume it's to do with checking the hashes of the client's and the server actually match? Otherwise I don't see where str would come in. I've updated my post with my latest progress... I think I need help, I'm completely lost in all this. – user325962 Nov 24 '22 at 01:34
  • I abandoned Crypto++ because I could not get it to compile on QtCreator. Spent hours on it. So I switched to OpenSSL but now I'm facing how to interface the outputs of my updated script above with the verify function from this code example: https://gist.github.com/irbull/08339ddcd5686f509e9826964b17bb59 Any help would be appreeciated :) – user325962 Nov 24 '22 at 05:12
  • Well, maybe you should write down in pseudo-code what each function does and compare. I see hex vs base 64 and SHA(-1) vs SHA-256. Check if the output of the keys match, especially if the word "RSA" appears in the header as RSA PUBLIC KEY is PKCS#1 and PUBLIC KEY is SPKI format. The signature mechanism is probably PKCS#1 v1.5 in both examples, so that should be OK. – Maarten Bodewes Nov 24 '22 at 14:25
  • I had to switch to OpenSSL, but thanks for the insightful discussion and to have stuck with me. I appreciate it :) – user325962 Nov 24 '22 at 18:41

1 Answers1

0

I abandoned Crypto++ because I couldn't get it to work on QtCreator + Windows, and used OpenSSL instead. It's horribly counterintuitive to code, but there is a lot of support and I got it to work with a member's help in this thread: Verify in OpenSSL C++ a signature generated in PyCryptoDome

Use this if you are flexible on the C++ implementation of the verifying process and you can't get Crypto++ to work, all the code is there.

user325962
  • 11
  • 2