Modern browsers have real crypto built in the class SubtleCrypto, to quote the MDN page:

The SubtleCrypto interface of the Web Crypto API provides a number of low-level cryptographic functions. Access to the features of SubtleCrypto is obtained through the subtle property of the Crypto object you get from Window.crypto.



An example of how to do public key encryption in Javascript using SubtleCrypto:

A Practical Guide to the Web Cryptography API

Mozilla Documentation for SubtleCrypto and examples on mdn github. There are links in the reference documentation to running examples on the mdn github.

The trick in using this is handling encrypted payloads outside of JS with the same parameters. The use case I'm interested in is encryption using a public key in JS and decryption with the private key in Elixir. On the JS side, the parameters are:

return await window.crypto.subtle.importKey(
      "spki",
      binaryDer,
      {
        name: "RSA-OAEP",
        hash: "SHA-256"
      },
      true,
      ["encrypt"]
    );

The trouble is that RSA-OAEP with SHA-256 support on the openssl side is not available in recent older versions of openssl and while I've been able to use the openssl command to do this successfully at one point with some version, I gave up on this approach to testing the process due to poor documentation.

However, doing this in Elixir with a recent openssl library and the erlang public_key module is not too difficult and looks like this:

Base.decode64!(input, padding: false )
|> :public_key.decrypt_private( 
 key,
 rsa_padding: :rsa_pkcs1_oaep_padding,
 rsa_oaep_md: :sha256 )