- Notifications
You must be signed in to change notification settings - Fork11
Audited & minimal JS implementation of Salsa20, ChaCha and AES
License
paulmillr/noble-ciphers
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Audited & minimal JS implementation of Salsa20, ChaCha and AES.
- 🔒Audited by an independent security firm
- 🔻 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), AESKW, AESKWP
- 💃 Salsa20, ChaCha, XSalsa20, XChaCha, ChaCha8, ChaCha12, Poly1305
- 🥈 Two AES implementations: pure JS or friendly wrapper around webcrypto
- 🪶 29KB (11KB gzipped) for everything, 7KB (3KB gzipped) for ChaCha build
Take a glance atGitHub Discussions for questions and support.
noble cryptography — high-security, easily auditable set of contained cryptographic libraries and tools.
- Zero or minimal dependencies
- Highly readable TypeScript / JS code
- PGP-signed releases and transparent NPM builds
- All libraries:ciphers,curves,hashes,post-quantum,4kbsecp256k1 /ed25519
- Check out homepagefor reading resources, documentation and apps built with noble
npm install @noble/ciphers
deno add jsr:@noble/ciphers
deno doc jsr:@noble/ciphers
# command-line documentation
We support all major platforms and runtimes.For React Native, you may need apolyfill for getRandomValues.A standalone filenoble-ciphers.js is also available.
// import * from '@noble/ciphers'; // Error: use sub-imports, to ensure small app sizeimport{gcm,siv}from'@noble/ciphers/aes';import{chacha20poly1305,xchacha20poly1305}from'@noble/ciphers/chacha';import{xsalsa20poly1305,secretbox}from'@noble/ciphers/salsa';// Unauthenticated encryption: make sure to use HMAC or similarimport{ctr,cfb,cbc,ecb}from'@noble/ciphers/aes';import{salsa20,xsalsa20}from'@noble/ciphers/salsa';import{chacha20,xchacha20,chacha8,chacha12}from'@noble/ciphers/chacha';import{aeskw,aeskwp}from'@noble/ciphers/aes';// KWimport{bytesToHex,hexToBytes,bytesToUtf8,utf8ToBytes}from'@noble/ciphers/utils';import{managedNonce,randomBytes}from'@noble/ciphers/webcrypto';
Note
Use different nonce every timeencrypt()
is done.
import{xchacha20poly1305}from'@noble/ciphers/chacha';import{utf8ToBytes}from'@noble/ciphers/utils';import{randomBytes}from'@noble/ciphers/webcrypto';constkey=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');constnonce=randomBytes(24);constchacha=xchacha20poly1305(key,nonce);constdata=utf8ToBytes('hello, noble');constciphertext=chacha.encrypt(data);constdata_=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';constkey=randomBytes(32);constnonce=randomBytes(24);constdata=utf8ToBytes('hello, noble');constaes=gcm(key,nonce);constciphertext=aes.encrypt(data);constdata_=aes.decrypt(ciphertext);// utils.bytesToUtf8(data_) === data
import{gcm,siv,ctr,cfb,cbc,ecb}from'@noble/ciphers/aes';import{randomBytes}from'@noble/ciphers/webcrypto';constplaintext=newUint8Array(32).fill(16);for(letcipherof[gcm,siv]){constkey=randomBytes(32);// 24 for AES-192, 16 for AES-128constnonce=randomBytes(12);constciphertext_=cipher(key,nonce).encrypt(plaintext);constplaintext_=cipher(key,nonce).decrypt(ciphertext_);}for(constcipherof[ctr,cbc,cfb]){constkey=randomBytes(32);// 24 for AES-192, 16 for AES-128constnonce=randomBytes(16);constciphertext_=cipher(key,nonce).encrypt(plaintext);constplaintext_=cipher(key,nonce).decrypt(ciphertext_);}for(constcipherof[ecb]){constkey=randomBytes(32);// 24 for AES-192, 16 for AES-128constciphertext_=cipher(key).encrypt(plaintext);constplaintext_=cipher(key).decrypt(ciphertext_);}
Noble implements AES. Sometimes people want to use built-incrypto.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';constplaintext=newUint8Array(32).fill(16);constkey=randomBytes(32);for(constcipherof[gcm]){constnonce=randomBytes(12);constciphertext_=awaitcipher(key,nonce).encrypt(plaintext);constplaintext_=awaitcipher(key,nonce).decrypt(ciphertext_);}for(constcipherof[ctr,cbc]){constnonce=randomBytes(16);constciphertext_=awaitcipher(key,nonce).encrypt(plaintext);constplaintext_=awaitcipher(key,nonce).decrypt(ciphertext_);}
import{aeskw,aeskwp}from'@noble/ciphers/aes';import{hexToBytes}from'@noble/ciphers/utils';constkek=hexToBytes('000102030405060708090A0B0C0D0E0F');constkeyData=hexToBytes('00112233445566778899AABBCCDDEEFF');constciphertext=aeskw(kek).encrypt(keyData);
We provide API that manages nonce internally instead of exposing them to library's user.
Forencrypt
, anonceBytes
-length buffer is fetched from CSPRNG and prenended to encrypted ciphertext.
Fordecrypt
, firstnonceBytes
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';constkey=hexToBytes('fa686bfdffd3758f6377abbc23bf3d9bdc1a0dda4a6e7f8dbdd579fa1ff6d7e1');constchacha=managedNonce(xchacha20poly1305)(key);// manages nonces for youconstdata=utf8ToBytes('hello, noble');constciphertext=chacha.encrypt(data);constdata_=chacha.decrypt(ciphertext);
To avoid additional allocations, Uint8Array can be reusedbetween encryption and decryption calls.
Note
Some ciphers don't support unaligned (byteOffset % 4 !== 0
) Uint8Array asdestination. 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';constkey=randomBytes(32);constnonce=randomBytes(12);constchacha=chacha20poly1305(key,nonce);constinput=utf8ToBytes('hello, noble');// length == 12constinputLength=input.length;consttagLength=16;constbuf=newUint8Array(inputLength+tagLength);conststart=buf.subarray(0,inputLength);start.set(input);// copy input to bufchacha.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.
- Salsa20 stream cipher, 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-noncevariant 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-boxauthenticated encryption. Secretbox is just xsalsa20-poly1305. We provide thealias and corresponding seal / open methods. We don't provide "box" or "sealedbox".
- Check outPDF andwiki.
- ChaCha20 stream cipher, releasedin 2008. Developed after Salsa20, ChaCha aims to increase diffusion per round.It was standardized inRFC 8439and is now used in TLS 1.3.
- AESis 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-SIVnonce-misuse-resistant mode is also provided. It's recommended to use it,to prevent catastrophic consequences of nonce reuse. Our implementation of SIVhas the same speed as GCM: there is no performance hit.
- We also have AESKW and AESKWP fromRFC 3394 /RFC 5649
- Check outAES internals and block modes.
- We expose polynomial-evaluation MACs:Poly1305, AES-GCM'sGHash andAES-SIV'sPolyval.
- Poly1305 (PDF,wiki)is a fast and parallel secret-key message-authentication code suitable fora wide variety of applications. It was standardized inRFC 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'tbe used in PAKE schemes. Seeinvisible 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 - soHKDF(key, i)
could be used instead.
- Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.See more info.
We suggest to use XChaCha20-Poly1305.If you can't use it, prefer AES-GCM-SIV, or AES-GCM.
- 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 andencryption limits
- Random key must be using cryptographically secure random number generator (CSPRNG), not
- Use new nonce every time anddon't repeat it
- chacha and salsa20 are fine for sequential counters thatnever repeat:
01, 02...
- xchacha and xsalsa20 should use random nonces instead
- AES-GCM should use 12-byte nonces: smaller nonces are security risk
- chacha and salsa20 are fine for sequential counters thatnever repeat:
- Prefer authenticated encryption (AEAD)
- chacha20poly1305 / aes-gcm / ChaCha + HMAC / AES + HMAC is good
- chacha20 / aes-ctr / aes-cbc without poly1305 or hmac 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 can be bad
- Use hkdf or, at least, a hash function to create sub-key instead
- If you need AES, only use AES-256 for new protocols
- For post-quantum security
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-noncealgorithms, key rotation is advised. hkdf would work great for this case.
A "protected message" would mean a probability of2**-50
that a passive attackersuccessfully distinguishes the ciphertext outputs of the AEAD scheme from the outputsof a random function. Seedraft-irtf-cfrg-aead-limits for details.
- Max message size:
- AES-GCM: ~68GB,
2**36-256
- Salsa, ChaCha, XSalsa, XChaCha: ~256GB,
2**38-64
- AES-GCM: ~68GB,
- 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
- AES-GCM:
- Max amount of protected messages, across all keys:
- AES-GCM:
2**69/B
where B is max blocks encrypted by a key. Meaning2**59
for 1KB,2**49
for 1MB,2**39
for 1GB - Salsa, ChaCha, XSalsa, XChaCha:
2**100
- AES-GCM:
cipher = encrypt(block, key)
. Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round does:
- S-box, table substitution
- Shift rows, cyclic shift left of all rows of data array
- Mix columns, multiplying every column by fixed polynomial
- 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 (Electronic Codebook): Deterministic encryption; identical plaintext blocks yield identical ciphertexts. Insecure due to pattern leakage.SeeAES Penguin
- CBC (Cipher Block Chaining): Each plaintext block is XORed with the previous ciphertext block before encryption.Hard to use: requires proper padding and an IV. Needs a separate MAC.
- CTR (Counter Mode): Turns a block cipher into a stream cipher using a counter and IV (nonce).Efficient and parallelizable. Requires a unique nonce per encryption. Better, but still needs a separate MAC.
- GCM (Galois/Counter Mode): Combines CTR mode with polynomial MAC. Efficient and widely used.
- SIV (Synthetic IV): Nonce-misuse-resistant mode; repeating nonces reveal only if plaintexts are identical.Maintains security even if nonces are reused.
- XTS: Designed for disk encryption.Similar to ECB (deterministic), but has
[i][j]
tweak arguments corresponding to sector i and 16-byte block (part of sector) j.Lacks MAC.
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
The library has been independently audited:
- at version 1.0.0, in Sep 2024, bycure53
- PDFs:website,in-repo
- Changes since audit
- Scope: everything
- The audit has been funded byOpenSats
It is tested against property-based, cross-library and Wycheproof vectors,and is being fuzzed inthe separate repo.
If you see anything unusual: investigate and report.
We're targetting algorithmic constant time.JIT-compiler andGarbage Collector make "constant time"extremely hard to achievetiming attack resistancein a scripting language. Which meansany other JS library can't haveconstant-timeness. Even statically typed Rust, a language without GC,makes it harder to achieve constant-timefor 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, whichleak access timings.This is also done inOpenSSL andGo stdlib for performance reasons.The analysis was mentioned inhal-04652991.
- 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 verifyprovenance logs
- Use GitHub CLI to verify single-file builds:
gh attestation verify --owner paulmillr noble-ciphers.js
- Use GitHub CLI to verify single-file builds:
- Rare releasing is followed to ensure less re-audit need for end-users
- Dependencies are minimized and locked-down: any dependency could get hacked and users will be downloading malware with every install.
- We make sure to use as few dependencies as possible
- Automatic dep updates are prevented by locking-down version ranges; diffs are checked with
npm-diff
- Dev Dependencies are disabled for end-users; they are only used to develop / build the source code
For this package, there are 0 dependencies; and a few dev dependencies:
- micro-bmark, micro-should and jsbt are used for benchmarking / testing / build tooling and developed by the same author
- prettier, fast-check and typescript are used for code quality / test generation / ts compilation. It's hard to audit their source code thoroughly and fully because of their size
We're deferring to built-incrypto.getRandomValueswhich 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 weaknessis 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 andavoid memory allocations by passingoutput
uint8array into encrypt / decrypt methods.
Benchmark results on Apple M2 with node v22:
64Bxsalsa20poly1305 x 501,756 ops/sec @ 1μs/opchacha20poly1305 x 428,082 ops/sec @ 2μs/opxchacha20poly1305 x 343,170 ops/sec @ 2μs/opaes-256-gcm x 147,492 ops/sec @ 6μs/opaes-256-gcm-siv x 122,085 ops/sec @ 8μs/op# Unauthenticated encryptionsalsa20 x 1,288,659 ops/sec @ 776ns/opxsalsa20 x 1,055,966 ops/sec @ 947ns/opchacha20 x 1,506,024 ops/sec @ 664ns/opxchacha20 x 1,064,962 ops/sec @ 939ns/opchacha8 x 1,683,501 ops/sec @ 594ns/opchacha12 x 1,628,664 ops/sec @ 614ns/opaes-ecb-256 x 775,193 ops/sec @ 1μs/opaes-cbc-256 x 738,552 ops/sec @ 1μs/opaes-ctr-256 x 737,463 ops/sec @ 1μs/op1MBxsalsa20poly1305 x 205 ops/sec @ 4ms/opchacha20poly1305 x 213 ops/sec @ 4ms/opxchacha20poly1305 x 213 ops/sec @ 4ms/opaes-256-gcm x 77 ops/sec @ 12ms/opaes-256-gcm-siv x 81 ops/sec @ 12ms/op# Unauthenticated encryptionsalsa20 x 498 ops/sec @ 2ms/opxsalsa20 x 493 ops/sec @ 2ms/opchacha20 x 506 ops/sec @ 1ms/opxchacha20 x 506 ops/sec @ 1ms/opchacha8 x 956 ops/sec @ 1ms/opchacha12 x 735 ops/sec @ 1ms/op# Wrapper over built-in webcryptowebcrypto ctr-256 x 5,068 ops/sec @ 197μs/opwebcrypto cbc-256 x 1,116 ops/sec @ 895μs/opwebcrypto 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/opchacha20poly1305 (encrypt, 1MB)├─node x 1,360 ops/sec @ 735μs/op├─stablelib x 117 ops/sec @ 8ms/op└─noble x 193 ops/sec @ 5ms/opchacha (encrypt, 1MB)├─node x 2,035 ops/sec @ 491μs/op├─stablelib x 206 ops/sec @ 4ms/op└─noble x 474 ops/sec @ 2ms/opctr-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/opcbc-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/opgcm-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
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 benchmarks, which may need their deps first (npm run bench:install
)npm run build:release
will build single file
Check outgithub.com/paulmillr/guidelinesfor general coding practices and rules.
Seepaulmillr.com/noblefor useful resources, articles, documentation and demosrelated to the library.
The MIT License (MIT)
Copyright (c) 2023 Paul Miller(https://paulmillr.com)Copyright (c) 2016 Thomas Porninpornin@bolet.org
See LICENSE file.
About
Audited & minimal JS implementation of Salsa20, ChaCha and AES