mrkeyoor.com_
Sat 08 Aug 22:54 UTC
npmSecurityupdated 08 Aug 2026

libsodium-wrappers

libsodium-wrappers is the recommended standard JavaScript wrapper around libsodium, compiled to WebAssembly with a pure-JavaScript fallback for browsers, Node.js, and Bun. It exposes high-level authenticated encryption, public-key boxes, signatures, hashes, secure random bytes, streaming encryption, binary encoders, and constant-time helpers through Uint8Array-friendly functions. The module now ships CommonJS, ESM, and TypeScript declarations, but cryptographic functions are attached only after its asynchronous ready promise resolves.

Verdict

libsodium-wrappers is a strong choice when a protocol genuinely needs libsodium across JavaScript runtimes. Do not install it casually for one ordinary hash or browser cipher, and do not mistake a large primitive catalog for key management or secure protocol design.

API stability4/5The wrapper intentionally follows libsodium's C API while removing explicit buffer lengths, and established function families such as secretbox, box, sign, generichash, and randombytes retain recognizable contracts. Version 0.8.4 adds modern ESM exports and generated declarations without replacing the default sodium.ready model. A score of five would overstate ease: the package is still pre-1.0, crypto symbols appear dynamically after initialization, state handles require lifecycle care, and output object shapes must track generated wrappers.
Docs4/5The README is unusually candid about standard versus sumo, package layouts, async readiness, named-export limits, UTF-8 corruption when inlining WASM, binary return shapes, state handles, manual free requirements, supported engines, and the separate React Native project. It also supplies complete secretstream and secretbox examples. The remaining difficulty is navigation: the JavaScript wrapper mirrors a very large C API, so choosing a safe primitive still requires consulting libsodium's conceptual documentation rather than copying function signatures alone.
Maintenance5/5Version 0.8.4 was published in April 2026, the repository was pushed in July 2026, and its current npm metadata includes generated ESM and CommonJS types plus an export map. GitHub reports no open issues and pull requests in its combined count at the captured point. The project tracks both libsodium and modern build tooling, including current Emscripten, Binaryen, Bun, and generated bindings, which is materially different from an old wrapper merely retaining high download traffic.
Ecosystem5/5The package recorded 3,357,179 downloads in the measured week and the repository has 1,151 stars and 164 forks. It interoperates with native libsodium implementations across languages, supports browsers, Node.js, and Bun, and documents a compatible React Native binding. Standard and sumo npm variants cover different risk and feature needs, while byte-level compatibility with the underlying library makes it suitable for cross-language protocols rather than JavaScript-only data formats.

Use it if

  • You need the same well-reviewed libsodium primitives in browsers and server-side JavaScript
  • You need XChaCha20-Poly1305, secretstream, sealed boxes, or Ed25519 features not consistently offered by Web Crypto
  • Your protocol already specifies libsodium-compatible byte formats and algorithms
  • You can budget for asynchronous initialization, a large crypto payload, and explicit key lifecycle design
Skip it if

Setup reality

Install libsodium-wrappers, import its default export, and await sodium.ready once before touching any crypto function or constant. Helpers such as from_hex can be named imports in ESM, but cryptographic functions cannot because they are added dynamically at runtime. The package includes ESM, CommonJS, and corresponding TypeScript declarations, plus a libsodium dependency containing compiled code. No native compiler is needed for the npm artifact, but WebAssembly initialization is asynchronous and a restrictive browser Content Security Policy may require deployment work. The README warns that inlining the browser build without UTF-8 metadata or a charset=utf-8 response can corrupt its embedded WASM bytes; serving the separate artifact is safer. The standard package deliberately excludes crypto_pwhash, so password-based encryption requires the sumo package or a different KDF implementation. Inputs and outputs are mainly Uint8Array values. Design an encoding and framing format that stores algorithm version, nonce, ciphertext, and any associated-data requirements unambiguously. Nonces are not secret but must obey each primitive's uniqueness rules. Keys are your responsibility: generate them with the matching keygen function, store them outside source control, rotate them deliberately, and never substitute user passwords for keys. memzero can overwrite a Uint8Array, but JavaScript copies, strings, logs, serialization, and garbage collector behavior prevent a general guarantee that every secret copy disappears. Stateful hash and secretstream APIs allocate WASM heap state; the README says to call sodium.free for state types without an automatic finalizer. Authentication failure should terminate processing, not fall back to plaintext or a second unauthenticated decoder.

Patterns

Wait for cryptographic functions to loadinitialize-module

import sodium from 'libsodium-wrappers';

await sodium.ready;
const key = sodium.crypto_secretbox_keygen();

