Git Product home page Git Product logo

noble-ciphers's Introduction

noble-ciphers

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

  • ๐Ÿ”’ Auditable
  • ๐Ÿ”ป Tree-shakeable: unused code is excluded from your builds
  • ๐ŸŽ Fast: hand-optimized for caveats of JS engines
  • ๐Ÿ” Reliable: property-based / cross-library / wycheproof tests ensure correctness
  • ๐Ÿ’ผ AES: ECB, CBC, CTR, CFB, GCM, SIV (nonce misuse-resistant)
  • ๐Ÿ’ƒ Salsa20, ChaCha, XSalsa20, XChaCha, ChaCha8, ChaCha12, Poly1305
  • ๐Ÿฅˆ Two AES implementations: pure JS or friendly wrapper around webcrypto
  • ๐Ÿชถ 45KB (8KB gzipped) for everything, 10KB (3KB gzipped) for ChaCha build

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/[email protected]/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');

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

Use same array for input and output

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

// Utilities
import { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils';
import { managedNonce, randomBytes } from '@noble/ciphers/webcrypto';

Implementations

Salsa

import { xsalsa20poly1305 } from '@noble/ciphers/salsa';
import { secretbox } from '@noble/ciphers/salsa'; // == xsalsa20poly1305
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa';

Salsa20 stream cipher was released in 2005. Salsa's goal was to implement AES replacement that does not rely on S-Boxes, which are hard to implement in a constant-time manner. Salsa20 is usually faster than AES, a big deal on slow, budget mobile phones.

XSalsa20, extended-nonce variant was released in 2008. It switched nonces from 96-bit to 192-bit, and became safe to be picked at random.

Nacl / Libsodium popularized term "secretbox", a simple black-box authenticated encryption. Secretbox is just xsalsa20-poly1305. We provide the alias and corresponding seal / open methods. We don't provide "box" or "sealedbox".

Check out PDF and wiki.

ChaCha

import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha';

ChaCha20 stream cipher was released in 2008. ChaCha aims to increase the diffusion per round, but had slightly less cryptanalysis. It was standardized in RFC 8439 and is now used in TLS 1.3.

XChaCha20 extended-nonce variant is also provided. Similar to XSalsa, it's safe to use with randomly-generated nonces.

Check out PDF and wiki.

AES

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

AES is a variant of Rijndael block cipher, standardized by NIST in 2001. We provide the fastest available pure JS implementation.

We support AES-128, AES-192 and AES-256: the mode is selected dynamically, based on key length (16, 24, 32).

AES-GCM-SIV nonce-misuse-resistant mode is also provided. It's recommended to use it, to prevent catastrophic consequences of nonce reuse. Our implementation of SIV has the same speed as GCM: there is no performance hit.

Check out AES internals and block modes.

Webcrypto AES

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

We also have a separate wrapper over WebCrypto built-in.

It's the same as using crypto.subtle, but with massively simplified API.

Unlike pure js version, it's asynchronous.

Poly1305, GHash, Polyval

import { poly1305 } from '@noble/ciphers/_poly1305';
import { ghash, polyval } from '@noble/ciphers/_polyval';

We expose polynomial-evaluation MACs: Poly1305, AES-GCM's GHash and AES-SIV's Polyval.

Poly1305 (PDF, wiki) is a fast and parallel secret-key message-authentication code suitable for a wide variety of applications. It was standardized in RFC 8439 and is now used in TLS 1.3.

Polynomial MACs are not perfect for every situation: they lack Random Key Robustness: the MAC can be forged, and can't be used in PAKE schemes. See invisible salamanders attack. To combat invisible salamanders, 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.

FF1

Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G. See more info.

Managed nonces

import { managedNonce } from '@noble/ciphers/webcrypto';
import { gcm, siv, ctr, cbc, cbc, ecb } from '@noble/ciphers/aes';
import { xsalsa20poly1305 } from '@noble/ciphers/salsa';
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';

const wgcm = managedNonce(gcm);
const wsiv = managedNonce(siv);
const wcbc = managedNonce(cbc);
const wctr = managedNonce(ctr);
const wsalsapoly = managedNonce(xsalsa20poly1305);
const wchacha = managedNonce(chacha20poly1305);
const wxchacha = managedNonce(xchacha20poly1305);

// Now:
const encrypted = wgcm(key).encrypt(data); // no 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.

Guidance

Which cipher should I pick?

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

How to encrypt properly

  • Use unpredictable key with enough entropy
    • Random key must be using cryptographically secure random number generator (CSPRNG), not Math.random etc.
    • Non-random key generated from KDF is fine
    • Re-using key is fine, but be aware of rules for cryptographic key wear-out and encryption limits
  • Use new nonce every time and don't repeat it
    • chacha and salsa20 are fine for sequential counters that never repeat: 01, 02...
    • xchacha and xsalsa20 should be used for random nonces instead
  • Prefer authenticated encryption (AEAD)
    • HMAC+ChaCha / HMAC+AES / chacha20poly1305 / aes-gcm is good
    • chacha20 without poly1305 or hmac / aes-ctr / aes-cbc is bad
    • Flipping bits or ciphertext substitution won't be detected in unauthenticated ciphers
  • Don't re-use keys between different protocols
    • For example, using secp256k1 key in AES is bad
    • Use hkdf or, at least, a hash function to create sub-key instead

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.

  • Max message size:
    • AES-GCM: ~68GB, 2**36-256
    • Salsa, ChaCha, XSalsa, XChaCha: ~256GB, 2**38-64
  • Max amount of protected messages, under same key:
    • AES-GCM: 2**32.5
    • Salsa, ChaCha: 2**46, but only integrity is affected, not confidentiality
    • XSalsa, XChaCha: 2**72
  • Max amount of protected messages, across all keys:
    • AES-GCM: 2**69/B where B is max blocks encrypted by a key. Meaning 2**59 for 1KB, 2**49 for 1MB, 2**39 for 1GB
    • Salsa, ChaCha, XSalsa, XChaCha: 2**100
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.

  • ECB โ€” simple deterministic replacement. Dangerous: always map x to y. See AES Penguin
  • CBC โ€” key is previous roundโ€™s block. Hard to use: need proper padding, also needs MAC
  • CTR โ€” counter, allows to create streaming cipher. Requires good IV. Parallelizable. OK, but no MAC
  • GCM โ€” modern CTR, parallel, with MAC
  • SIV โ€” synthetic initialization vector, nonce-misuse-resistant. Guarantees that, when a nonce is repeated, the only security loss is that identical plaintexts will produce identical ciphertexts.
  • XTS โ€” used in hard drives. Similar to ECB (deterministic), but has [i][j] tweak arguments corresponding to sector i and 16-byte block (part of sector) j. Not authenticated!

GCM / SIV are not ideal:

  • Conservative key wear-out is 2**32 (4B) msgs
  • MAC can be forged: see Poly1305 section above. Same for SIV

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

  • Commits are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures.
  • Releases are transparent and built on GitHub CI. Make sure to verify provenance logs
  • Rare releasing is followed to ensure less re-audit need for end-users
  • Dependencies are minimized and locked-down:
    • If your app has 500 dependencies, any dep could get hacked and you'll be downloading malware with every install. We make sure to use as few dependencies as possible
    • We prevent automatic dependency updates by locking-down version ranges. Every update is checked with npm-diff
  • Dev Dependencies are only used if you want to contribute to the repo. They are disabled for end-users:
    • scure-base, micro-bmark and micro-should are developed by the same author and follow identical security practices
    • prettier (linter), fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. The packages are big, which makes it hard to audit their source code thoroughly and fully

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,672 ops/sec @ 2ฮผs/op
โ”œโ”€chacha20poly1305 x 466,200 ops/sec @ 2ฮผs/op
โ”œโ”€xchacha20poly1305 x 312,500 ops/sec @ 3ฮผs/op
โ”œโ”€aes-256-gcm x 151,057 ops/sec @ 6ฮผs/op
โ””โ”€aes-256-gcm-siv x 124,984 ops/sec @ 8ฮผs/op
encrypt (1KB)
โ”œโ”€xsalsa20poly1305 x 146,477 ops/sec @ 6ฮผs/op
โ”œโ”€chacha20poly1305 x 145,518 ops/sec @ 6ฮผs/op
โ”œโ”€xchacha20poly1305 x 126,119 ops/sec @ 7ฮผs/op
โ”œโ”€aes-256-gcm x 43,207 ops/sec @ 23ฮผs/op
โ””โ”€aes-256-gcm-siv x 39,363 ops/sec @ 25ฮผs/op
encrypt (8KB)
โ”œโ”€xsalsa20poly1305 x 23,773 ops/sec @ 42ฮผs/op
โ”œโ”€chacha20poly1305 x 24,134 ops/sec @ 41ฮผs/op
โ”œโ”€xchacha20poly1305 x 23,520 ops/sec @ 42ฮผs/op
โ”œโ”€aes-256-gcm x 8,420 ops/sec @ 118ฮผs/op
โ””โ”€aes-256-gcm-siv x 8,126 ops/sec @ 123ฮผs/op
encrypt (1MB)
โ”œโ”€xsalsa20poly1305 x 195 ops/sec @ 5ms/op
โ”œโ”€chacha20poly1305 x 199 ops/sec @ 5ms/op
โ”œโ”€xchacha20poly1305 x 198 ops/sec @ 5ms/op
โ”œโ”€aes-256-gcm x 76 ops/sec @ 13ms/op
โ””โ”€aes-256-gcm-siv x 78 ops/sec @ 12ms/op

Unauthenticated encryption:

encrypt (64B)
โ”œโ”€salsa x 1,287,001 ops/sec @ 777ns/op
โ”œโ”€chacha x 1,555,209 ops/sec @ 643ns/op
โ”œโ”€xsalsa x 938,086 ops/sec @ 1ฮผs/op
โ””โ”€xchacha x 920,810 ops/sec @ 1ฮผs/op
encrypt (1KB)
โ”œโ”€salsa x 353,107 ops/sec @ 2ฮผs/op
โ”œโ”€chacha x 377,216 ops/sec @ 2ฮผs/op
โ”œโ”€xsalsa x 331,674 ops/sec @ 3ฮผs/op
โ””โ”€xchacha x 336,247 ops/sec @ 2ฮผs/op
encrypt (8KB)
โ”œโ”€salsa x 57,084 ops/sec @ 17ฮผs/op
โ”œโ”€chacha x 59,520 ops/sec @ 16ฮผs/op
โ”œโ”€xsalsa x 57,097 ops/sec @ 17ฮผs/op
โ””โ”€xchacha x 58,278 ops/sec @ 17ฮผs/op
encrypt (1MB)
โ”œโ”€salsa x 479 ops/sec @ 2ms/op
โ”œโ”€chacha x 491 ops/sec @ 2ms/op
โ”œโ”€xsalsa x 483 ops/sec @ 2ms/op
โ””โ”€xchacha x 492 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 [email protected]

See LICENSE file.

noble-ciphers's People

Contributors

antonioconselheiro avatar kigawas avatar mirceanis avatar ocavue avatar paulmillr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

noble-ciphers's Issues

Add more algorithms: ascon, aegis, eme, rijndael-256, aes-kw

  1. Ascon won NIST lightweight cryptography contest 1
  2. Aegis is AES-based cipher present in linux, zig, libsodium 2
  3. EME (ECB-Mix-ECB or, clearer, Encrypt-Mix-Encrypt) is a wide-block encryption mode developed by Halevi and Rogaway in 2003 eme. The reference link is also from an implementation in Go.
    • It's parallelizable. And it's used in rclone for the crypt backend. A personal reason is that I'm porting rclone to Web/Deno so having a professional implementation would be much better than my own.
    • Seems to be abandoned
  4. Rijndael-256. The confidentiality of AES-GCM is far below 128-bit security 3. Confidentiality advantage for an attacker is < $\sigma^2/2^{129}$ where $\sigma$ is the number of encrypted 128-bit chunks. Rijndael to AES is what keccak is to SHA3: previous, unstandardized version. The idea is to support 256-bit blocks instead of 128-bit blocks of AES.
  5. AES Key Wrap (rfc3394)[https://datatracker.ietf.org/doc/html/rfc3394#section-2.2.1]

It's unclear if any of these algorithms are actually worth implementing in noble.

Footnotes

  1. https://csrc.nist.gov/News/2023/lightweight-cryptography-nist-selects-ascon โ†ฉ

  2. https://doc.libsodium.org/secret-key_cryptography/aead/aegis-256 โ†ฉ

  3. https://csrc.nist.gov/csrc/media/Presentations/2023/proposal-for-standardization-of-encryption-schemes/images-media/sess-4-mattsson-bcm-workshop-2023.pdf โ†ฉ โ†ฉ2

[Feature Request] export sigma32_32 from _arx.ts or simply the constant 'expand 32-byte k' that it's derived from

Ok hear me out,

I know you mention that you don't provide NaCl crypto_box, understandable since crypto_box / scalarMult is deprecated, but I'm currently working with interoperability with a codebase that is pretty set in stone for the time being.

That codebase uses plain old NaCl crypto_box/sealedbox for it's e2ee, which uses hsalsa20 to derive the shared secret; I noticed you implemented hsalsa so deriving this myself was pretty easy, but hardcoding the sigma constant left a nasty taste in my mouth as I'm sure you'd understand.

I'm currently using your (top-notch btw) noble packages for a bunch of cryptographic operations and a single source of truth given that they've already been audited (bar the ciphers package if I'm correct), and I'm not looking to bloat the repo with an unnecessary implementation of hsalsa20 when it's easily doable myselft and ill-advised. BUT, you'd be doing my ocd a favour with just one little export ๐Ÿ˜†

If you or anybody stumbling onto this is interested here's how I implemented hsalsa20 using what your suite exports:

/**
   * Returns hashed x25519 sharedSecret / scalarMult for use with encryption methods
   * and interoperability with NaCl box construct
   * Implements crypto_box_beforenm
   * @param {Uint8Array} sharedSecret x25519 sharedSecret / scalarMult
   * @returns {Uint8Array} hashed shared secret using hsalsa20
   */
  getHsalsa20SharedSecret(sharedSecret: Uint8Array): Uint8Array {
    // todo: pull-request to get sigma32_32 or const exported from @noble/ciphers - for now it's safe to hard code
    const sigma = u32(utf8ToBytes('expand 32-byte k'));
    const hashedSharedSecret = new Uint32Array(8);
    const i = new Uint32Array(16);
    hsalsa(sigma, u32(sharedSecret), i, hashedSharedSecret);

    return u8(hashedSharedSecret);
  }

For the record this is purely for the sake of interoperability. And using that snippet I can just plug in the shared secret from x25519.getSharedSecret() and enable communicating with any system still using NaCl boxes and not using a custom HKDF without a hitch.

Please don't ask my why hardcoding 'expand 32-byte k' is such a gripe for me ๐Ÿ˜…. But a simple export of the constant or sigma32_32 would be a lifesaver.

Happy to put in a quick pull request if either of those options is ok with you? If not it's not the end of the world and I'd totally understand. Just let me know your thoughts!

Also, a little aside here.... have you ever though about implementing libsodium's secretstream?

One of xchacha20-poly1305's shortcomings (whilst also a benefit) is that it is a stream cipher, so when it comes to large files... browser memory becomes an issue. I'm currently looking for a way to encrypt / decrypt in chunks (as secretstream does) and pipe it to a writable stream (for encrypting & uploading on-the-fly), or (currently only supported by Chrome it seems with their FileSystemAPI) download and decrypt in chunks directly to the browsers download queue; just like a regular download, but without holding an entire file in memory and having to decrypt the whole thing and potentially wreaking all kinds of havoc... Currently just using WebCryptoAPI AES-GCM for file encryption, which works fine in most cases, but as files get larger so does the memory requirements; had to limit mobile devices max file size already.

I did start working with libsodium.js specifically for that method but the ugly emscripten build was waaaay too big for production and not at all tree-shakeable.

Anyways, if it's something that you'd be interested in, just let me know, I'd be more than happy discuss and to contribute! Looks like your noble suite has all the right primitives in place already to implement such a cool feature - even if not specifically in this package (I totally understand unwanted bloat). Just let me know what you think ๐Ÿ™๐Ÿฝ

AES-GCM integration with webcrypto brakes when optional param not sent

When a cipher is generated with AES-GCM using integration with webcrypto, the third parameter of the gcm function, ADD, is optional, however undefined is not accepted by the webcrypto API and error is launched. It didn't happen in nodejs, only in a browser environment.

See the reproduction of the bug:
webcrypto-aes-gcm-bug

My user agent:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36

But you can reproduce by yourself in the following repository:
https://antonioconselheiro.github.io/noble-cipher-bug-in-webcrypto-aes-gcm-integration/

I've opened a PR with the fix:
#34

invalid tag error cannot be caught?

updated from 0.1.4 to 0.4.0 now i keep getting invalid tag error - has something changed here? what should I look for?

Error: invalid tag
at Object.decrypt (node_modules/@noble/ciphers/src/chacha.ts:266:48)

Provide a wrapper function that would prepend nonce to ciphertext

Nonce management is messy, and could be simplified:

import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { utf8ToBytes } from '@noble/ciphers/utils';
import { randomBytes, usePrependedRandomNonce } from '@noble/ciphers/webcrypto/utils';

const key = randomBytes(32);
const data = utf8ToBytes('hello, noble'); // strings must be converted to Uint8Array
const stream_x = usePrependedRandomNonce(xchacha20poly1305)(key);
const ciphertext = stream_x.encrypt(data);
const plaintext = stream_x.decrypt(ciphertext);

It would auto-generate nonce from csprng with proper byte length per-cipher.

The nonce would be prepended to ciphertext then.

Implement AES in pure JS

That would be a good alternative to native crypto.subtle methods which are not supported in RN and could provide a fine low-level primitive for stuff like SIV

Understand what AAD api should look like

a. current, aad in constructor:

chacha20poly1305(key, nonce).encrypt(plaintext)
chacha20poly1305(key, nonce).encrypt(plaintext, dst)
chacha20poly1305(key, nonce, aad).encrypt(plaintext)
chacha20poly1305(key, nonce, aad).encrypt(plaintext, dst)

if we move aad out of constructor, we have two options:

b. aad as second argument

chacha20poly1305(key, nonce).encrypt(plaintext)
chacha20poly1305(key, nonce).encrypt(plaintext, undefined, dst)
chacha20poly1305(key, nonce).encrypt(plaintext, aad)
chacha20poly1305(key, nonce).encrypt(plaintext, aad, dst)

c. aad as third argument

chacha20poly1305(key, nonce).encrypt(plaintext)
chacha20poly1305(key, nonce).encrypt(plaintext, dst)
chacha20poly1305(key, nonce).encrypt(plaintext, undefined, aad)
chacha20poly1305(key, nonce).encrypt(plaintext, dst, aad)

The question is whether to keep a, or to switch to b / c

`isPlainObject` returns `false` incorrectly

The isPlainObject function returns false when it's running in a NextJS API function. Here is a minimal reproduction:

  1. https://stackblitz.com/github/issueset/noble-nextjs-error?file=lib%2Frun.js
  2. Append /api/hello in the internal browser window
  3. Get the following error: โจฏ node_modules/@noble/ciphers/esm/utils.js (143:14) @ checkOpts โจฏ options must be object or undefined
image

Although I don't know why isPlainObject doesn't work in this case, it seems that a popular NPM package is better than a hand-written version for this feature. Maybe we can use https://www.npmjs.com/package/is-plain-object or https://www.npmjs.com/package/is-plain-obj.

Allow replacing crypto.subtle for AES wrappers

The README says that this library supports React Native:

We support all major platforms and runtimes. For Deno, ensure to use npm specifier. For React Native, you may need a polyfill for crypto.getRandomValues.

However, seeing as how the code of this library throws when no crypto.subtle and React Native does not have crypto.subtle, this library does not have RN compat right now.

To support RN package authors have to support node crypto, similar to the other @noble packages which still support node crypto.

Consider implementing Rijndael-256

The confidentiality of AES-GCM is far below 128-bit security 1.

Confidentiality advantage for an attacker is < $\sigma^2/2^{129}$ where $\sigma$ is the number of encrypted 128-bit chunks.

Rijndael to AES is what keccak is to SHA3: previous, unstandardized version. The idea is to support 256-bit blocks instead of 128-bit blocks of AES.

Footnotes

  1. https://csrc.nist.gov/csrc/media/Presentations/2023/proposal-for-standardization-of-encryption-schemes/images-media/sess-4-mattsson-bcm-workshop-2023.pdf โ†ฉ

Utils functions for base64 conversion

Sometimes it makes sense to store keys / cypher in base64 format instead of hex, it would be nice to have utility function for that. The same way as there are hex conversion functions (hexToBytes and bytesToHex in @noble/ciphers/utils).

This is useful especially if we want to store encrypted data in database since base64 string is shorter.

Consider adding EME

EME (ECB-Mix-ECB or, clearer, Encrypt-Mix-Encrypt) is a wide-block encryption mode developed by Halevi and Rogaway in 2003 eme

The reference link is also from an implementation in Go.

Reasons to add it: it's parallelizable. And it's used in rclone for the crypt backend. A personal reason is that I'm porting rclone to Web/Deno so having a professional implementation would be much better than my own.

Reasons not to add it: seems to be abandoned.

SIV Doesnt work on Node, because of overwrite of slice Function in Node Buffer Impl

Hi, as the Node Buffer Implenetation .slice doesnt copy the array but just returns a new reference to same memory space the SIV decrypt breaks, when you give a Node Buffer to the decrypt func, as the tag(last byte and the first with the counter) will be altered by the decryption logic, and the the byte compare fails. It can be solved by using the Uint8 Array Function directly in the aes logic on line 46 as Uint8Array.prototype.slice.call(tag). If you want i could open a PR for fix in the next Days

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.