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

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.

Verdict

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

Lab card: what happened when we installed libsodiumScreenshot of libsodium documentation
Install✓ · 0.7s1 package on disk · 2 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser131.8 KBgzipped (304.7 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 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

API stability2/5The Sodium C API below version 0.8.4 is established, but this npm package exposes generated Emscripten machinery rather than the supported JavaScript wrapper. Consumers face initialization state, raw exports, heap operations, and release coupling to generated files. The current tagged diff changes how wrapper code detects factory versus module exports, showing why applications should depend on the wrapper boundary instead of the engine shape.
Docs3/5The repository README directly tells applications to use `libsodium-wrappers`, documents `ready`, named helper exports, browser loading, UTF-8 corruption errors, standard versus sumo contents, binary conversions, and manual state cleanup. The linked repository returned HTTP 200. Those instructions cover the wrapper well; they deliberately do not provide an application tutorial for direct calls into the bare `libsodium` package.
Maintenance5/5npm published 0.8.4 on April 19, 2026, and GitHub records a push on July 14, 2026. The 0.8.4 diff adds an ESM interop test and handles factory and module export shapes in generated wrappers. The preceding 0.8.3 release updated the embedded native library to Sodium 1.0.22. GitHub currently reports 0 open issues and pull requests, and the repository is not archived.
Ecosystem4/5The npm API counted 3,858,477 downloads for August 18 through August 24, 2026, and GitHub reports 1,151 stars. The project supports browsers, Node, Bun, WebAssembly, and a JavaScript fallback, while companion packages cover standard wrappers, sumo functions, native Node bindings, and React Native. Direct usage of this raw package remains uncommon by design because wrappers own the developer-facing API.

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
Skip it if

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

PackageRegistryPick it when
libsodium-wrappersnpmChoose this for ordinary browser, Node, or Bun code using the standard Sodium API
libsodium-wrappers-sumonpmChoose it only when a required function such as password hashing is absent from the standard wrapper
sodium-nativenpmChoose 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.