Crypto functions and constants are attached after ready resolves. They cannot be imported as ESM named exports even though conversion helpers can.

Generate secure random bytesgenerate-random-bytes

await sodium.ready;
const token = sodium.randombytes_buf(32);
const tokenText = sodium.to_base64(
  token,
  sodium.base64_variants.URLSAFE_NO_PADDING
);

Use randombytes_buf for security tokens. Do not replace it with Math.random or derive independent secrets by slicing one human password.

Encrypt and frame a secretbox messageencrypt-secretbox

await sodium.ready;
const key = sodium.crypto_secretbox_keygen();
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
const ciphertext = sodium.crypto_secretbox_easy('private message', nonce, key);

const packed = {
  nonce: sodium.to_base64(nonce),
  ciphertext: sodium.to_base64(ciphertext),
};

Store the nonce with the ciphertext. The nonce is not secret, but reuse with the same key breaks the primitive's security. Store the key separately.

Authenticate and decrypt a secretbox messagedecrypt-secretbox

await sodium.ready;
const nonce = sodium.from_base64(packed.nonce);
const ciphertext = sodium.from_base64(packed.ciphertext);
const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
const message = sodium.to_string(plaintext);

Authentication failure throws. Treat it as tampering or corruption and stop; never return the ciphertext, guessed plaintext, or an unauthenticated fallback.

Bind metadata with XChaCha20-Poly1305encrypt-with-associated-data

await sodium.ready;
const key = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
const nonce = sodium.randombytes_buf(
  sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
);
const aad = sodium.from_string('tenant=acme;v=1');
const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
  'payload', aad, null, nonce, key
);

The same associated data must be supplied during decryption. It is authenticated but not encrypted, so do not put secrets in it.

Create a BLAKE2b generic hashhash-data

await sodium.ready;
const digest = sodium.crypto_generichash(
  32,
  sodium.from_string('content to hash'),
  null
);
console.log(sodium.to_hex(digest));

crypto_generichash is not a password hash. The standard package excludes crypto_pwhash, and fast hashes are unsafe for storing human passwords.

Create and verify a detached signaturesign-message

await sodium.ready;
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);

A signature authenticates the holder of the private key but does not encrypt the message. Distribute public keys through a trusted channel.

Encrypt anonymously to one recipientseal-for-recipient

await sodium.ready;
const recipient = sodium.crypto_box_keypair();
const sealed = sodium.crypto_box_seal('for recipient only', recipient.publicKey);
const opened = sodium.crypto_box_seal_open(
  sealed, recipient.publicKey, recipient.privateKey
);

Sealed boxes do not authenticate a sender. Use a signature or an authenticated sender-recipient box protocol when sender identity matters.

Round-trip URL-safe base64encode-binary-values

await sodium.ready;
const variant = sodium.base64_variants.URLSAFE_NO_PADDING;
const text = sodium.to_base64(bytes, variant);
const restored = sodium.from_base64(text, variant);

The default is already URLSAFE_NO_PADDING, but specifying the variant makes a stored or network protocol explicit and prevents decoder mismatches.

Compare equal-length secrets in constant timecompare-secrets

await sodium.ready;
const expected = sodium.from_hex(expectedHex);
const received = sodium.from_hex(receivedHex);
const matches = expected.length === received.length &&
  sodium.memcmp(expected, received);

memcmp requires equal-size values. Validate lengths before calling it and avoid converting secrets to strings for ordinary equality checks.

Overwrite a key buffer after useerase-key-buffer

await sodium.ready;
const key = sodium.crypto_secretbox_keygen();
try {
  useKey(key);
} finally {
  sodium.memzero(key);
}

memzero overwrites this Uint8Array only. Copies, serialized forms, logs, strings, engine internals, and other references may still contain the key.

Finalize and free a secretstream stateencrypt-message-stream

await sodium.ready;
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
const { state, header } =
  sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
try {
  const finalChunk = sodium.crypto_secretstream_xchacha20poly1305_push(
    state, 'last chunk', null,
    sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
  );
  send(header, finalChunk);
} finally {
  sodium.free(state);
}

secretstream state is an opaque WASM heap handle. The README specifically requires free() for this API after the last operation; do not use the handle afterward.

Alternatives

PackageRegistryPick it when
@noble/ciphersnpmYou want audited TypeScript-oriented pure-JavaScript cipher primitives and can use its narrower algorithm set
tweetnaclnpmYou need a much smaller, frozen NaCl-compatible API and do not need newer libsodium features
sodium-nativenpmYour service is Node-only and prefers native libsodium performance despite native addon installation requirements
libsodium-wrappers-sumonpmYou specifically require crypto_pwhash or another symbol omitted from the recommended standard build