libsodium
This npm package is the raw Emscripten build of the Sodium cryptography library, with WebAssembly and a pure JavaScript fallback. It is not the application-facing API most developers mean when they ask for libsodium in JavaScript. The package metadata says “raw library, no wrappers,” and the repository tells applications to load libsodium-wrappers, which installs this package transitively and adds readiness handling, Uint8Array conversions, generated TypeScript declarations, constants, and callable high-level crypto functions. Treat libsodium as an implementation artifact unless you are maintaining a wrapper.
Do not install the bare libsodium package for application code. Install libsodium-wrappers and let it bring this raw engine transitively; choose the sumo wrapper only for a verified missing primitive.
Use it if
- You maintain libsodium-wrappers or another binding that needs the raw compiled module as its engine
- It appears transitively under libsodium-wrappers in your lockfile and you are deciding whether it is legitimate
- You are writing a specialized Emscripten binding and are prepared to manage raw memory pointers, underscored C exports, initialization, and version coupling yourself
- You are writing application cryptography: the README explicitly says to load libsodium-wrappers, which already depends on this package and exposes the supported JavaScript API
- You use TypeScript: libsodium 0.8.4 ships no declarations, while libsodium-wrappers 0.8.4 publishes generated declarations for both import and require paths
- Bundle weight is tight: the repository says the standard browser package with WebAssembly and pure JavaScript is around 290 KB gzipped, and the sumo build is around 375 KB
- You need password hashing from the standard build: crypto_pwhash is only in the larger sumo variant, whose README also includes undocumented, deprecated, low-level, and easy-to-misuse symbols
- You want Web Crypto CryptoKey objects or a React Native binding: this API uses Uint8Array values and opaque state handles, and the repository points React Native users to react-native-libsodium
Setup reality
Do not add libsodium directly to an application. Install libsodium-wrappers, which declares libsodium as its dependency, then import the wrapper's default export and await sodium.ready before touching any crypto function or runtime-added constant. Crypto functions are attached dynamically after initialization, so they cannot be imported as ESM named exports; only helpers such as ready, free, from_hex, to_hex, from_string, and to_string have named exports. There are no native builds for consumers and no credentials or config file, because the published artifact includes WebAssembly plus a pure JavaScript fallback. That convenience costs size: the README puts the standard distribution around 290 KB gzipped and sumo around 375 KB. Use libsodium-wrappers-sumo only when the required primitive, notably crypto_pwhash, is absent from standard. Binary values are Uint8Array objects, so define one storage encoding and include every nonce, salt, header, algorithm version, and public key needed for later decryption or verification. Nonces must never repeat under the same key; use randombytes_buf at the library's required size instead of counters unless you have a reviewed protocol. Authentication failure must stop processing rather than return partial plaintext. Long-running state APIs allocate opaque WebAssembly handles; the README requires sodium.free() for APIs without a finalizer, including secretstream and XOF state. Browser script users must serve or inline with UTF-8 metadata or the embedded WebAssembly can be corrupted. For secrets, memzero() only clears the specific Uint8Array you pass, not copies created by your application or garbage collector.
Patterns
Install the application-facing packageinstall-supported-wrapper
npm uninstall libsodium
npm install libsodium-wrapperslibsodium-wrappers installs libsodium transitively; keeping a separate direct dependency on the raw package usually adds version ambiguity without adding an API.
Wait for Sodium before using runtime exportsinitialize-wrapper
import sodium from 'libsodium-wrappers';
await sodium.ready;
console.log(sodium.SODIUM_VERSION_STRING);Crypto functions and constants are attached dynamically after ready resolves, so they cannot be imported as named ESM exports.
Generate a URL-safe random tokengenerate-random-token
await sodium.ready;
const bytes = sodium.randombytes_buf(32);
const token = sodium.to_base64(
bytes,
sodium.base64_variants.URLSAFE_NO_PADDING
);Store or transmit the encoding label with long-lived values; the wrapper's default base64 variant is URL-safe without padding, which may differ from other systems.
Encrypt and prepend a fresh secretbox nonceencrypt-secretbox
const key = sodium.crypto_secretbox_keygen();
function encrypt(message) {
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
const packed = new Uint8Array(nonce.length + ciphertext.length);
packed.set(nonce);
packed.set(ciphertext, nonce.length);
return packed;
}A nonce is public but must never repeat with the same key; generating it at NONCEBYTES and storing it beside the ciphertext is the README's recommended shape.
Split and authenticate a secretbox payloaddecrypt-secretbox
function decrypt(packed) {
const min = sodium.crypto_secretbox_NONCEBYTES + sodium.crypto_secretbox_MACBYTES;
if (packed.length < min) throw new Error('Ciphertext is too short');
const nonce = packed.slice(0, sodium.crypto_secretbox_NONCEBYTES);
const ciphertext = packed.slice(sodium.crypto_secretbox_NONCEBYTES);
const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
return sodium.to_string(plaintext);
}open_easy authenticates before returning plaintext and throws when verification fails; catch that only to reject the message, never to continue with guessed data.
Create and verify a detached signaturesign-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 signature proves possession of the private key but does not encrypt the message; distribute public keys through an authenticated channel.
Encrypt anonymously to a recipient public keyseal-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
);
console.log(sodium.to_string(opened));Sealed boxes hide the message for the recipient but do not authenticate a sender; add a signature or authenticated protocol when sender identity matters.
Hash content with the generic hash APIhash-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 is suitable for fingerprints, not password storage; use the sumo password-hashing API for human passwords.
Store and verify a password with the sumo wrapperhash-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 sumo-only and deliberately memory intensive; benchmark the chosen limit on production hardware and enforce request rate limits.
Detect hashes that need stronger current limitsrehash-password
const needsUpgrade = sodium.crypto_pwhash_str_needs_rehash(
stored,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE
);
if (needsUpgrade && sodium.crypto_pwhash_str_verify(stored, candidate)) {
stored = sodium.crypto_pwhash_str(
candidate,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE
);
}Run the upgrade after a successful login so the plaintext password is available; this pattern also requires libsodium-wrappers-sumo.
Encrypt an ordered stream and free its statesencrypt-stream-chunks
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
const pushed = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
const c1 = sodium.crypto_secretstream_xchacha20poly1305_push(
pushed.state, 'chunk one', null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);
const c2 = sodium.crypto_secretstream_xchacha20poly1305_push(
pushed.state, 'chunk two', null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);
const pulled = sodium.crypto_secretstream_xchacha20poly1305_init_pull(pushed.header, key);
const r1 = sodium.crypto_secretstream_xchacha20poly1305_pull(pulled, c1, null);
const r2 = sodium.crypto_secretstream_xchacha20poly1305_pull(pulled, c2, null);
if (!r1 || !r2 || r2.tag !== sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
throw new Error('Invalid or truncated stream');
}
sodium.free(pushed.state);
sodium.free(pulled);Transmit the header before ciphertext chunks, preserve chunk order, require a final tag, and free both opaque WebAssembly states after the last operation.
Compare equal-length secrets and clear a key buffercompare-and-wipe
if (expected.length !== received.length || !sodium.memcmp(expected, received)) {
throw new Error('Authentication failed');
}
sodium.memzero(key);memcmp is constant-time for equal-length buffers; memzero clears only that Uint8Array, so avoid creating extra secret copies that remain managed by JavaScript's garbage collector.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| libsodium-wrappers | npm | The correct default for browser, Node, or Bun application code using standard Sodium functions |
| libsodium-wrappers-sumo | npm | You specifically need password hashing or another function omitted from the standard build and accept the larger, riskier surface |
| sodium-native | npm | Node-only code can accept native installation constraints in exchange for native bindings and no browser payload |