Search code examples
c#c++windowscryptographycrypto++

RSA signature in c# and verification in C++ with Crypto++


I'm trying to sign some bytes in C# thanks to the class RSACryptoServiceProvider and to verify it in C++ with the Crypto++ library. Despite all my attempts, the validation fail although I'm sure of my key and signature.

In C# I sign as follow :

var message = "hello";
var bytes = System.Text.Encoding.UTF8.GetBytes(message);
byte[] signedHash;
using (RSACryptoServiceProvider rsa = new  RSACryptoServiceProvider())
{
    // Import the key information.
    rsa.ImportParameters(privateKey);
    // Sign the data, using SHA256 as the hashing algorithm 
    signedHash = rsa.SignData(bytes, CryptoConfig.MapNameToOID("SHA256"));
}

My keys are generated as follow :

CspParameters parameters = new CspParameters();
parameters.KeyNumber = (int)KeyNumber.Signature;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024, parameters))
{
    privateKeyInfo = rsa.ExportParameters(true);
    publicKeyInfo = rsa.ExportParameters(false);
}

In C++, I create the public key and try to verify it as follow :

RSA::PublicKey publicKey;
byte signature[128];
signature[0]= 150;
//....fill up to 127 , corresponds to "signedHash" variable from c# code
signature[127]= 89;

string simplemessage = "hello";
string modulus = "0Z8GUI/rxlXanCCjkiP+c9HyvdlOibst2YD5XmZk4F86aLr7LbLtI7FMnr6rcQZa6RXkAykb5MIbasmkOmkLzSjhdTThnaZyuKBOBoybYB5mDecF2VMXfUIryEBFn4i6y58qhy0BnDnIhucdNXX0px10HL3uYzR2KBTC0lSFFmE=";
string exponent = "AQAB";

char modulusCharred[1024];
strncpy_s(modulusCharred, base64ToHex(modulus).c_str(), sizeof(modulusCharred));
modulusCharred[sizeof(modulusCharred) - 1] = 0;

char exponentCharred[1024];
strncpy_s(exponentCharred, base64ToHex(exponent).c_str(), sizeof(exponentCharred));
exponentCharred[sizeof(exponentCharred) - 1] = 0;

Integer n(modulusCharred);
Integer e(exponentCharred);

publicKey.Initialize(n, e);

AutoSeededRandomPool rnd;
if(!publicKey.Validate(rnd, 3))
    throw runtime_error("Rsa public key validation failed"); // no error is thrown 

RSASS<PSS, SHA256>::Verifier verifier(publicKey);
bool result = verifier.VerifyMessage((const byte*)simplemessage.c_str(),simplemessage.length(), signature,128);

if(true == result) {
    cout << "Signature on message verified" << endl;
} else {
    cout << "Message verification failed" << endl; // always fail...
}

Modulus and exponent are copy/past from the xml obtained in c# using rsa.ToXmlString(false). The function base64toHex is given by (found on another SO post):

std::string base64ToHex(std::string base64String)
{
    std::string decodedString, finalString;
    CryptoPP::StringSource river(base64String, true,
        new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decodedString)));

    CryptoPP::StringSource stream(decodedString, true,
        new CryptoPP::HexEncoder(new CryptoPP::StringSink(finalString)));

    finalString.erase(std::remove(finalString.begin(), finalString.end(), '\n'), finalString.end());

    return finalString;
} 

I do not want to use any external files, only bytes (or string) variable. Also I'm not sure of the way I define my verifier : RSASS<PSS, SHA256>::Verifier.

Could you help me with this ?


