Home

Awesome

noble-ciphers

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

For discussions, questions and support, visit GitHub Discussions section of the repository.

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@0.5.0/chacha'; // Deno

Examples

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);
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 aes = gcm(key, nonce);
const data = utf8ToBytes('hello, noble');
const ciphertext = aes.encrypt(data);
const data_ = aes.decrypt(ciphertext); // utils.bytesToUtf8(data_) === data

Use existing key instead of a new one

const key = new Uint8Array([
  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,
]);
const nonce = new Uint8Array([
  180, 90, 27, 63, 160, 191, 150, 33, 67, 212, 86, 71, 144, 6, 200, 102, 218,
  32, 23, 147, 8, 41, 147, 11,
]);
// or, hex:
import { hexToBytes } from '@noble/ciphers/utils';
const key2 = hexToBytes('4b7f89bac90a1086fef73f5da2cbe93b2fae9dfbf7678ae1f3e75fd118ddf999');
const nonce2 = hexToBytes('9610467513de0bbd7c4cc2c3c64069f1802086fbd3232b13');

Different AES block modes

import { gcm, siv, ctr, cfb, cbc, ecb } from '@noble/ciphers/aes';
import { randomBytes } from '@noble/ciphers/webcrypto';
const plaintext = new Uint8Array(32).fill(16);
const key = randomBytes(32); // 24 for AES-192, 16 for AES-128
for (let cipher of [gcm, siv]) {
  const stream = cipher(key, randomBytes(12));
  const ciphertext_ = stream.encrypt(plaintext);
  const plaintext_ = stream.decrypt(ciphertext_);
}
for (const cipher of [ctr, cbc, cbc]) {
  const stream = cipher(key, randomBytes(16));
  const ciphertext_ = stream.encrypt(plaintext);
  const plaintext_ = stream.decrypt(ciphertext_);
}
for (const cipher of [ecb]) {
  const stream = cipher(key);
  const ciphertext_ = stream.encrypt(plaintext);
  const plaintext_ = stream.decrypt(ciphertext_);
}

Friendly wrapper around AES WebCrypto

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: it's 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 stream = cipher(key, randomBytes(12));
  const ciphertext_ = await stream.encrypt(plaintext);
  const plaintext_ = await stream.decrypt(ciphertext_);
}
for (const cipher of [ctr, cbc]) {
  const stream = cipher(key, randomBytes(16));
  const ciphertext_ = await stream.encrypt(plaintext);
  const plaintext_ = await stream.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);

Encrypt without nonce

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

Re-use same array for input & output

This allows re-use Uint8Array between encryption/decryption calls (works if all plaintext or ciphertext are same size).

NOTE: some ciphers don't support unaligned (byteOffset % 4 !== 0) Uint8Array as destination, since it will significantly decrease performance and this optimization will become 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 buf = new Uint8Array(12 + 16);
const _data = utf8ToBytes('hello, noble');
buf.set(_data, 0); // first 12 bytes
const _12b = buf.subarray(0, 12);

const chacha = chacha20poly1305(key, nonce);
chacha.encrypt(_12b, buf);
chacha.decrypt(buf, _12b); // _12b now same as _data

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, aeskw, aeskwp } from '@noble/ciphers/aes';
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa';
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha';

// 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?

To simplify, XChaCha20-Poly1305 is the safest bet these days. AES-GCM-SIV is the second safest. AES-GCM is the third.

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.

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 not been independently audited yet.

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.

AES uses T-tables, which means it can't be done in constant-time in JS.

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 v20:

