Saurabh Palatkar
Saurabh Palatkar

Reputation: 3384

Unable to decrypt a string in C# which is encrypted with RSA public key in Node.js

I have created xml key with c#:

RSACryptoServiceProvider RSA = new RSACryptoServiceProvider (2048);
string str = RSA.ToXmlString (true);
System.IO.File.WriteAllText (@"c:\path\to\keypair.xml", str);

Then using this online tool, I converted XML key to pem private key. Then using open SSL, I extracted the public key from the private key:

openssl rsa -in ./private.pem -pubout -out public.pem 

Using the public.pem I am encrypting the secret string in Node:

const encryptWithRsa = (secretStringToEncrypt) => {
  const publicKeyPath = path.join(appRoot, `${process.env.PUBLIC_KEY_PATH}`);
  const publicKeyString = fs.readFileSync(publicKeyPath, 'utf8');
  const buffer = Buffer.from(secretStringToEncrypt, 'utf8');
  const encrypted = crypto.publicEncrypt({
    key: publicKeyString,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256",
  }, buffer);
  const encryptedString = encrypted.toString('base64');
  console.log("encryptWithRsa -> encryptedString", encryptedString);
  return encryptedString;
};

Then I am trying to decrypt the string in c#:

public string RSADecrypt (string encryptedStringFromNode) {
            string keyPair = System.IO.File.ReadAllText (@"c:\path\to\keypair.xml");
            using var csp = new RSACryptoServiceProvider ();
            ImportKey (csp, keyPair);
            var decrypted = csp.Decrypt (Convert.FromBase64String (encryptedStringFromNode), true);
            return Encoding.UTF8.GetString (
                decrypted
            );
        }

        public void ImportKey (RSA rsa, string xmlKey) {
            var parameters = new RSAParameters ();

            var xmlDoc = new XmlDocument ();
            xmlDoc.LoadXml (xmlKey);

            if (xmlDoc.DocumentElement.Name.Equals ("RSAKeyValue")) {
                foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes) {
                    var value = ToByteArray (node.InnerText);
                    switch (node.Name) {
                        case nameof (parameters.Modulus):
                            parameters.Modulus = value;
                            break;
                        case nameof (parameters.Exponent):
                            parameters.Exponent = value;
                            break;
                        case nameof (parameters.P):
                            parameters.P = value;
                            break;
                        case nameof (parameters.Q):
                            parameters.Q = value;
                            break;
                        case nameof (parameters.DP):
                            parameters.DP = value;
                            break;
                        case nameof (parameters.DQ):
                            parameters.DQ = value;
                            break;
                        case nameof (parameters.InverseQ):
                            parameters.InverseQ = value;
                            break;
                        case nameof (parameters.D):
                            parameters.D = value;
                            break;
                    }
                }
            } else {
                throw new Exception ("");
            }
            rsa.ImportParameters (parameters);
        }

        public byte[] ToByteArray (string input) {
            return string.IsNullOrEmpty (input) ? null : Convert.FromBase64String (input);
        }

But when I try to decrypt the string which is encrypted by Node.js, I get below error:

    Exception has occurred: CLR/Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException
    An exception of type 'Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException' occurred in 
System.Security.Cryptography.Csp.dll but was not handled in user code: 'The parameter is incorrect.'

However, if I try to encrypt with the same public key on this online tool and then decrypt with my C# code, it works and I get the original unencrypted string.

Is there any issue with the Padding I am using for the RSA encryption in Node.js?

Upvotes: 0

Views: 2030

Answers (1)

Michael Fehr
Michael Fehr

Reputation: 6414

First - sorry for being too lazy to analyze your code to find out what is the real reason for failure. Most times it is the padding that does not match between platforms and it is always a good programming technique to define parameters even if they are called "standard" or "default" because they may change in the future.

In my example I'm using the PKCS1 padding for RSA encryption that is available on both platforms.

Second: I wanted to use online compiler for demonstration, so I'm using fixed / stringed public and private RSA keys. Please note that I'm using an *UNSECURE RSA keypair with 512 bit key length (just to shorten the key strings) and yes - the keys are sample keys.

Third: both solutions are for educational purpose only because they do not have any exception handling.

Fourth: both solution provide a "full round" means both do an encryption and a decryption, the C#-program does a second decryption with an encrypted string coming from the Node.JS-Solution.

