mrkeyoor.com_
Wed 23 Sept 00:36 UTC
npmSecurityupdated 22 Sept 2026

libsodium-wrappers review

libsodium-wrappers 0.8.4 exposes the standard libsodium cryptographic API to browsers, Node, and Bun through WebAssembly with a JavaScript fallback. Its default object gains authenticated encryption, signatures, hashes, sealed boxes, random-byte generation, secret streams, encoders, and memory helpers after sodium.ready resolves. Inputs and outputs normally use Uint8Array. The 0.8.4 release fixed ESM interop with libsodium factory exports and added a regression test; it includes the libsodium 1.0.22 update shipped in 0.8.3.

Verdict

libsodium-wrappers 0.8.4 installed in 0.6 seconds and 2 MB on our box, but its full browser import reached 147.7 KB gzipped, so the dependency makes sense for protocols that specifically need libsodium across runtimes. Choose Web Crypto or a smaller package for a single common primitive, and do not adopt this wrapper before key management and ciphertext framing are designed.

We installed it

Lab card: what happened when we installed libsodium-wrappersScreenshot of libsodium-wrappers documentation
Install✓ · 0.6s2 packages on disk · 2 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser147.7 KBgzipped (424 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does libsodium-wrappers install cleanly?

Yes. In a fresh container with an empty cache, npm install libsodium-wrappers finished in 0.6s, leaving 2 packages and 2 MB on disk. npm audit reported no known vulnerabilities.

How much does libsodium-wrappers add to a browser bundle?

147.7 KB gzipped (424 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does libsodium-wrappers work with both ESM and CommonJS?

Yes. Both import 'libsodium-wrappers' and require('libsodium-wrappers') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does libsodium-wrappers include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

libsodium-wrappers or @noble/ciphers: which should you use?

@noble/ciphers: Use it for a smaller TypeScript-focused set of pure JavaScript ciphers when cross-language libsodium compatibility is unnecessary. libsodium-wrappers 0.8.4 installed in 0.6 seconds and 2 MB on our box, but its full browser import reached 147.7 KB gzipped, so the dependency makes sense for protocols that specifically need libsodium across runtimes.

When should you not use libsodium-wrappers?

Web Crypto already covers the required browser operation; our full import measured 147.7 KB gzipped, which is expensive for one ordinary primitive

API stability4/5Version 0.8.4 mirrors the established libsodium function families while removing explicit buffer-length arguments and returning Uint8Array values or named result objects. The release fixed ESM factory interop without changing crypto call signatures. Stability is reduced by the pre-1.0 package version, asynchronous symbol attachment, generated wrapper surface, and state handles whose cleanup behavior varies between APIs.
Docs4/5The README documents sodium.ready, allowed named exports, standard and sumo differences, browser UTF-8 failures, binary conversions, secretbox framing, secretstream cleanup, and the React Native alternative. It also warns that sumo includes undocumented and deprecated symbols. Selecting an algorithm still requires the main libsodium documentation because a long generated API list cannot explain protocol design, nonce rules, or key lifecycle decisions.
Maintenance5/5Version 0.8.4 shipped on April 19, 2026 with an ESM interoperability fix and regression test, following the libsodium 1.0.22 update in 0.8.3. The repository was pushed in July 2026, and GitHub currently reports 0 open issues and PRs. Generated CommonJS and ESM declarations, browser tests, Bun benchmarks, and current build tooling show active maintenance beyond republishing compiled output.
Ecosystem5/5The npm endpoint counted 3,821,456 downloads for the week ending August 24, 2026; the repository has 1,151 stars and 165 forks. Ciphertexts and signatures interoperate with libsodium implementations in other languages, and the project supports browser, Node, and Bun builds plus a documented React Native sibling. The standard and sumo split gives teams a defined choice between common high-level calls and the much wider symbol set.

Use it if

  • A browser and server must produce byte-compatible ciphertexts or signatures using the same libsodium protocol
  • Your design calls for XChaCha20-Poly1305, secretstream, sealed boxes, or another primitive that Web Crypto does not expose consistently
  • You need generated TypeScript declarations for a broad libsodium API in both ESM and CommonJS projects
  • The application already has a reviewed plan for key creation, storage, rotation, framing, and authentication failures
Skip it if

Setup reality

Our fresh libsodium-wrappers 0.8.4 install completed in 0.6 seconds and left two packages using 2 MB. The wrapper declared one direct dependency, no peers, 568 KB unpacked, and an ISC license. npm audit reported 0 known vulnerabilities. TypeScript declarations were bundled. An import-all browser build measured 424 KB minified and 147.7 KB gzipped, enough weight to matter on a user-facing route.

No native compiler, account, key, or config file is needed for the published npm artifact. Import the default sodium object and await sodium.ready before reading a crypto constant or calling a primitive. Version 0.8.4 has an exports map; CommonJS require() and ESM import both worked in our sandbox. Helper functions have named ESM exports, but dynamically attached cryptographic functions must come from the initialized default object.

Keys, nonces, and framing belong to the application. Generate keys with the matching keygen function, keep them out of source control, and store the nonce beside its ciphertext. Define a format with an algorithm version and any associated data so future code can parse old records. The standard build omits crypto_pwhash. Do not replace a key with a human password or use crypto_generichash as a password-storage function.

Stateful APIs allocate handles in WebAssembly memory. The README requires sodium.free() after secretstream and XOF state types that lack an automatic finalizer; finalized state must never be reused. Authentication failure ends processing and should not trigger a plaintext fallback. memzero overwrites one Uint8Array, while strings, logs, serialized copies, and other references can retain secret material. When inlining the browser artifact, UTF-8 metadata and a charset=utf-8 response prevent corruption of embedded WASM bytes.

Patterns

Wait for the wrapper before using crypto initialize-sodium

import sodium from 'libsodium-wrappers';

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

Cryptographic functions and constants appear on the default object after ready resolves. Version 0.8.4 fixed ESM factory interoperability.

Generate a URL-safe random token create-random-token

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

randombytes_buf uses the library's secure random source. Math.random is unsuitable for tokens or keys.

Encrypt with a fresh secretbox nonce encrypt-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 record', nonce, key);

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

Persist the nonce beside the ciphertext and keep the key elsewhere. Reusing one nonce with the same key breaks secretbox security.

Authenticate before decoding plaintext decrypt-secretbox

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

A failed open means the bytes are corrupt or unauthenticated. Stop processing instead of trying a plaintext decoder.

Authenticate metadata with XChaCha20-Poly1305 encrypt-with-aad

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=42;schema=1');
const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
  'payload', aad, null, nonce, key
);

Decryption needs identical associated data. AAD is authenticated and remains visible, so it must not contain a secret.

Compute a BLAKE2b digest hash-bytes

await sodium.ready;
const digest = sodium.crypto_generichash(
  32,
  sodium.from_string('artifact contents'),
  null
);
console.log(sodium.to_hex(digest));

crypto_generichash is a fast general-purpose hash. It is not suitable for storing passwords; crypto_pwhash is absent from the standard package.

Sign and verify a manifest sign-bytes

await sodium.ready;
const keys = sodium.crypto_sign_keypair();
const manifest = sodium.from_string('release=2026.08');
const signature = sodium.crypto_sign_detached(manifest, keys.privateKey);
const valid = sodium.crypto_sign_verify_detached(
  signature, manifest, keys.publicKey
);

A detached signature proves possession of the private key and leaves the message readable. Public-key distribution still needs a trusted channel.

Create an anonymous sealed box seal-for-recipient

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

A sealed box hides the message for one recipient but supplies no sender identity. Add a signature when the receiver must authenticate the sender.

Round-trip explicit base64 variants encode-binary

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

Store the chosen variant in the protocol definition. Producer and consumer must agree about alphabet and padding.

Compare fixed-length byte arrays compare-secrets

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

memcmp performs a constant-time equality check on equal-length arrays. Validate lengths before calling it.

Overwrite a key buffer clear-key-buffer

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

memzero clears this Uint8Array. It cannot erase copies in logs, strings, serialized objects, engine internals, or other references.

Finalize and release secretstream state finish-secret-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 has no automatic finalizer. The README requires free() after the last operation and forbids using that state handle again.

Alternatives

PackageRegistryPick it when
@noble/ciphersnpmUse it for a smaller TypeScript-focused set of pure JavaScript ciphers when cross-language libsodium compatibility is unnecessary
tweetnaclnpmUse it for a compact and frozen NaCl-style surface when newer libsodium algorithms are outside the protocol
sodium-nativenpmUse it in Node-only services that accept a native addon in exchange for direct native libsodium bindings
libsodium-wrappers-sumonpmUse it only when crypto_pwhash or another symbol missing from the standard build is required

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.