Search code examples
javascriptc#reactjstypescriptrsa

Construct RSA Public Key in JS/TS


I have a c# web api that passes a public key to a React app. I need to use that public key to encrypt a payload/message and then send it over back to my api. However, I am having trouble reconstructing the public key in the front end. Let me explain.

The app does not use https but only http. Given that this is the case, I went ahead and generated public and private keys in c#, like so:

using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
    RSAParameters publicKey = rsa.ExportParameters(false);
    RSAParameters privateKey = rsa.ExportParameters(true);
}

The public key yields byte arrays for the Modulus (n) and Exponent (e) parameters, which can be used to reconstruct the public key (I've tested that on the backend). So, I then convert the n and e into base64 strings and send them over to the client.

Now, in my react app, I am using JSEncrypt library to encrypt the payload with the public key from the backend (example here). But it is failing. Here is my js:

const encryptMessage = (messageToEncrypt) => {
if (publicKeyParts?.length === 2) {
  const encrypt = new JSEncrypt();
  // publicKeyParts is a string[] with the exponent stored in index 0
  // and the modulus stored in index 1
  encrypt.setPublicKey(`${publicKeyParts[1]}${publicKeyParts[0]}`);

  let data = {
    payload: messageToEncrypt
  }

  let encryptedPayload = encrypt.encrypt(JSON.stringify(data));
  return encryptedPayload;
} 

}

The encrypted message is false and does not work. I think it has to do with my public key's format. Here is my public key generated from the c# (and converted to base64):

8NHGP6WqPGgHa5Q+15V5z+SI7XAvIgMVjbNuGdmlvHoLY2kutP5u+m1lcJpcj49aVMFnoo0eJ5nBIMXVMw1Qz0EpR3BreHp0X0Yfavf2E1lyIl5oyQFTpuzvMu0U3DebLvRezICn66CmFJxnsuERTM5NQ+vE5/3WUx4uMI8jKLE=

When I compare this to other public keys I notice that my key has characters that I don't see in other working public keys (like the one in the example I linked above).

Is there a conversion that I am missing? I have scoured websites but am not able to see what could be the issue.


Solution

  • I have taken a look at the example you have linked in your question. In the example, they are using PEM RSA keys, both public and private. The public key they are using

    const pub_key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDeoeE0ytVpM2uDidhMFilE13nXHfEIQqfMJ1TUqGotm6HI72h6ZEqf2PhZEcZkyV34u1d822dqoJMs00IlQiNsshdzU2cIrDYhqPNRfkcHQXCzZoep/BAUvUQrDouhSl89BQXzxK45CLdoy2kcvqqXb2U+GK9LmSqxJSWPEZDYZQIDAQAB'
    

    can be written as

    -----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDeoeE0ytVpM2uDidhMFilE
    13nXHfEIQqfMJ1TUqGotm6HI72h6ZEqf2PhZEcZkyV34u1d822dqoJMs00IlQiNss
    hdzU2cIrDYhqPNRfkcHQXCzZoep/BAUvUQrDouhSl89BQXzxK45CLdoy2kcvqqX
    b2U+GK9LmSqxJSWPEZDYZQIDAQAB
    -----END PUBLIC KEY-----
    

    meaning that they are using a public key in X.509 format with modulus dea1e134cad569336b8389d84c162944d779d71df10842a7cc2754d4a86a2d9ba1c8ef687a644a9fd8f85911c664c95df8bb577cdb676aa0932cd3422542236cb21773536708ac3621a8f3517e47074170b36687a9fc1014bd442b0e8ba14a5f3d0505f3c4ae3908b768cb691cbeaa976f653e18af4b992ab125258f1190d865 and public exponent 10001.

    If you take a look at the library JSEncrypt code, you will find that the method setPublicKey is expecting a key in a string format. It doesn't specify a format in the method comments but from the example you have provided and some other online example I have found, this method is expecting the public RSA key in a PEM format. More importantly, in the README they have stated that their library uses PEM format. This is the reason your code isn't working. You are sending to this method a key which is composed of concatenated, base64 encoded, public key parameters, exponent and modulus. Here you have two options:

    1. Create a whole public RSA PEM key in the backend and send it to the frontend.
    2. Generate a public key in your frontend code from your public key parameters.

    If you take a look at the library repo, there is an open issue about creating a public key from its parameters. Here is a copy of the code from the aforementioned issue on how to create a key from its parameters:

    import JSEncrypt from 'jsencrypt';
    import { parseBigInt } from 'jsencrypt/lib/lib/jsbn/jsbn';
    
    const nStr = 'BB6FE79432CC6EA2D8F970675A5A87BFBE1AFF0BE63E879F2AFFB93644D4D2C6D000430DEC66ABF47829E74B8C5108623A1C0EE8BE217B3AD8D36D5EB4FCA1D9';
    const eStr = '010001';
    
    const n = parseBigInt(nStr, 16);
    const e = parseInt(eStr, 16);
    
    const encrypt = new JSEncrypt();
    const key = encrypt.getKey();
    key.parsePropertiesFrom({ n, e });
    

    Finally, be mindful of which padding scheme, OAEP or PKCS#1, you are using when encrypting data. Also, don't forget you use an appropriate key length when creating your keys (you might find this useful).