
Research
/Security News
Intercom’s npm Package Compromised in Ongoing Mini Shai-Hulud Worm Attack
Compromised intercom-client@7.0.4 npm package is tied to the ongoing Mini Shai-Hulud worm attack targeting developer and CI/CD secrets.
@noble/ciphers
Advanced tools
Audited & minimal JS implementation of Salsa20, ChaCha and AES.
Use awasm-noble if you need an even faster (WASM) alternative. Check out Upgrading for information about upgrading from previous versions. Take a glance at GitHub Discussions for questions and support.
noble cryptography — high-security, easily auditable set of contained cryptographic libraries and tools.
npm install @noble/ciphers
deno add jsr:@noble/ciphers
We support all major platforms and runtimes. For React Native, you may need a polyfill for getRandomValues. A standalone file noble-ciphers.js is also available.
// import * from '@noble/ciphers'; // Error: use sub-imports, to ensure small app size
import { gcm, gcmsiv } from '@noble/ciphers/aes.js';
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha.js';
import { xsalsa20poly1305 } from '@noble/ciphers/salsa.js';
// Unauthenticated encryption: make sure to use HMAC or similar
import { ctr, cfb, cbc, ecb } from '@noble/ciphers/aes.js';
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa.js';
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha.js';
import { aeskw, aeskwp } from '@noble/ciphers/aes.js'; // KW
import { bytesToHex, hexToBytes, managedNonce, randomBytes } from '@noble/ciphers/utils.js';
[!NOTE] Use different nonce every time
encrypt()is done.
import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
import { randomBytes } from '@noble/ciphers/utils.js';
const key = randomBytes(32); // random key
// const key = new Uint8Array([ // existing key
// 169, 88, 160, 139, 168, 29, 147, 196, 14, 88, 237, 76, 243, 177, 109, 140,
// 195, 140, 80, 10, 216, 134, 215, 71, 191, 48, 20, 104, 189, 37, 38, 55,
// ]);
// import { hexToBytes } from '@noble/ciphers/utils.js'; // hex key
// const key = hexToBytes('4b7f89bac90a1086fef73f5da2cbe93b2fae9dfbf7678ae1f3e75fd118ddf999');
const nonce = randomBytes(24);
const chacha = xchacha20poly1305(key, nonce);
const data = new TextEncoder().encode('hello noble');
const ciphertext = chacha.encrypt(data);
const data_ = chacha.decrypt(ciphertext); // new TextDecoder().decode(data_) === data
import { gcm } from '@noble/ciphers/aes.js';
import { randomBytes } from '@noble/ciphers/utils.js';
const key = randomBytes(32);
const nonce = randomBytes(24);
const data = new TextEncoder().encode('hello noble');
const aes = gcm(key, nonce);
const ciphertext = aes.encrypt(data);
const data_ = aes.decrypt(ciphertext); // new TextDecoder().decode(data_) === data
We provide API that manages nonce internally instead of exposing them to library's user.
For encrypt: a nonceBytes-length buffer is fetched from CSPRNG and prenended to encrypted ciphertext.
For decrypt: first nonceBytes of ciphertext are treated as nonce.
[!NOTE] AES-GCM & ChaCha (NOT XChaCha) limit amount of messages encryptable under the same key.
import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
import { hexToBytes, managedNonce } from '@noble/ciphers/utils.js';
const key = hexToBytes('fa686bfdffd3758f6377abbc23bf3d9bdc1a0dda4a6e7f8dbdd579fa1ff6d7e1');
const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
const data = new TextEncoder().encode('hello noble');
const ciphertext = chacha.encrypt(data);
const data_ = chacha.decrypt(ciphertext);
import { gcm, gcmsiv, aessiv, ctr, cfb, cbc, ecb } from '@noble/ciphers/aes.js';
import { randomBytes } from '@noble/ciphers/utils.js';
const plaintext = new Uint8Array(32).fill(16);
for (let cipher of [gcm, gcmsiv, aessiv]) {
const key = randomBytes(32); // 24 for AES-192, 16 for AES-128
const nonce = randomBytes(12);
const ciphertext_ = cipher(key, nonce).encrypt(plaintext);
const plaintext_ = cipher(key, nonce).decrypt(ciphertext_);
}
for (const cipher of [ctr, cbc, cfb]) {
const key = randomBytes(32); // 24 for AES-192, 16 for AES-128
const nonce = randomBytes(16);
const ciphertext_ = cipher(key, nonce).encrypt(plaintext);
const plaintext_ = cipher(key, nonce).decrypt(ciphertext_);
}
for (const cipher of [ecb]) {
const key = randomBytes(32); // 24 for AES-192, 16 for AES-128
const ciphertext_ = cipher(key).encrypt(plaintext);
const plaintext_ = cipher(key).decrypt(ciphertext_);
}
// AESKW, AESKWP
import { aeskw, aeskwp } from '@noble/ciphers/aes.js';
import { hexToBytes } from '@noble/ciphers/utils.js';
const kek = hexToBytes('000102030405060708090A0B0C0D0E0F');
const keyData = hexToBytes('00112233445566778899AABBCCDDEEFF');
const ciphertext = aeskw(kek).encrypt(keyData);
Noble implements AES. Sometimes people want to use built-in crypto.subtle instead. However, it has terrible API. We simplify access to built-ins.
[!NOTE] Webcrypto methods are always async.
import { gcm, ctr, cbc } from '@noble/ciphers/webcrypto.js';
import { randomBytes } from '@noble/ciphers/utils.js';
const plaintext = new Uint8Array(32).fill(16);
const key = randomBytes(32);
for (const cipher of [gcm]) {
const nonce = randomBytes(12);
const ciphertext_ = await cipher(key, nonce).encrypt(plaintext);
const plaintext_ = await cipher(key, nonce).decrypt(ciphertext_);
}
for (const cipher of [ctr, cbc]) {
const nonce = randomBytes(16);
const ciphertext_ = await cipher(key, nonce).encrypt(plaintext);
const plaintext_ = await cipher(key, nonce).decrypt(ciphertext_);
}
To avoid additional allocations, Uint8Array can be reused between encryption and decryption calls.
[!NOTE] Some ciphers don't support unaligned (
byteOffset % 4 !== 0) Uint8Array as destination. It can decrease performance, making the optimization pointless.
import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
import { randomBytes } from '@noble/ciphers/utils.js';
const key = randomBytes(32);
const nonce = randomBytes(12);
const chacha = chacha20poly1305(key, nonce);
const input = new TextEncoder().encode('hello noble'); // length == 12
const inputLength = input.length;
const tagLength = 16;
const buf = new Uint8Array(inputLength + tagLength);
const start = buf.subarray(0, inputLength);
start.set(input); // copy input to buf
chacha.encrypt(start, buf); // encrypt into `buf`
chacha.decrypt(buf, start); // decrypt into `start`
xsalsa20poly1305 also supports this, but requires 32 additional bytes for encryption / decryption, due to its inner workings.
We provide userspace CSPRNG (cryptographically secure pseudorandom number generator). It's best to limit their usage to non-production, non-critical cases: for example, test-only usage. ChaCha-based CSPRNG does not have a specification as per 2025, which makes it less secure.
import { randomBytes } from '@noble/ciphers/utils.js';
import { rngAesCtrDrbg256 } from '@noble/ciphers/aes.js';
import { rngChacha8, rngChacha20 } from '@noble/ciphers/chacha.js';
// 1. Best: WebCrypto
const rnd1 = randomBytes(32);
// 2. AES-CTR DRBG
const seed2 = randomBytes(48);
const rnd2 = rngAesCtrDrbg256(seed2).randomBytes(1024);
// 3. ChaCha8 CSPRNG
const seed3 = randomBytes(32);
const rnd3 = rngChacha8(seed3).randomBytes(1024);
It is not safe to convert password into Uint8Array. Instead, KDF stretching function like PBKDF2 / Scrypt / Argon2id should be applied to convert password to AES key. Make sure to use salt (app-specific secret) in addition to password.
npm install @noble/hashes
import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
import { managedNonce } from '@noble/ciphers/utils.js';
import { scrypt } from '@noble/hashes/scrypt.js';
// Convert password into 32-byte key using scrypt
const PASSWORD = 'correct-horse-battery-staple';
const APP_SPECIFIC_SECRET = 'salt-12345678-secret';
const SECURITY_LEVEL = 2 ** 20; // requires 1GB of RAM to calculate
// sync, but scryptAsync is also available
const key = scrypt(PASSWORD, APP_SPECIFIC_SECRET, { N: SECURITY_LEVEL, r: 8, p: 1, dkLen: 32 });
// Use random, managed nonce
const chacha = managedNonce(xchacha20poly1305)(key);
const data = new TextEncoder().encode('hello noble');
const ciphertext = chacha.encrypt(data);
const data_ = chacha.decrypt(ciphertext);
We suggest to use XChaCha20-Poly1305 because it's very fast and allows random keys. AES-GCM-SIV is also a good idea, because it provides resistance against nonce reuse. AES-GCM is a good option when those two are not available.
Math.random etc.01, 02...hash(key) can be included in ciphertext,
however, this would violate ciphertext indistinguishability:
an attacker would know which key was used - so HKDF(key, i)
could be used instead.Most ciphers need a key and a nonce (aka initialization vector / IV) to encrypt a data. Repeating (key, nonce) pair with different plaintexts would allow an attacker to decrypt it.
ciphertext_a = encrypt(plaintext_a, key, nonce)
ciphertext_b = encrypt(plaintext_b, key, nonce)
stream_diff = xor(ciphertext_a, ciphertext_b) # Break encryption
One way of not repeating nonces is using counters:
for i in 0..:
ciphertext[i] = encrypt(plaintexts[i], key, i)
Another is generating random nonce every time:
for i in 0..:
rand_nonces[i] = random()
ciphertext[i] = encrypt(plaintexts[i], key, rand_nonces[i])
random() can collide and produce repeating nonce.
Chance is even higher for 64-bit nonces, which GCM allows - don't use them.A "protected message" would mean a probability of 2**-50 that a passive attacker
successfully distinguishes the ciphertext outputs of the AEAD scheme from the outputs
of a random function.
2**36-2562**38-642**32.52**46, but only integrity (MAC) is affected, not confidentiality (encryption)2**722**69/B where B is max blocks encrypted by a key. Meaning
2**59 for 1KB, 2**49 for 1MB, 2**39 for 1GB2**100managedNonce: AES-GCM, ChaCha2**23 (8M) messages for 2**-50 chance, 2**32.5 (4B) for 2**-32.5 chanceCheck out draft-irtf-cfrg-aead-limits for details.
For non-deterministic (not ECB) schemes, initialization vector (IV) is mixed to block/key; and each new round either depends on previous block's key, or on some counter.
2**32 (4B) msgs.
b) key wear-out under random nonces is even smaller: 2**23 (8M) messages for 2**-50 chance.
c) MAC can be forged: see Poly1305 documentation.[i][j] tweak arguments corresponding to
sector i and 16-byte block (part of sector) j. Lacks MAC.The library has been audited:
It is tested against property-based, cross-library and Wycheproof vectors, and is being fuzzed in the separate repo.
If you see anything unusual: investigate and report.
We're targetting algorithmic constant time. JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve timing attack resistance in a scripting language. Which means any other JS library can't have constant-timeness. Even statically typed Rust, a language without GC, makes it harder to achieve constant-time for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages.
The library uses T-tables for AES, which leak access timings. This is also done in OpenSSL and Go stdlib for performance reasons. The analysis was mentioned in hal-04652991.
For this package, there are 0 dependencies; and a few dev dependencies:
We rely on the built-in
crypto.getRandomValues,
which is considered a cryptographically secure PRNG.
Browsers have had weaknesses in the past - and could again - but implementing a userspace CSPRNG is even worse, as there’s no reliable userspace source of high-quality entropy.
Cryptographically relevant quantum computer, if built, will allow to utilize Grover's algorithm to break ciphers in 2^n/2 operations, instead of 2^n.
This means AES128 should be replaced with AES256. Salsa and ChaCha are already safe.
Australian ASD prohibits AES128 after 2030.
npm run bench
Benchmarks measured on Apple M4. If you need truly exemplar performance, switch to awasm-noble.
64B
xsalsa20poly1305 x 735,835 ops/sec @ 1μs/op
chacha20poly1305 x 581,395 ops/sec @ 1μs/op
xchacha20poly1305 x 468,384 ops/sec @ 2μs/op
aes-256-gcm x 201,126 ops/sec @ 4μs/op
aes-256-gcm-siv x 162,284 ops/sec @ 6μs/op
# Unauthenticated encryption
salsa20 x 1,655,629 ops/sec @ 604ns/op
xsalsa20 x 1,400,560 ops/sec @ 714ns/op
chacha20 x 1,996,007 ops/sec @ 501ns/op
xchacha20 x 1,404,494 ops/sec @ 712ns/op
chacha8 x 2,145,922 ops/sec @ 466ns/op
chacha12 x 2,036,659 ops/sec @ 491ns/op
aes-ecb-256 x 1,019,367 ops/sec @ 981ns/op
aes-cbc-256 x 931,966 ops/sec @ 1μs/op
aes-ctr-256 x 954,198 ops/sec @ 1μs/op
1MB
xsalsa20poly1305 x 334 ops/sec @ 2ms/op
chacha20poly1305 x 333 ops/sec @ 2ms/op
xchacha20poly1305 x 334 ops/sec @ 2ms/op
aes-256-gcm x 94 ops/sec @ 10ms/op
aes-256-gcm-siv x 90 ops/sec @ 11ms/op
# Unauthenticated encryption
salsa20 x 831 ops/sec @ 1ms/op
xsalsa20 x 830 ops/sec @ 1ms/op
chacha20 x 804 ops/sec @ 1ms/op
xchacha20 x 797 ops/sec @ 1ms/op
chacha8 x 1,495 ops/sec @ 668μs/op
chacha12 x 1,148 ops/sec @ 871μs/op
aes-ecb-256 x 289 ops/sec @ 3ms/op
aes-cbc-256 x 114 ops/sec @ 8ms/op
aes-ctr-256 x 127 ops/sec @ 7ms/op
# Wrapper over built-in webcrypto
webcrypto ctr-256 x 6,508 ops/sec @ 153μs/op
webcrypto cbc-256 x 1,820 ops/sec @ 549μs/op
webcrypto gcm-256 x 5,106 ops/sec @ 195μs/op
Compare to other implementations:
xsalsa20poly1305 (encrypt, 1MB)
├─tweetnacl x 196 mb/sec
├─awasm-noble_threads x 2,318 mb/sec
├─awasm-noble_no_threads x 1,196 mb/sec
└─noble x 305 mb/sec
aes-ctr-256 (encrypt, 1MB)
├─stablelib x 123 mb/sec
├─aesjs x 42 mb/sec
├─awasm-noble_thread x 2,105 mb/sec
├─awasm-noble_no_threads x 272 mb/sec
├─noble_webcrypto x 5,965 mb/sec
└─noble x 124 mb/sec
Supported node.js versions:
Changelog of v2, when upgrading from ciphers v1:
.js extension must be used for all modules
@noble/ciphers/aes@noble/ciphers/aes.jsrandomBytes and managedNonce to utils.jsstring_assert (use utils), _micro and crypto (use webcrypto)npm install && npm run build && npm test will build the code and run tests.npm run lint / npm run format will run linter / fix linter issues.npm run bench will run benchmarksnpm run build:release will build single fileSee paulmillr.com/noble for useful resources, articles, documentation and demos related to the library.
The MIT License (MIT)
Copyright (c) 2023 Paul Miller (https://paulmillr.com) Copyright (c) 2016 Thomas Pornin pornin@bolet.org
See LICENSE file.
FAQs
Audited & minimal JS implementation of Salsa20, ChaCha and AES
The npm package @noble/ciphers receives a total of 10,898,332 weekly downloads. As such, @noble/ciphers popularity was classified as popular.
We found that @noble/ciphers demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Compromised intercom-client@7.0.4 npm package is tied to the ongoing Mini Shai-Hulud worm attack targeting developer and CI/CD secrets.

Research
Socket detected a malicious supply chain attack on PyPI package lightning versions 2.6.2 and 2.6.3, which execute credential-stealing malware on import.

Research
A brand-squatted TanStack npm package used postinstall scripts to steal .env files and exfiltrate developer secrets to an attacker-controlled endpoint.