1

I want to use the same public-private key to do some encryption/decryption at the code level and want to send encrypted data to the backend along with my public key which I am appending inside of my JWT AUTH Token. So pls help me to encrypt / decrypt with that approach if possible, as I can't change this code due to reusability

      const keyDetails = await window.crypto.subtle.generateKey(
          {
              name: 'RSASSA-PKCS1-v1_5',
              modulusLength: 2048, 
              publicExponent: new Uint8Array([1, 0, 1]),
              hash: { name: 'SHA-256' }, 
          },
          true, 
          ['verify', 'sign'] 
      );
      

I tried that way but got an error.

Also, I want to use my exported public and private keys which I am doing with that approach

const publicKey: any = await window.crypto.subtle.exportKey('jwk', keyDetails.publicKey);
const privateKey: any = await window.crypto.subtle.exportKey('jwk', keyDetails.privateKey);
      const enc = new TextEncoder();
      const encodedText = enc.encode("testing 1234");
    
      const encryptedText = await window.crypto.subtle.encrypt({
        name: "RSASSA-PKCS1-v1_5"
      },
      publicKey,
      encodedText
    )
    console.log(encryptedText);
    const decryptedText = await window.crypto.subtle.decrypt({
        name: "RSASSA-PKCS1-v1_5"
      },
      privateKey,
      encryptedText
    )
TypeError: Failed to execute 'encrypt' on 'SubtleCrypto': parameter 2 is not of type 'CryptoKey'.
nks
  • 623
  • 1
  • 8
  • 19

2 Answers2

1

RSASSA-PKCS1-v1_5 is a padding that is applied during signing/verification. It cannot be used for encryption/decryption. The padding for encryption/decryption is RSAES-PKCS1-v1_5, but this is not supported by WebCrypto API. WebCrypto only supports RSAES-OAEP for encryption/decryption. See RFC8017 and WebCrypto API for more details.
Also, the exported JWK keys must first be adapted for encryption/decryption. Then the keys must be imported before they can be used in encryption/decryption.

The following example demonstrates this: First, a key pair for signing/verifying with RSASSA-PKCS1-v1_5 is generated. Both keys are exported as JWK. Then the key_ops and alg parameters are adjusted. Afterwards, the modified keys are re-imported and used for encryption/decryption with RSAES-OAEP:

(async () => {

// Generate
const keyDetails = await window.crypto.subtle.generateKey(
    {
        name: 'RSASSA-PKCS1-v1_5',
        modulusLength: 2048, 
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: { name: 'SHA-256' }, 
    },
    true, 
    ['verify', 'sign'] 
);
console.log(keyDetails)

// Export
const publicKey = await window.crypto.subtle.exportKey('jwk', keyDetails.publicKey);
const privateKey = await window.crypto.subtle.exportKey('jwk', keyDetails.privateKey);
console.log(publicKey)
console.log(privateKey)

// Adapt parameters and import
publicKey.key_ops = ['encrypt'];
privateKey.key_ops = ['decrypt'];
publicKey.alg = 'RSA-OAEP-256';
privateKey.alg = 'RSA-OAEP-256';
const publicKeyReloaded = await window.crypto.subtle.importKey("jwk", publicKey, {name: "RSA-OAEP", hash: {name: "SHA-256"}}, true, ["encrypt"]);    
const privateKeyReloaded = await window.crypto.subtle.importKey("jwk", privateKey,{name: "RSA-OAEP", hash: {name: "SHA-256"}}, true, ["decrypt"]);    
console.log(publicKeyReloaded)
console.log(privateKeyReloaded)

// Encrypt/Decrypt
const enc = new TextEncoder();
const encodedText = enc.encode("testing 1234");
const encryptedText = await window.crypto.subtle.encrypt({name: "RSA-OAEP"}, publicKeyReloaded, encodedText)
console.log(ab2b64(encryptedText));
const dec = new TextDecoder();
const decryptedText = await window.crypto.subtle.decrypt({name: "RSA-OAEP"}, privateKeyReloaded, encryptedText)
console.log(dec.decode(decryptedText));

// Helper
function ab2b64(arrayBuffer) {
    return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}

})();

Note that in general the same key pair should actually be used for either signing/verifying or encrypting/decrypting, not for both together, see here.
WebCrypto API provides some protection against this misuse by binding the purpose of the key to it (however, this protection can be circumvented all too easily, as shown above).

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thanks @topaco , For sharing your detailed solution with a valuable article. Now I am having a better understanding. – nks Jan 30 '23 at 13:18
  • Hi @topaco, as per the above approach. Suppose we encrypt string value from the backend side (Java API) and decrypt from the front end side. is there any change we need to make in the above approach? Actually, I tried the same approach but it's not working for me if we receive ciphertext from the backend side same code not working. – nks Apr 21 '23 at 10:14
  • @nks - If both sides use OAEP, the OAEP parameters match, and the encrypting (public) and decrypting (private) keys are related, I see no reason why it shouldn't work. If you have problems, I recommend you to post a new question on SO with the Java code, the JavaScript code and non-productive sample data (plaintext, key pair, ciphertext), for making the problem more clear. – Topaco Apr 21 '23 at 10:34
0

Well, the answer is already provided by @topaco. now I Just want to add one more approch here. If someone wants to Encrypt and Decrypt sensitive data with help of [JSON Web Encryption - Ciphertext] JOSE npm lib. with that public/ private key which is generated for sign/ verify only!

const jose = require('jose'); // npm i jose
async encryptDecryptLogic(data: string): Promise<any>{

    const keyDetails = await window.crypto.subtle.generateKey(
          {
              name: 'RSASSA-PKCS1-v1_5',
              modulusLength: 2048, 
              publicExponent: new Uint8Array([1, 0, 1]),
              hash: { name: 'SHA-256' }, 
          },
          true, 
          ['verify', 'sign'] 
      );

       // updating operation from sign-varify to encrypt-decrypt.
       // As that private/ public key is generated for sign and verification purposes only but here we extended its purpose. So we need to update a few properties to do encryption/decryption

        publicKey.key_ops = ['encrypt'];
        privateKey.key_ops = ['decrypt'];

        // updating algo from sign-varify[RS256] to encrypt-decrypt[RSA-OAEP]
        // Defines the algorithm used to encrypt the Content Encryption Key (CEK). This MUST be set to “RSA-OAEP”.

        publicKey.alg = 'RSA-OAEP';
        privateKey.alg = 'RSA-OAEP';

 const encodedText =  await this.jose.jwe.encrypt(publicKey, "lets encrypt me!!")
        console.log('encodedText', encodedText);

 const decodedText =  await this.jose.jwe.decrypt(privateKey, encodedText)
        console.log('decodedText', decodedText);
}
nks
  • 623
  • 1
  • 8
  • 19