OpenSSL for WebAssembly
v4.0.2WebAssemblyOpenSSL 4.0.2 for browsers, Node.js and edge runtimes, precompiled for wasm32, single-threaded and multi-threaded as @crossbind/port-openssl-wasm.
npm install @crossbind/port-openssl-wasm@betaInstall
crossbind itself arrives with your bundler plugin, or with a new project from npm create crossbind@beta; Bundlers has Vite, Webpack, Rspack and Rollup.
Usage
Each example runs here, in this tab, and prints what the site build checked.
Each example also has a JavaScript only tab: the same task with no C++ file, calling OpenSSL's own headers from @crossbind/port-openssl directly. All 5 work that way.
Read a certificate like openssl x509 -text
What people open a certificate for, from C++: PEM_read_bio_X509 parses it, X509_NAME_print_ex writes the subject and issuer, ASN1_TIME_print_ex the validity, X509V3_EXT_print the alternative names, X509_check_host matches a host name the way a TLS client does and X509_digest takes the SHA-256 fingerprint. WebCrypto has no X.509 parser.
CN=shop.example.com,O=Example Shop,C=US issued by CN=Example Shop Test CA,O=Example Shop,C=US valid 2026-03-01 00:00:00Z to 2026-05-30 00:00:00Z DNS:shop.example.com, DNS:www.shop.example.com, IP Address:192.0.2.10 EC prime256v1 256 bits, self-signed false www.shop.example.com true shop.example.org false 68:2D:81:9E:75:7E:2C:15:C1:92:FB:84:3D:BE:AA:99:B4:54:58:D4:5F:A3:F3:3A:C8:13:1E:9D:03:9C:89:3C
The same OpenSSL calls the C++ makes, on x509.h, x509v3.h, pem.h, bio.h, asn1.h and evp.h as OpenSSL ships them. OpenSSL prints into a BIO, so JavaScript passes a memory BIO and reads the text back with BIO_ctrl_pending and BIO_read, because BIO_get_mem_data is a macro and has no binding. The flag and NID macros such as XN_FLAG_RFC2253 have none either, so their values are written out.
CN=shop.example.com,O=Example Shop,C=US issued by CN=Example Shop Test CA,O=Example Shop,C=US valid 2026-03-01 00:00:00Z to 2026-05-30 00:00:00Z DNS:shop.example.com, DNS:www.shop.example.com, IP Address:192.0.2.10 EC prime256v1 256 bits, self-signed false www.shop.example.com true shop.example.org false 68:2D:81:9E:75:7E:2C:15:C1:92:FB:84:3D:BE:AA:99:B4:54:58:D4:5F:A3:F3:3A:C8:13:1E:9D:03:9C:89:3C
Hash and HMAC
EVP_Q_digest hashes data already in memory with any digest by name, EVP_DigestUpdate takes it in pieces as a file or a download arrives, and EVP_Q_mac makes the HMAC that webhook and API request signatures use. WebCrypto stops at SHA-1 and SHA-2; SHA-3 and BLAKE2 come from OpenSSL here. Every line is the published test vector of its standard.
SHA256 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad SHA3-256 3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532 BLAKE2B-512 ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923 HMAC-SHA256 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 streamed SHA256 cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0
EVP_Q_digest, EVP_Q_mac and the EVP_Digest* calls work as in C. Their data parameters are const void * or const unsigned char *, which take a handle and not a string (and const unsigned char * refuses a cstring handle as a pointer type mismatch), so text goes in through allocBuffer and writeBytes. The digest length comes back through a 4-byte out-parameter, read with readNumberAt.
SHA256 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad SHA3-256 3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532 BLAKE2B-512 ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923 HMAC-SHA256 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 streamed SHA256 cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0
Encrypt and authenticate with AES-256-GCM
EVP_CipherInit_ex2 with AES-256-GCM encrypts and authenticates in one pass; EVP_CTRL_AEAD_GET_TAG reads the 16-byte tag, and with EVP_CTRL_AEAD_SET_TAG decryption refuses anything that changed, including the associated data that travels in the clear. The output is ciphertext then tag, the layout WebCrypto reads.
ciphertext 78b3da886495443e7a46341982c948837dbf7b3510d774e5072034 tag 4f8c8f6e9e6177f201ed83f957fbe6d7 meet at the north gate at 7 refused: std::runtime_error: authentication failed: wrong key, nonce or AAD, or the data was changed
The same EVP_Cipher* calls as the C++. EVP_CTRL_AEAD_GET_TAG and EVP_CTRL_AEAD_SET_TAG are macros and have no binding, so their values are written out. There is no C++ exception to catch: JavaScript throws its own Error when EVP_DecryptFinal_ex refuses the tag, so the last line loses the std::runtime_error: prefix the C++ version prints.
ciphertext 78b3da886495443e7a46341982c948837dbf7b3510d774e5072034 tag 4f8c8f6e9e6177f201ed83f957fbe6d7 meet at the north gate at 7 refused: authentication failed: wrong key, nonce or AAD, or the data was changed
Generate a key, sign and verify
EVP_PKEY_Q_keygen makes a key pair, PEM_write_bio_PUBKEY exports its public half, EVP_DigestSign signs and EVP_DigestVerify checks with nothing but that PEM. The same calls sign with ECDSA P-256, Ed25519 and ML-DSA-65, the post-quantum signature of FIPS 204. Keys are random, so the example prints what stays the same: sizes, strength and the verdicts.
P-256: EC, 128-bit security, signatures up to 72 bytes, valid true, altered message false ED25519: ED25519, 128-bit security, signatures up to 64 bytes, valid true, altered message false ML-DSA-65: ML-DSA-65, 192-bit security, signatures up to 3309 bytes, valid true, altered message false
EVP_PKEY_Q_keygen is variadic and has no binding (importing it fails the build with MISSING_EXPORT), so the key comes from the context it would create: EVP_PKEY_CTX_new_from_name, EVP_PKEY_CTX_set_group_name for P-256 and EVP_PKEY_generate, whose EVP_PKEY ** result is read from an allocPointer slot with readPointerAt. Signing and verifying are the same EVP_DigestSign and EVP_DigestVerify calls, with the signature length read back from its out-parameter.
P-256: EC, 128-bit security, signatures up to 72 bytes, valid true, altered message false ED25519: ED25519, 128-bit security, signatures up to 64 bytes, valid true, altered message false ML-DSA-65: ML-DSA-65, 192-bit security, signatures up to 3309 bytes, valid true, altered message false
Make a self-signed certificate for localhost
The most asked OpenSSL question, answered in C++: X509_new, a random serial, X509V3_EXT_conf_nid for the subjectAltName browsers match, and X509_sign with the key it certifies, the fields openssl req -x509 -addext subjectAltName=... writes. The key comes from the signing example, and the certificate example reads the result back.
-----BEGIN CERTIFICATE----- CN=localhost DNS:localhost, IP Address:127.0.0.1 valid 2026-01-01 00:00:00Z to 2027-01-01 00:00:00Z EC prime256v1 256 bits, self-signed true localhost true example.com false
Every call the C++ makes is reachable. X509V3_CTX is a struct that crossbind binds as the class v3_ext_ctx: JavaScript creates one and X509V3_set_ctx fills it in C, which the subject key identifier needs (without it X509V3_EXT_conf_nid returns null). The key is used as generated rather than passed in as PEM, and macros such as X509_VERSION_3 and MBSTRING_UTF8 are written out as numbers.
-----BEGIN CERTIFICATE----- CN=localhost DNS:localhost, IP Address:127.0.0.1 valid 2026-01-01 00:00:00Z to 2027-01-01 00:00:00Z EC prime256v1 256 bits, self-signed true localhost true example.com false
What is different on WebAssembly
- In a browser the module runs in a Worker by default (
useWorker), so every call returns a promise:awaitcalls and constructors alike. - The module has its own filesystem:
m.FSwrites files,m.getFileBytesreads them back andm.autoMountFilesmountsFileobjects from an<input type=file>./memfslives in memory;/opfspersists across reloads and needs the Worker. See Filesystem. - In Node.js,
m.FSis the real disk, so use real paths there. - Multi-threaded builds (
runtime: 'mt') need COOP and COEP headers in production. See Threading.
Other platforms
- OpenSSL overview: the apps, every platform's setup and the packages.
- OpenSSL for Android: React Native apps on Android.
- OpenSSL for iOS: React Native apps on iOS.
- OpenSSL for WASI: command-line programs under wasmtime.
Facts on this page come from the port manifests in the repository and from what npm served on beta when the site was built. See the Libraries guide for the full consumer flow.