Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RSA sign in node.js and verify in C++

I'm working on a way to secure the message I sent from a node.js server to a C++ application.

From node.js , I created a key pair.

I'm using node-rsa to read the public key on node.js side ( https://github.com/rzcoder/node-rsa )

var rsa =  new nodeRSA(publicKeyBuffer  ,{encryptionScheme :'pkcs1'})

As my message can be long, I calculate a salted sha256 of the message before calling encrypt.

const hash = crypto.createHash('sha256').update(message + config.signSalt).digest('hex')

this part is working fine because I am able to generate the exact same hash on C++ side.

then, I'm calling the encrypt function of node-rsa to generate a buffer

const signature = rsa.encrypt(hash)

I tried various encoding , but as the data is sent through a websocket (+ MsgPack packing) the binary format is a good option

On C++ side , I am first reading the key for a char[]

const char keyStr[] = "-----BEGIN RSA PRIVATE KEY-----\n" ..........

BIO* bio = BIO_new_mem_buf(keyStr, (int)strlen(keyStr)); // -1: assume string is null terminated

m_rsaPrivKey = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);

if (!m_rsaPrivKey)
        LogOutSys("ERROR: Could not load PRIVATE KEY! PEM_write_bio_RSAPrivateKey FAILED: %s\n", ERR_error_string(ERR_get_error(), NULL));

the key is read without errors, after that, I'm calculating the hash from the message, making a std::string from the unsigned char buffer

std::string hash = sha256(msg.c_str());

std::string signatureStr(signature.begin(), signature.end());

char *decrypt;

int decryptLen;

decrypt = new char[RSA_size(m_rsaPrivKey)];

decryptLen = RSA_private_decrypt((int)msg.size() + 1, (unsigned char*)msg.c_str(), (unsigned char*)decrypt, m_rsaPrivKey, RSA_NO_PADDING /* RSA_PKCS1_OAEP_PADDING */ );

if (decryptLen == -1)
{
    char errStr[130];
    ERR_error_string(ERR_get_error(), errStr);
    LogOutSys("Rsa::decrypt - Error decrypting string ssl error %s", errStr);
}

for (int i = 0; i < decryptLen; i++)
{
    decryptData.push_back(decrypt[i]);
}

delete decrypt;

the decrypt failed with the following error

Rsa::decrypt - Error decrypting string ssl error error:0406506C:lib(4):func(101):reason(108)

I tried various encoding and padding mode but always getting an error.

like image 209
Nico AD Avatar asked Oct 02 '17 09:10

Nico AD


1 Answers

(Assuming you want to sign using the private key on the nodejs side and verify using the public key on the openssl side)

To sign string Test with sample private key, use e.g.:

NodeRSA=require('node-rsa');
key = new NodeRSA(null, {signingScheme: 'pkcs1-sha256'});
key.importKey('-----BEGIN PRIVATE KEY-----\nMIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAI2GkqoVj1l59MQh\nI/ZswgdnNG3o5XSmyeGgNdmTgQQ6cWCJcscCS6d3+nWFl3Xe7SxzKxo7pHMHTeJU\nGTZpLzW7fk5Y/ISWIr2Qsswpm8JUVOAUQVU/qwZYPr0ACCDQLGLaVByKFgvKnf5p\npkdroM63AFakn5YlCP+WM4ASuZyvAgMBAAECgYAUsBNIYZZu0fEBqoaDQyqpwmBb\noKvJ/YeNP8ofX/yADbr9DZqFlMRSWqt1+m1FgazRzpQCZa2IUw0DhJ+a4I1R6E30\nw7ZVWdVvWtkA70YGMaqB618fMR6SpTmzVGUjzQqk7Zim+uQVugTXEimC6/7sa7em\nLtVdjXuvOFOCVEeXwQJBAO/bN2q2u8YTBy9q4A34KoeeM8NX8zV3bRVhrB4eVcT0\ngpNNCrvjo2g5qKQs1fmLmjylSBihus0RjjJZTwxsffkCQQCXDRhh83OCtLfDEztO\nObu5BvVFcli76VEdw4EqzJtrddG77B43ggYYyJFxOuJHz+33oM4GtnHEiHkV9sRS\n47nnAkBcu8qPLZsnl4+9m3qIrBv1Vwr4SXa0gznffGXJNz096rLZNH4j6nzw/Ong\nn50S4BB/xf871rucMV9iw/i1+vQxAkEAjDBDKOVhlzVSN2Jp8Df02cxzZnixkfUA\nq7b+8lHjDODUPqztfmbWcbn0Ajq8OBnqqaA8lk5NWDGw74mOu79OkQJARDwRop7c\nfkd39rY/+an50uj2L4UJv1Jb+Yal5c0u37ACRnp+0n6qlcL0pj98UfW9H+oYFqwT\nKXzq4lptzgfQIg==\n-----END PRIVATE KEY-----\n', 'pkcs8');
signature=key.sign('Test', 'hex');

