12

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.

neubert
  • 15,947
  • 24
  • 120
  • 212
Nico AD
  • 1,657
  • 4
  • 31
  • 51
  • Why are you not using the `sign(buffer, [encoding], [source_encoding])` method on the javascript side (which is meant for signing and should perform hashing inside) ? The way you do it will not produce a correct signature (the padding for signature and encryption is different). It might help to use the `openssl` command line utility to verify key/signature/encryption correctness. Does `publicKeyBuffer` contain public or private key (as it should contain a private key for signature, this variable name is quite misleading)? Good luck! – vlp Oct 02 '17 at 09:34
  • it's the first time I'm doing this so maybe I m doing the wrong way (that's why I asking the question actually :) ) yes the publicKeyBuffer contains a public key. I thought if was possible to sign with public key and verify with private key but according to your answer maybe it s not the case. – Nico AD Oct 02 '17 at 10:34
  • You said "the key is read with errors". Did you mean _without_ errors? Otherwise that seems like the problem right there. – justin.m.chase Oct 11 '17 at 14:40
  • And yeah you should be able to encrypt with a public key and decrypt with a private key and vice versa. – justin.m.chase Oct 11 '17 at 14:41
  • worked on this today, still not able to make it works, I navigated between errors with reason(118), reason(108), reason(159). trying to change the encoding on node size dont help, the best way to get the data on C++ side without modification is to use a buffer (so no encoding option) , when I manage to RSA_private_decrypt returning no errors, I ended with a signature of size 256 while RSA_private_decrypts return a buffer of size 64 (and the first 64 chars dont match) – Nico AD Oct 12 '17 at 10:50
  • 1
    Signing should be done with a private key, and verification with a public key, otherwise what you're doing is pointless. – Sean Burton Oct 12 '17 at 15:03

1 Answers1

2

(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-----
vlp
  • 7,811
  • 2
  • 23
  • 51