libsodium review
libsodium 0.8.4 is the raw WebAssembly and JavaScript build underneath `libsodium-wrappers`. Its own npm description says "raw library, no wrappers." Our install had no TypeScript declarations, and the browser build measured 304.7 KB minified and 131.8 KB gzipped. The 0.8.4 tagged diff repairs ESM wrapper interop with two export shapes and updates the embedded Sodium source. Application code normally imports `libsodium-wrappers`, waits for `ready`, and lets that package install this engine transitively.
libsodium 0.8.4 installed in 0.7 seconds but produced a 131.8 KB gzipped raw engine with no TypeScript declarations in our sandbox. Application developers should install `libsodium-wrappers`; direct installation belongs to binding authors who need the Emscripten layer.
We installed it
| Install | ✓ · 0.7s | 1 package on disk · 2 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package with exports map |
| Browser | 131.8 KB | gzipped (304.7 KB minified), bundled with esbuild |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does libsodium install cleanly?
Yes. In a fresh container with an empty cache, npm install libsodium finished in 0.7s, leaving 1 package and 2 MB on disk. npm audit reported no known vulnerabilities.
How much does libsodium add to a browser bundle?
131.8 KB gzipped (304.7 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does libsodium work with both ESM and CommonJS?
Yes. Both import 'libsodium' and require('libsodium') worked in Node 22 in our run. The package is published as CommonJS with an exports map.
Does libsodium include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
libsodium or libsodium-wrappers: which should you use?
libsodium-wrappers: Choose this for ordinary browser, Node, or Bun code using the standard Sodium API. libsodium 0.8.4 installed in 0.7 seconds but produced a 131.8 KB gzipped raw engine with no TypeScript declarations in our sandbox.
When should you not use libsodium?
You are adding encryption to an application: the repository explicitly identifies libsodium-wrappers as the module applications should load
Use it if
- You maintain libsodium-wrappers or another binding that directly consumes the generated Emscripten engine
- This package appears under libsodium-wrappers in a lockfile and you are checking why it exists
- You are building a low-level binding and understand Emscripten initialization, heap pointers, and the Sodium C ABI
- You are adding encryption to an application: the repository explicitly identifies `libsodium-wrappers` as the module applications should load
- You need TypeScript declarations: our libsodium 0.8.4 install contained none, while the wrapper project generates declarations for its callable API
- A 131.8 KB gzipped raw crypto engine is too much for the browser route you are shipping
- You need `crypto_pwhash`: the README places password hashing in the sumo wrapper, which exposes a larger set of low-level and deprecated functions
- You target React Native: the repository points that runtime to `react-native-libsodium` instead of the browser and Node build
Setup reality
Our install of libsodium 0.8.4 took 0.7 seconds and left 1 package using 2 MB on disk. It has 0 direct dependencies, 0 peer dependencies, and 0 audit findings. The package is CommonJS with an exports map; both require() and ESM import worked on Node 22. No TypeScript declarations were present. Our browser build reached 304.7 KB minified and 131.8 KB gzipped.
Applications should install libsodium-wrappers and await sodium.ready before reading crypto functions or constants, which are added at runtime. Version 0.8.4 changes the generated ESM wrapper so it accepts either a factory export or an initialized module export. The ESM path also requires globalThis.crypto.getRandomValues; the repository's error names Node 19 or newer as the expected server runtime. No credential or configuration file initializes the library.
Binary inputs and outputs are Uint8Array values. Store nonces, salts, headers, public keys, and an encoding or format version beside ciphertext because decryption cannot reconstruct them. A nonce may be public but cannot repeat under one key. Authentication failure must reject the message. Opaque WebAssembly states without a finalizer, including secretstream and XOF state, require one sodium.free(state) after the last operation. memzero clears the supplied array only; JavaScript copies remain outside its reach.
Patterns
Install the application package install-supported-wrapper
npm uninstall libsodium
npm install libsodium-wrappers`libsodium-wrappers` already depends on the raw `libsodium` engine. A second direct dependency gives application code no extra API.
Wait for runtime exports wait-for-initialization
import sodium from 'libsodium-wrappers';
await sodium.ready;
console.log(sodium.SODIUM_VERSION_STRING);Crypto functions and constants appear after `ready` resolves, so import them through the default wrapper object.
Create a URL-safe random token generate-random-token
await sodium.ready;
const bytes = sodium.randombytes_buf(32);
const token = sodium.to_base64(
bytes,
sodium.base64_variants.URLSAFE_NO_PADDING
);The example requests 32 random bytes and records a specific base64 variant. Other systems must decode the same variant.
Encrypt with a fresh secretbox nonce encrypt-secretbox
const key = sodium.crypto_secretbox_keygen();
function encrypt(message) {
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
const body = sodium.crypto_secretbox_easy(message, nonce, key);
const packed = new Uint8Array(nonce.length + body.length);
packed.set(nonce);
packed.set(body, nonce.length);
return packed;
}A secretbox nonce can travel with the ciphertext, but it must never repeat for the same key.
Authenticate a secretbox payload decrypt-secretbox
function decrypt(packed) {
const offset = sodium.crypto_secretbox_NONCEBYTES;
if (packed.length < offset + sodium.crypto_secretbox_MACBYTES) {
throw new Error('Ciphertext is too short');
}
const nonce = packed.slice(0, offset);
const body = packed.slice(offset);
return sodium.crypto_secretbox_open_easy(body, nonce, key);
}`crypto_secretbox_open_easy` authenticates the body. Treat any thrown verification error as a rejected message.
Sign and verify bytes sign-detached-message
const { publicKey, privateKey } = sodium.crypto_sign_keypair();
const message = sodium.from_string('release manifest');
const signature = sodium.crypto_sign_detached(message, privateKey);
const valid = sodium.crypto_sign_verify_detached(signature, message, publicKey);
if (!valid) throw new Error('Invalid signature');A detached signature authenticates the bytes and key holder; it does not conceal the message.
Seal data to one public key seal-for-recipient
const recipient = sodium.crypto_box_keypair();
const sealed = sodium.crypto_box_seal('private payload', recipient.publicKey);
const opened = sodium.crypto_box_seal_open(
sealed,
recipient.publicKey,
recipient.privateKey
);A sealed box keeps content private for the recipient but does not prove which sender created it.
Create a content digest hash-content
const digest = sodium.crypto_generichash(
sodium.crypto_generichash_BYTES,
sodium.from_string('content to fingerprint'),
null
);
const hex = sodium.to_hex(digest);An unkeyed generic hash fits content fingerprints. Human passwords need the sumo password-hashing functions.
Hash a password with the sumo package hash-password
import sodium from 'libsodium-wrappers-sumo';
await sodium.ready;
const stored = sodium.crypto_pwhash_str(
password,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE
);
if (!sodium.crypto_pwhash_str_verify(stored, candidate)) {
throw new Error('Invalid credentials');
}`crypto_pwhash` is available in `libsodium-wrappers-sumo`, not the standard wrapper. Benchmark its memory and time limits on production hardware.
Refresh an old password hash upgrade-password-hash
const stale = sodium.crypto_pwhash_str_needs_rehash(
stored,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE
);
if (stale && sodium.crypto_pwhash_str_verify(stored, candidate)) {
stored = sodium.crypto_pwhash_str(
candidate,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE
);
}Rehash only after successful verification, while the plaintext candidate is already present during login.
Encrypt ordered chunks and free state encrypt-stream
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
const pushed = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
const finalChunk = sodium.crypto_secretstream_xchacha20poly1305_push(
pushed.state,
'last chunk',
null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);
sodium.free(pushed.state);Send `pushed.header` before the encrypted chunks, preserve their order, require a final tag on decryption, and free the state once.
Compare secrets and clear one buffer compare-and-wipe
if (expected.length !== received.length || !sodium.memcmp(expected, received)) {
throw new Error('Authentication failed');
}
sodium.memzero(key);`memcmp` expects equal-length arrays for constant-time comparison. `memzero` clears only the exact `Uint8Array` passed to it.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| libsodium-wrappers | npm | Choose this for ordinary browser, Node, or Bun code using the standard Sodium API |
| libsodium-wrappers-sumo | npm | Choose it only when a required function such as password hashing is absent from the standard wrapper |
| sodium-native | npm | Choose it for Node-only systems that accept a native addon and do not need a browser build |
More security guides
cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