Giving a hex encoded signature:

'45f0c0672c8c07ecfe318b8ffa425c169ed2458ac6f0e4f1ffe0bcebec38cc3e59311858ed443d45f0dc81935ee0c490fa452b2f427d59a2c43fdd69f71f2b46d9e39072cb517b4afba5d5c66b26e14ca8a2900650b923fd77271deba84103d42c1f81619825d4987eeabc05401b0bc35bee08c59843aa94a535d2fb032b681b'

To verify this signature with openssl, use e.g.:

#include <stdlib.h>
#include <stdio.h>

#include <openssl/pem.h>
#include <openssl/evp.h>

int main(void) {

    // Read public key from memory
    EVP_PKEY *pubKey=NULL;
    {
        BIO * pubKeyPemBio = BIO_new_mem_buf("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCNhpKqFY9ZefTEISP2bMIHZzRt\n6OV0psnhoDXZk4EEOnFgiXLHAkund/p1hZd13u0scysaO6RzB03iVBk2aS81u35O\nWPyEliK9kLLMKZvCVFTgFEFVP6sGWD69AAgg0Cxi2lQcihYLyp3+aaZHa6DOtwBW\npJ+WJQj/ljOAErmcrwIDAQAB\n-----END PUBLIC KEY-----", -1);
        if(pubKeyPemBio==NULL) { printf("Error at line %i\n", __LINE__); return 0; }
        pubKey=PEM_read_bio_PUBKEY(pubKeyPemBio, &pubKey, NULL, NULL);
        BIO_free(pubKeyPemBio);
        if(pubKey==NULL) { printf("Error at line %i\n", __LINE__); return 0; }
    }

    // Verify signature
    {
        EVP_MD_CTX *ctx = EVP_MD_CTX_create();
        if(ctx==NULL) { printf("Error at line %i\n", __LINE__); return 0; }

        int ret=EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, pubKey);
        if(ret!=1) { printf("Error at line %i\n", __LINE__); return 0; }

        ret=EVP_DigestVerifyUpdate(ctx, "Test", 4);
        if(ret!=1) { printf("Error at line %i\n", __LINE__); return 0; }

        ret=EVP_DigestVerifyFinal(ctx, "\x45\xf0\xc0\x67\x2c\x8c\x07\xec\xfe\x31\x8b\x8f\xfa\x42\x5c\x16\x9e\xd2\x45\x8a\xc6\xf0\xe4\xf1\xff\xe0\xbc\xeb\xec\x38\xcc\x3e\x59\x31\x18\x58\xed\x44\x3d\x45\xf0\xdc\x81\x93\x5e\xe0\xc4\x90\xfa\x45\x2b\x2f\x42\x7d\x59\xa2\xc4\x3f\xdd\x69\xf7\x1f\x2b\x46\xd9\xe3\x90\x72\xcb\x51\x7b\x4a\xfb\xa5\xd5\xc6\x6b\x26\xe1\x4c\xa8\xa2\x90\x06\x50\xb9\x23\xfd\x77\x27\x1d\xeb\xa8\x41\x03\xd4\x2c\x1f\x81\x61\x98\x25\xd4\x98\x7e\xea\xbc\x05\x40\x1b\x0b\xc3\x5b\xee\x08\xc5\x98\x43\xaa\x94\xa5\x35\xd2\xfb\x03\x2b\x68\x1b", 128);
        if(ret!=1) { printf("Error at line %i\n", __LINE__); return 0; }
        EVP_MD_CTX_destroy(ctx);
    }
    EVP_PKEY_free(pubKey);

    printf("All OK!\n");
    return EXIT_SUCCESS;
}