encrypt (64B)
├─xsalsa20poly1305 x 485,908 ops/sec @ 2ÎŒs/op
├─chacha20poly1305 x 419,639 ops/sec @ 2ÎŒs/op
├─xchacha20poly1305 x 335,232 ops/sec @ 2ÎŒs/op
├─aes-256-gcm x 143,595 ops/sec @ 6ÎŒs/op
└─aes-256-gcm-siv x 120,743 ops/sec @ 8ÎŒs/op
encrypt (1KB)
├─xsalsa20poly1305 x 135,924 ops/sec @ 7ÎŒs/op
├─chacha20poly1305 x 134,843 ops/sec @ 7ÎŒs/op
├─xchacha20poly1305 x 124,626 ops/sec @ 8ÎŒs/op
├─aes-256-gcm x 39,588 ops/sec @ 25ÎŒs/op
└─aes-256-gcm-siv x 36,989 ops/sec @ 27ÎŒs/op
encrypt (8KB)
├─xsalsa20poly1305 x 22,178 ops/sec @ 45ÎŒs/op
├─chacha20poly1305 x 22,691 ops/sec @ 44ÎŒs/op
├─xchacha20poly1305 x 22,463 ops/sec @ 44ÎŒs/op
├─aes-256-gcm x 8,082 ops/sec @ 123ÎŒs/op
└─aes-256-gcm-siv x 7,907 ops/sec @ 126ÎŒs/op
encrypt (1MB)
├─xsalsa20poly1305 x 171 ops/sec @ 5ms/op
├─chacha20poly1305 x 186 ops/sec @ 5ms/op
├─xchacha20poly1305 x 189 ops/sec @ 5ms/op
├─aes-256-gcm x 73 ops/sec @ 13ms/op
└─aes-256-gcm-siv x 77 ops/sec @ 12ms/op

Unauthenticated encryption:

encrypt (64B)
├─salsa x 1,245,330 ops/sec @ 803ns/op
├─chacha x 1,468,428 ops/sec @ 681ns/op
├─xsalsa x 995,024 ops/sec @ 1ÎŒs/op
└─xchacha x 1,026,694 ops/sec @ 974ns/op
encrypt (1KB)
├─salsa x 349,283 ops/sec @ 2ÎŒs/op
├─chacha x 369,822 ops/sec @ 2ÎŒs/op
├─xsalsa x 326,370 ops/sec @ 3ÎŒs/op
└─xchacha x 334,001 ops/sec @ 2ÎŒs/op
encrypt (8KB)
├─salsa x 55,050 ops/sec @ 18ÎŒs/op
├─chacha x 56,474 ops/sec @ 17ÎŒs/op
├─xsalsa x 54,068 ops/sec @ 18ÎŒs/op
└─xchacha x 55,469 ops/sec @ 18ÎŒs/op
encrypt (1MB)
├─salsa x 449 ops/sec @ 2ms/op
├─chacha x 459 ops/sec @ 2ms/op
├─xsalsa x 448 ops/sec @ 2ms/op
└─xchacha x 459 ops/sec @ 2ms/op

AES
encrypt (64B)
├─ctr-256 x 689,179 ops/sec @ 1ÎŒs/op
├─cbc-256 x 639,795 ops/sec @ 1ÎŒs/op
└─ecb-256 x 668,449 ops/sec @ 1ÎŒs/op
encrypt (1KB)
├─ctr-256 x 93,668 ops/sec @ 10ÎŒs/op
├─cbc-256 x 94,428 ops/sec @ 10ÎŒs/op
└─ecb-256 x 151,699 ops/sec @ 6ÎŒs/op
encrypt (8KB)
├─ctr-256 x 13,342 ops/sec @ 74ÎŒs/op
├─cbc-256 x 13,664 ops/sec @ 73ÎŒs/op
└─ecb-256 x 22,426 ops/sec @ 44ÎŒs/op
encrypt (1MB)
├─ctr-256 x 106 ops/sec @ 9ms/op
├─cbc-256 x 109 ops/sec @ 9ms/op
└─ecb-256 x 179 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

Upgrading

Upgrade from micro-aes-gcm package is simple:

// prepare
const key = Uint8Array.from([
  64, 196, 127, 247, 172, 2, 34, 159, 6, 241, 30, 174, 183, 229, 41, 114, 253, 122, 119, 168, 177,
  243, 155, 236, 164, 159, 98, 72, 162, 243, 224, 195,
]);
const message = 'Hello world';

// previous
import * as aes from 'micro-aes-gcm';
const ciphertext = await aes.encrypt(key, aes.utils.utf8ToBytes(message));
const plaintext = await aes.decrypt(key, ciphertext);
console.log(aes.utils.bytesToUtf8(plaintext) === message);

// became =>

import { gcm } from '@noble/ciphers/aes';
import { bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils';
import { managedNonce } from '@noble/ciphers/webcrypto';
const aes = managedNonce(gcm)(key);
const ciphertext = aes.encrypt(utf8ToBytes(message));
const plaintext = aes.decrypt(key, ciphertext);
console.log(bytesToUtf8(plaintext) === message);

Contributing & testing

  1. Clone the repository
  2. npm install to install build dependencies like TypeScript
  3. npm run build to compile TypeScript code
  4. npm run test will execute all main tests

Resources

Check out 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.