Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Audited & minimal JS implementation of Salsa20, ChaCha and AES

License

NotificationsYou must be signed in to change notification settings

paulmillr/noble-ciphers

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.

This library belongs tonoble cryptography

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

Usage

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

Examples

Note

Use different nonce every timeencrypt() is done.

XChaCha20-Poly1305 encryption

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

AES-256-GCM encryption

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

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

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

Friendly WebCrypto AES

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

AESKW and AESKWP

import{aeskw,aeskwp}from'@noble/ciphers/aes';import{hexToBytes}from'@noble/ciphers/utils';constkek=hexToBytes('000102030405060708090A0B0C0D0E0F');constkeyData=hexToBytes('00112233445566778899AABBCCDDEEFF');constciphertext=aeskw(kek).encrypt(keyData);

Auto-handle nonces

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

Reuse array for input and output

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.

Internals

Implemented primitives

  • 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.
    • XChaCha20extended-nonce variant is also provided. Similar to XSalsa, it's safe to use withrandomly-generated nonces.
    • Check outPDF andwiki.
  • 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.

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

  • Use unpredictable key with enough entropy
    • Random key must be using cryptographically secure random number generator (CSPRNG), notMath.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
  • 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
  • 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

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-noncealgorithms, key rotation is advised. hkdf would work great for this case.

Encryption limits

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
  • 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. Meaning2**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 (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 is2**32 (4B) msgs
  • MAC can be forged: see Poly1305 section above. Same for SIV

Security

The library has been independently audited:

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.

Constant-timeness

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.

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 verifyprovenance logs
    • Use GitHub CLI to verify single-file builds:gh attestation verify --owner paulmillr noble-ciphers.js
  • 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 withnpm-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

Randomness

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.

Speed

To summarize, noble is the fastest JS implementation of Salsa, ChaCha and AES.

You can gain additional speed-up andavoid memory allocations by passingoutputuint8array 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

Contributing & testing

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

License

The MIT License (MIT)

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

See LICENSE file.


[8]ページ先頭

©2009-2025 Movatter.jp