Some (random) notes:

  • above code is just a working proof-of-concept for RSA sign/verify between nodejs and openssl. Please do not expect it to be a secure implementation with correct error handling, secure key lengths, secure against timing/side-channel attacks etc.

  • signed string/buffer can be arbitrarily long as hashing is performed by node-rsa

  • encoded public/private key can be obtained in nodejs using e.g. key.exportKey('pkcs8-public')/key.exportKey('pkcs8')

  • select encoding that fits you (I used strings and hex-strings as they are clearly readable). You probably want to use buffers

Good luck!

Disclaimer: I am no crypto expert, so please do validate my thoughts.


Used private key:

-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAI2GkqoVj1l59MQh
I/ZswgdnNG3o5XSmyeGgNdmTgQQ6cWCJcscCS6d3+nWFl3Xe7SxzKxo7pHMHTeJU
GTZpLzW7fk5Y/ISWIr2Qsswpm8JUVOAUQVU/qwZYPr0ACCDQLGLaVByKFgvKnf5p
pkdroM63AFakn5YlCP+WM4ASuZyvAgMBAAECgYAUsBNIYZZu0fEBqoaDQyqpwmBb
oKvJ/YeNP8ofX/yADbr9DZqFlMRSWqt1+m1FgazRzpQCZa2IUw0DhJ+a4I1R6E30
w7ZVWdVvWtkA70YGMaqB618fMR6SpTmzVGUjzQqk7Zim+uQVugTXEimC6/7sa7em
LtVdjXuvOFOCVEeXwQJBAO/bN2q2u8YTBy9q4A34KoeeM8NX8zV3bRVhrB4eVcT0
gpNNCrvjo2g5qKQs1fmLmjylSBihus0RjjJZTwxsffkCQQCXDRhh83OCtLfDEztO
Obu5BvVFcli76VEdw4EqzJtrddG77B43ggYYyJFxOuJHz+33oM4GtnHEiHkV9sRS
47nnAkBcu8qPLZsnl4+9m3qIrBv1Vwr4SXa0gznffGXJNz096rLZNH4j6nzw/Ong
n50S4BB/xf871rucMV9iw/i1+vQxAkEAjDBDKOVhlzVSN2Jp8Df02cxzZnixkfUA
q7b+8lHjDODUPqztfmbWcbn0Ajq8OBnqqaA8lk5NWDGw74mOu79OkQJARDwRop7c
fkd39rY/+an50uj2L4UJv1Jb+Yal5c0u37ACRnp+0n6qlcL0pj98UfW9H+oYFqwT
KXzq4lptzgfQIg==
-----END PRIVATE KEY-----

With corresponding public key:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCNhpKqFY9ZefTEISP2bMIHZzRt
6OV0psnhoDXZk4EEOnFgiXLHAkund/p1hZd13u0scysaO6RzB03iVBk2aS81u35O
WPyEliK9kLLMKZvCVFTgFEFVP6sGWD69AAgg0Cxi2lQcihYLyp3+aaZHa6DOtwBW
pJ+WJQj/ljOAErmcrwIDAQAB
-----END PUBLIC KEY-----
like image 182
vlp Avatar answered Oct 12 '22 03:10

vlp