Solution

  • This answer is for both crypto++ and the windows API.

    After a lot of trials/errors I finally succeed, the problem came from the way I built the crypto++ rsa keys ( Integer type : modulus and exponent).

    I have created a new function GetIntegerFromBase64String that convert directly the modulus and exponent we obtain from the xml output of rsa.ToXmlString to the Integer type to initialize key in cryptopp.

    Full crypto++ Example :

            string signature_64str = "G+PQaArLByTNYF5c5BZo2X3Guf1AplyJyik6NXCJmXnZ7CD5AC/OKq+Iswcv8GboUVsMTvl8G+lCa9Od0DfytnDui7kA/c1qtH7BZzF55yA5Yf9DGOfD1RHOl3OkRvpK/mF+Sf8nJwgxsg51C3pk/oBFjA450q2zq8HfFG2KJcs=";  
            string modulus_64str = "0Z8GUI/rxlXanCCjkiP+c9HyvdlOibst2YD5XmZk4F86aLr7LbLtI7FMnr6rcQZa6RXkAykb5MIbasmkOmkLzSjhdTThnaZyuKBOBoybYB5mDecF2VMXfUIryEBFn4i6y58qhy0BnDnIhucdNXX0px10HL3uYzR2KBTC0lSFFmE=";
            string exponent_64str  = "AQAB";
    
            Integer mod_integer = GetIntegerFromBase64String(modulus_64str);
            Integer pub_integer = GetIntegerFromBase64String(exponent_64str);
            InvertibleRSAFunction param;
            param.SetModulus(mod_integer);
            param.SetPublicExponent(pub_integer);
            RSA::PublicKey pubkey(param);
    
            string decoded_sig = DecodeBase64String(signature_64str);
    
    
            if(!pubkey.Validate(rnd, 3))
                    cout << "Rsa public key validation failed" << endl;
            else
                   cout << " key validation success"<<  endl;
    
    
            RSASS<PKCS1v15, SHA512>::Verifier verif(pubkey);
            bool res = verif.VerifyMessage( reinterpret_cast<const byte*>(message.c_str()), message.length(), reinterpret_cast<const byte*>(decoded_sig.c_str()), decoded_sig.length() );
    
             if( res ) {
                        cout << "Signature on message verified " << endl;
             } else {
                        cout << "Message verification failed " << endl;
             }
    

    with :

    string DecodeBase64String(string encoded )
    {
            string decoded;    
            Base64Decoder decoder;
            decoder.Attach( new StringSink( decoded ) );
            decoder.Put( (byte*)encoded.data(), encoded.size() );
            decoder.MessageEnd();
            return decoded;
    
    }
     Integer GetIntegerFromBase64String(string encoded)
    {
            string decoded = DecodeBase64String(encoded);              
            Integer integer( (byte*)decoded.c_str(),decoded.length());
            return integer;
    }
    

    Additionaly, I have reproduced the verification with the windows API, for this case I do not use the xml key but directly the blob key (encoded in 64 bit) that I obtain from rsa.ExportCspBlob(false)

    Full windows api example :

    In c# I got the CspBlob as follow :

      using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
                {
                    rsa.ImportParameters(privateKey);
                    var cspBlob = rsa.ExportCspBlob(false);
                    var cspBlobBase_64str = Convert.ToBase64String(cspBlob);// <---- HERE
    

    Then in c++, I load the blob and verify the signature as follow :

    #include <windows.h>
    ...
                string ErrorMessage;
                string signature_64str = "G+PQaArLByTNYF5c5BZo2X3Guf1AplyJyik6NXCJmXnZ7CD5AC/OKq+Iswcv8GboUVsMTvl8G+lCa9Od0DfytnDui7kA/c1qtH7BZzF55yA5Yf9DGOfD1RHOl3OkRvpK/mF+Sf8nJwgxsg51C3pk/oBFjA450q2zq8HfFG2KJcs=";
                string public_key_blob_64_bit_encoded = "BgIAAACkAABSU0ExAAQAAAEAAQBhFoVU0sIUKHY0Y+69HHQdp/R1NR3nhsg5nAEthyqfy7qIn0VAyCtCfRdT2QXnDWYeYJuMBk6guHKmneE0deEozQtpOqTJahvC5BspA+QV6VoGcau+nkyxI+2yLfu6aDpf4GRmXvmA2S27iU7ZvfLRc/4jkqMgnNpVxuuPUAaf0Q==";
                string message = "hello";
    
    
                if( RSA_VerifySignature(message,   signature_64str, public_key_blob_64_bit_encoded,  ErrorMessage))
                {
                     cout << "OK : Signature on message verified " << endl;
                }
                else
                {
                     cout << "Message verification failed, Error : " << ErrorMessage << endl;
                }
    

    with :

     bool RSA_VerifySignature(string message, string signature_64BitEncoded, string publickeyBlob_64BitEncoded, string &ErrorMessage)
    {
         const size_t LENGHT_SIGNATURE = 128; // 128 bytes == 1024 RSA Key bits 
         const size_t LENGHT_BLOB_PUBLIC_KEY = 148; // 148 bytes 
    
         bool isSigOk = false;
         HCRYPTHASH hash;
    
         byte  decoded_Blob[LENGHT_BLOB_PUBLIC_KEY] ;
         size_t size_pubkey = Base64Decode(publickeyBlob_64BitEncoded, decoded_Blob, LENGHT_BLOB_PUBLIC_KEY);
    
         byte  decoded_signature[LENGHT_SIGNATURE] ;
         size_t size_signature =Base64Decode(signature_64BitEncoded, decoded_signature, LENGHT_SIGNATURE);
    
         //reverse bytes
         byte reverse_decoded_signature[LENGHT_SIGNATURE];
         for(int i=0;i<sizeof(reverse_decoded_signature);i++)
             reverse_decoded_signature[i] = decoded_signature[LENGHT_SIGNATURE-i-1];
    
    
         HCRYPTPROV cryptProvider;
         // Get a handle to the PROV_RSA_AES (for CALG_SHA_512).
         if (!CryptAcquireContext(&cryptProvider, 0, 0, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){  
            ErrorMessage = "Failure to acquire context";
            goto Exit;
         }
    
         HCRYPTKEY publicKeyc;
         // convert the blob to the public key
         if(!CryptImportKey(cryptProvider, decoded_Blob, LENGHT_BLOB_PUBLIC_KEY, 0, 0, &publicKeyc)){ 
    
            ErrorMessage = "Failure to import key";
            goto Exit;
         }
    
         // create the hash object
         if(!CryptCreateHash(cryptProvider, CALG_SHA_512 , 0, 0, &hash)){
            ErrorMessage = "Failure to creat Hash"  ;
            goto Exit;
         }
    
         //hash the message
         if(!CryptHashData(hash, (byte*) message.c_str(), message.length(), 0)){  
            ErrorMessage = "Failure to Hash Data"  ;
            goto Exit;
         }
    
           isSigOk = CryptVerifySignature(hash, reverse_decoded_signature, sizeof(reverse_decoded_signature), publicKeyc, nullptr, 0);
    
           if(!isSigOk) ErrorMessage = "Invalid Signature"  ;
    
    
         Exit:  
         // After processing, hHash and cryptProvider must be released. 
         if(hash) 
            CryptDestroyHash(hash);
         if(cryptProvider) 
            CryptReleaseContext(cryptProvider,0);
           return isSigOk;
    }
    

    where Base64Decode comes from this SO answer.

    ps: note that I have switched to SHA512 in this answer .