Audited & minimal JS implementation of Salsa20, ChaCha and AES.
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
We support all major platforms and runtimes. For Deno, ensure to use npm specifier. 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 { xchacha20poly1305 } from '@noble/ciphers/chacha';
// import { xchacha20poly1305 } from 'npm:@noble/ciphers@1.0.0/chacha'; // Deno
[!NOTE] Use different nonce every time
encrypt()
is done.
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { utf8ToBytes } from '@noble/ciphers/utils';
import { randomBytes } from '@noble/ciphers/webcrypto';
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'; // hex key
// const key = hexToBytes('4b7f89bac90a1086fef73f5da2cbe93b2fae9dfbf7678ae1f3e75fd118ddf999');
const nonce = randomBytes(24);
const chacha = xchacha20poly1305(key, nonce);
const data = utf8ToBytes('hello, noble');
const ciphertext = chacha.encrypt(data);
const data_ = chacha.decrypt(ciphertext); // utils.bytesToUtf8(data_) === data
import { gcm } from '@noble/ciphers/aes';
import { utf8ToBytes } from '@noble/ciphers/utils';
import { randomBytes } from '@noble/ciphers/webcrypto';
const key = randomBytes(32);
const nonce = randomBytes(24);
const data = utf8ToBytes('hello, noble');
const aes = gcm(key, nonce);
const ciphertext = aes.encrypt(data);
const data_ = aes.decrypt(ciphertext); // utils.bytesToUtf8(data_) === data
import { gcm, siv, ctr, cfb, cbc, ecb } from '@noble/ciphers/aes';
import { randomBytes } from '@noble/ciphers/webcrypto';
const plaintext = new Uint8Array(32).fill(16);
for (let cipher of [gcm, siv]) {
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_);
}
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, randomBytes } from '@noble/ciphers/webcrypto';
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_);
}
import { aeskw, aeskwp } from '@noble/ciphers/aes';
import { hexToBytes } from '@noble/ciphers/utils';
const kek = hexToBytes('000102030405060708090A0B0C0D0E0F');
const keyData = hexToBytes('00112233445566778899AABBCCDDEEFF');
const ciphertext = aeskw(kek).encrypt(keyData);
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.
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { managedNonce } from '@noble/ciphers/webcrypto';
import { hexToBytes, utf8ToBytes } from '@noble/ciphers/utils';
const key = hexToBytes('fa686bfdffd3758f6377abbc23bf3d9bdc1a0dda4a6e7f8dbdd579fa1ff6d7e1');
const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
const data = utf8ToBytes('hello, noble');
const ciphertext = chacha.encrypt(data);
const data_ = chacha.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';
import { utf8ToBytes } from '@noble/ciphers/utils';
import { randomBytes } from '@noble/ciphers/webcrypto';
const key = randomBytes(32);
const nonce = randomBytes(12);
const inputLength = 12;
const tagLength = 16;
const buf = new Uint8Array(inputLength + tagLength);
const _data = utf8ToBytes('hello, noble'); // length == 12
buf.set(_data, 0); // first inputLength bytes
const _start = buf.subarray(0, inputLength);
const chacha = chacha20poly1305(key, nonce);
chacha.encrypt(_start, buf);
chacha.decrypt(buf, _start); // _start now same as _data
import { gcm, siv } from '@noble/ciphers/aes';
import { xsalsa20poly1305 } from '@noble/ciphers/salsa';
import { secretbox } from '@noble/ciphers/salsa'; // == xsalsa20poly1305
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
// Unauthenticated encryption: make sure to use HMAC or similar
import { ctr, cfb, cbc, ecb } from '@noble/ciphers/aes';
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa';
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha';
// KW
import { aeskw, aeskwp } from '@noble/ciphers/aes';
// Utilities
import { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils';
import { managedNonce, randomBytes } from '@noble/ciphers/webcrypto';
import { poly1305 } from '@noble/ciphers/_poly1305';
import { ghash, polyval } from '@noble/ciphers/_polyval';
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.We suggest to use XChaCha20-Poly1305. If you can't use it, prefer AES-GCM-SIV, or AES-GCM.
Math.random
etc.01, 02...
Most ciphers need a key and a nonce (aka initialization vector / IV) to encrypt a data:
ciphertext = encrypt(plaintext, key, nonce)
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
So, you can't repeat nonces. One way of doing so 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])
Counters are OK, but it's not always possible to store current counter value: e.g. in decentralized, unsyncable systems.
Randomness is OK, but there's a catch:
ChaCha20 and AES-GCM use 96-bit / 12-byte nonces, which implies higher chance of collision.
In the example above, random()
can collide and produce repeating nonce.
Chance is even higher for 64-bit nonces, which GCM allows - don't use them.
To safely use random nonces, utilize XSalsa20 or XChaCha: they increased nonce length to 192-bit, minimizing a chance of collision. AES-SIV is also fine. In situations where you can't use eXtended-nonce algorithms, key rotation is advised. hkdf would work great for this case.
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. See draft-irtf-cfrg-aead-limits for details.
2**36-256
2**38-64
2**32.5
2**46
, but only integrity is affected, not confidentiality2**72
2**69/B
where B is max blocks encrypted by a key. Meaning
2**59
for 1KB, 2**49
for 1MB, 2**39
for 1GB2**100
cipher = encrypt(block, key)
. Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round does:
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.
[i][j]
tweak arguments corresponding to sector i and 16-byte block (part of sector) j.
Lacks MAC.GCM / SIV are not ideal:
2**32
(4B) msgsThe library has been independently audited:
It is tested against property-based, cross-library and Wycheproof vectors, and has fuzzing by Guido Vranken's cryptofuzz.
If you see anything unusual: investigate and report.
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. Nonetheless we're targetting algorithmic constant time.
The library uses T-tables for AES, which leak access timings. This is also done in OpenSSL and Go stdlib for performance reasons.
npm-diff
We're deferring to built-in crypto.getRandomValues which is considered cryptographically secure (CSPRNG).
In the past, browsers had bugs that made it weak: it may happen again. Implementing a userspace CSPRNG to get resilient to the weakness is even worse: there is no reliable userspace source of quality entropy.
To summarize, noble is the fastest JS implementation of Salsa, ChaCha and AES.
You can gain additional speed-up and
avoid memory allocations by passing output
uint8array into encrypt / decrypt methods.
Benchmark results on Apple M2 with node v22:
encrypt (64B)
├─xsalsa20poly1305 x 485,908 ops/sec @ 2μs/op
├─chacha20poly1305 x 414,250 ops/sec @ 2μs/op
├─xchacha20poly1305 x 331,674 ops/sec @ 3μs/op
├─aes-256-gcm x 144,237 ops/sec @ 6μs/op
└─aes-256-gcm-siv x 121,373 ops/sec @ 8μs/op
encrypt (1KB)
├─xsalsa20poly1305 x 136,574 ops/sec @ 7μs/op
├─chacha20poly1305 x 136,017 ops/sec @ 7μs/op
├─xchacha20poly1305 x 126,008 ops/sec @ 7μs/op
├─aes-256-gcm x 40,149 ops/sec @ 24μs/op
└─aes-256-gcm-siv x 37,420 ops/sec @ 26μs/op
encrypt (8KB)
├─xsalsa20poly1305 x 22,517 ops/sec @ 44μs/op
├─chacha20poly1305 x 23,187 ops/sec @ 43μs/op
├─xchacha20poly1305 x 22,837 ops/sec @ 43μs/op
├─aes-256-gcm x 7,993 ops/sec @ 125μs/op
└─aes-256-gcm-siv x 7,836 ops/sec @ 127μs/op
encrypt (1MB)
├─xsalsa20poly1305 x 186 ops/sec @ 5ms/op
├─chacha20poly1305 x 191 ops/sec @ 5ms/op
├─xchacha20poly1305 x 191 ops/sec @ 5ms/op
├─aes-256-gcm x 71 ops/sec @ 14ms/op
└─aes-256-gcm-siv x 75 ops/sec @ 13ms/op
Unauthenticated encryption:
encrypt (64B)
├─salsa x 1,221,001 ops/sec @ 819ns/op
├─chacha x 1,373,626 ops/sec @ 728ns/op
├─xsalsa x 1,019,367 ops/sec @ 981ns/op
└─xchacha x 1,019,367 ops/sec @ 981ns/op
encrypt (1KB)
├─salsa x 349,162 ops/sec @ 2μs/op
├─chacha x 372,717 ops/sec @ 2μs/op
├─xsalsa x 327,868 ops/sec @ 3μs/op
└─xchacha x 332,446 ops/sec @ 3μs/op
encrypt (8KB)
├─salsa x 55,178 ops/sec @ 18μs/op
├─chacha x 51,535 ops/sec @ 19μs/op
├─xsalsa x 54,274 ops/sec @ 18μs/op
└─xchacha x 55,645 ops/sec @ 17μs/op
encrypt (1MB)
├─salsa x 451 ops/sec @ 2ms/op
├─chacha x 464 ops/sec @ 2ms/op
├─xsalsa x 455 ops/sec @ 2ms/op
└─xchacha x 462 ops/sec @ 2ms/op
AES
encrypt (64B)
├─ctr-256 x 679,347 ops/sec @ 1μs/op
├─cbc-256 x 699,300 ops/sec @ 1μs/op
└─ecb-256 x 717,875 ops/sec @ 1μs/op
encrypt (1KB)
├─ctr-256 x 93,423 ops/sec @ 10μs/op
├─cbc-256 x 95,721 ops/sec @ 10μs/op
└─ecb-256 x 154,726 ops/sec @ 6μs/op
encrypt (8KB)
├─ctr-256 x 12,908 ops/sec @ 77μs/op
├─cbc-256 x 13,411 ops/sec @ 74μs/op
└─ecb-256 x 22,681 ops/sec @ 44μs/op
encrypt (1MB)
├─ctr-256 x 105 ops/sec @ 9ms/op
├─cbc-256 x 108 ops/sec @ 9ms/op
└─ecb-256 x 181 ops/sec @ 5ms/op
Compare to other implementations:
xsalsa20poly1305 (encrypt, 1MB)
├─tweetnacl x 108 ops/sec @ 9ms/op
└─noble x 190 ops/sec @ 5ms/op
chacha20poly1305 (encrypt, 1MB)
├─node x 1,360 ops/sec @ 735μs/op
├─stablelib x 117 ops/sec @ 8ms/op
└─noble x 193 ops/sec @ 5ms/op
chacha (encrypt, 1MB)
├─node x 2,035 ops/sec @ 491μs/op
├─stablelib x 206 ops/sec @ 4ms/op
└─noble x 474 ops/sec @ 2ms/op
ctr-256 (encrypt, 1MB)
├─node x 3,530 ops/sec @ 283μs/op
├─stablelib x 70 ops/sec @ 14ms/op
├─aesjs x 31 ops/sec @ 32ms/op
├─noble-webcrypto x 4,589 ops/sec @ 217μs/op
└─noble x 107 ops/sec @ 9ms/op
cbc-256 (encrypt, 1MB)
├─node x 993 ops/sec @ 1ms/op
├─stablelib x 63 ops/sec @ 15ms/op
├─aesjs x 29 ops/sec @ 34ms/op
├─noble-webcrypto x 1,087 ops/sec @ 919μs/op
└─noble x 110 ops/sec @ 9ms/op
gcm-256 (encrypt, 1MB)
├─node x 3,196 ops/sec @ 312μs/op
├─stablelib x 27 ops/sec @ 36ms/op
├─noble-webcrypto x 4,059 ops/sec @ 246μs/op
└─noble x 74 ops/sec @ 13ms/op
npm install
to install build dependencies like TypeScriptnpm run build
to compile TypeScript codenpm run test
will execute all main testsCheck out 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.