Home

Awesome

noble-ciphers

Audited & minimal JS implementation of Salsa20, ChaCha and AES.

Take a glance at GitHub Discussions for questions and support.

This library belongs to noble cryptography

noble cryptography ā€” high-security, easily auditable set of contained cryptographic libraries and tools.

Usage

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.1.0/chacha'; // Deno

Examples

[!NOTE] Use different nonce every time encrypt() is done.

Encrypt with XChaCha20-Poly1305

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

Encrypt with AES-256-GCM

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

AES: gcm, siv, ctr, cfb, cbc, ecb

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_);
}

Friendly WebCrypto AES

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_);
}

AESKW and AESKWP

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);

Auto-handle nonces

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);

Reuse array for input and output

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 chacha = chacha20poly1305(key, nonce);

const input = utf8ToBytes('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.

All imports

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';

Internals

Implemented primitives

Which cipher should I pick?

We suggest to use XChaCha20-Poly1305. If you can't use it, prefer AES-GCM-SIV, or AES-GCM.

How to encrypt properly

Nonces

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.

Encryption limits

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.

AES internals and block modes

cipher = encrypt(block, key). Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round does:

  1. S-box, table substitution
  2. Shift rows, cyclic shift left of all rows of data array
  3. Mix columns, multiplying every column by fixed polynomial
  4. Add round key, round_key xor i-th column of array

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.

GCM / SIV are not ideal:

Security

The 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.

Constant-timeness

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. The analysis was mentioned in hal-04652991.

Supply chain security

Randomness

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.

Speed

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:

64B
xsalsa20poly1305 x 501,756 ops/sec @ 1Ī¼s/op
chacha20poly1305 x 428,082 ops/sec @ 2Ī¼s/op
xchacha20poly1305 x 343,170 ops/sec @ 2Ī¼s/op
aes-256-gcm x 147,492 ops/sec @ 6Ī¼s/op
aes-256-gcm-siv x 122,085 ops/sec @ 8Ī¼s/op
# Unauthenticated encryption
salsa20 x 1,288,659 ops/sec @ 776ns/op
xsalsa20 x 1,055,966 ops/sec @ 947ns/op
chacha20 x 1,506,024 ops/sec @ 664ns/op
xchacha20 x 1,064,962 ops/sec @ 939ns/op
chacha8 x 1,683,501 ops/sec @ 594ns/op
chacha12 x 1,628,664 ops/sec @ 614ns/op
aes-ecb-256 x 775,193 ops/sec @ 1Ī¼s/op
aes-cbc-256 x 738,552 ops/sec @ 1Ī¼s/op
aes-ctr-256 x 737,463 ops/sec @ 1Ī¼s/op

1MB
xsalsa20poly1305 x 205 ops/sec @ 4ms/op
chacha20poly1305 x 213 ops/sec @ 4ms/op
xchacha20poly1305 x 213 ops/sec @ 4ms/op
aes-256-gcm x 77 ops/sec @ 12ms/op
aes-256-gcm-siv x 81 ops/sec @ 12ms/op
# Unauthenticated encryption
salsa20 x 498 ops/sec @ 2ms/op
xsalsa20 x 493 ops/sec @ 2ms/op
chacha20 x 506 ops/sec @ 1ms/op
xchacha20 x 506 ops/sec @ 1ms/op
chacha8 x 956 ops/sec @ 1ms/op
chacha12 x 735 ops/sec @ 1ms/op
# Wrapper over built-in webcrypto
webcrypto ctr-256 x 5,068 ops/sec @ 197Ī¼s/op
webcrypto cbc-256 x 1,116 ops/sec @ 895Ī¼s/op
webcrypto gcm-256 x 4,374 ops/sec @ 228Ī¼s/op Ā± 1.69% [172Ī¼s..7ms]

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)
ā”œā”€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)
ā”œā”€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)
ā”œā”€stablelib x 27 ops/sec @ 36ms/op
ā”œā”€noble-webcrypto x 4,059 ops/sec @ 246Ī¼s/op
ā””ā”€noble x 74 ops/sec @ 13ms/op

Contributing & testing

Check out github.com/paulmillr/guidelines for general coding practices and rules.

See paulmillr.com/noble for useful resources, articles, documentation and demos related to the library.

License

The MIT License (MIT)

Copyright (c) 2023 Paul Miller (https://paulmillr.com) Copyright (c) 2016 Thomas Pornin pornin@bolet.org

See LICENSE file.