This is the output of the Node.JS-program (run it here: https://repl.it/@javacrypto/RSAEncryptionNodeJstoC):

enc: QPCE4WtNPLFGjWvwpN/JTITdr2k9IhGrsohuW0yPue4dQ2Cv63i+LlohmjsSUcnaiB/zbeItGDIx3s11ayBIUA==
dec: The quick brown fox jumps over the lazy dog

If you run the C#-version you will get this output (run it here: https://repl.it/@javacrypto/RSADecryptionCfromNodeJs-1)

encryptedData: jnAyBvjvjG/NXc/HUmfrTks3SWgeYz3HEksPvk9D9eH/DF0PK6nZu36wTAeZ/fHr3v8dWYwXtT8nXZgvokJJYg==
decryptedData: The quick brown fox jumps over the lazy dog
original Data: VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==
*** enc string from NodeJs ***
decryptedData: The quick brown fox jumps over the lazy dog

Node.JS code:

const crypto = require('crypto')

// SECURITY WARNING: these sample keys are UNSECURE 512 bit long RSA keys
const publicKey = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJn4LYaoLyuT+pD9NKI8wOvPrRjMIxGn
HbqIxUrpGsyj162fEOV4836oCZg0N8HFnt4Vivdjt8/7ZgLjeOygNGUCAwEAAQ==
-----END PUBLIC KEY-----`;

const privateKey = `-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAmfgthqgvK5P6kP00
ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN4
7KA0ZQIDAQABAkEAmCpGy/rxS08e5iXn26LRQvvm5UfyLKMNZWmAGk2QF8cRGFB7
dds/SI9wGTC9xyOoF4N2kWzYdLx+dYbR9lqwbQIhAPFQkWqNIWZiL+YFRhS6jg/+
5+k0CpXpAPelNUFc4pijAiEAo1bchWA5ddDd59FED37QcrakoTXNoxRspFtsLDKE
p1cCIQC6AXRVQTvBg2WQU/hU+geO5Nk1tFuEefm46atHGqW/KQIgXoGggC9Hr8pU
yo9DIPAP7X+Ny5TU0Vm87m/TK9Ni+2sCIGKjhxD/HYAr8+UPJ4aUIHayn3ZZo5DQ
BcShe496ZtPS
-----END PRIVATE KEY-----`;

function encrypt(toEncrypt, relativeOrAbsolutePathToPublicKey) {
  //const absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey)
  //const publicKey = fs.readFileSync(absolutePath, 'utf8')
  const buffer = Buffer.from(toEncrypt, 'utf8')
  var constants = require("constants");
  const encrypted = crypto.publicEncrypt(
  {"key":publicKey, padding:constants.RSA_PKCS1_PADDING}, buffer)
  return encrypted.toString('base64')
}

function decrypt(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
  //const absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey)
  //const privateKey = fs.readFileSync(absolutePath, 'utf8')
  const buffer = Buffer.from(toDecrypt, 'base64')
  var constants = require("constants");
  const decrypted = crypto.privateDecrypt(
    {
      key: privateKey.toString(),
      passphrase: '',
      padding:constants.RSA_PKCS1_PADDING,
    },
    buffer,
  )
  return decrypted.toString('utf8')
}

const enc = encrypt('The quick brown fox jumps over the lazy dog')
console.log('enc:', enc)
const dec = decrypt(enc)
console.log('dec:', dec)

C# code:

using System;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Generic;

class RSACSPSample {
    static void Main() {

        try {
            ASCIIEncoding ByteConverter = new ASCIIEncoding();
            string dataString = "The quick brown fox jumps over the lazy dog";

            // Create byte arrays to hold original, encrypted, and decrypted data.
            byte[] originalData = ByteConverter.GetBytes(dataString);
            byte[] encryptedData;
            byte[] decryptedData;

      // SECURITY WARNING: these sample keys are UNSECURE 512 bit long RSA keys
            // get private and public key
            var publicKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            var privateKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent><P>8VCRao0hZmIv5gVGFLqOD/7n6TQKlekA96U1QVzimKM=</P><Q>o1bchWA5ddDd59FED37QcrakoTXNoxRspFtsLDKEp1c=</Q><DP>ugF0VUE7wYNlkFP4VPoHjuTZNbRbhHn5uOmrRxqlvyk=</DP><DQ>XoGggC9Hr8pUyo9DIPAP7X+Ny5TU0Vm87m/TK9Ni+2s=</DQ><InverseQ>YqOHEP8dgCvz5Q8nhpQgdrKfdlmjkNAFxKF7j3pm09I=</InverseQ><D>mCpGy/rxS08e5iXn26LRQvvm5UfyLKMNZWmAGk2QF8cRGFB7dds/SI9wGTC9xyOoF4N2kWzYdLx+dYbR9lqwbQ==</D></RSAKeyValue>";

            RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider(512); // SECURITY WARNING: 512 is UNSECURE
            RSAalg.PersistKeyInCsp = false;
            // encrypt with xml-public key
      RSAalg.FromXmlString(publicKey);
            encryptedData = RSAalg.Encrypt(originalData, false);
            string encryptedDataBase64 = Convert.ToBase64String(encryptedData);
            Console.WriteLine("encryptedData: " + encryptedDataBase64);

            // decrypt with xml-private key
            RSAalg.FromXmlString(privateKey);
            decryptedData = RSAalg.Decrypt(encryptedData, false);
            Console.WriteLine("decryptedData: " + ByteArrayToString(decryptedData));
            Console.WriteLine("original Data: " + Convert.ToBase64String(originalData));

      Console.WriteLine("*** enc string from NodeJs ***");

      string b64string = "QPCE4WtNPLFGjWvwpN/JTITdr2k9IhGrsohuW0yPue4dQ2Cv63i+LlohmjsSUcnaiB/zbeItGDIx3s11ayBIUA==";
      encryptedData = Convert.FromBase64String(b64string);
      decryptedData = RSAalg.Decrypt(encryptedData, false);
            Console.WriteLine("decryptedData: " + ByteArrayToString(decryptedData));
        }
        catch(ArgumentNullException) {
            Console.WriteLine("error in data en-/decryption");
        }
    }
    
  private static string ByteArrayToString(byte[] arr)
  {
    System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
    return enc.GetString(arr);
  }
}

Upvotes: 4

Related Questions