noble-ciphers

repository·main·Indexed 19 days ago

https://github.com/paulmillr/noble-ciphers

An audited, minimal, and high-performance JavaScript implementation of cryptographic ciphers including AES, ChaCha, and Salsa20. Designed to be tree-shakeable for modern web and Node.js environments, it supports various modes such as GCM, GCM-SIV, XTS, and XChaCha20-Poly1305. The library includes utilities for managed nonces, WebCrypto integration, and CSPRNGs.

Tokens
6.2K
Snippets
19
Records
28
Agent score
63%

What's inside @noble/ciphers

  1. Security and Audit Information

    main

    The noble-ciphers library has undergone multiple audits to ensure security:

    • Version 2.2.0 (Apr 2026): Self-audited by the maintainers. Scope: everything.
    • Version 1.0.0 (Sep 2024): Independently audited by cure53. Scope: everything.

    Security Considerations

    • Constant-timeness: The library targets algorithmic constant time. However, due to the nature of JavaScript (JIT compilers and Garbage Collection), absolute constant-time resistance is difficult to achieve in a scripting language. For absolute security requirements, use low-level languages/libraries instead of JS.
    • AES Implementation: The library uses T-tables for AES to maintain performance, which can leak access timings. This is a common trade-off also seen in OpenSSL and Go.
    • Randomness: The library relies on the built-in crypto.getRandomValues (CSPRNG).
    • Quantum Resistance: To protect against Grover's algorithm, it is recommended to use AES-256 instead of AES-128. Salsa and ChaCha are considered safe.
  2. Automatic nonce handling with managedNonce

    main

    The managedNonce utility wraps a cipher to handle nonces automatically.

    • Encryption: It fetches a nonce of the required length from a CSPRNG and prepends it to the ciphertext.
    • Decryption: It treats the first nonceBytes of the ciphertext as the nonce.

    Warning: AES-GCM and ChaCha (but NOT XChaCha) have limits on the number of messages that can be encrypted under the same key when using this method.

    import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
    import { hexToBytes, managedNonce } from '@noble/ciphers/utils.js';
    
    const key = hexToBytes('fa686bfdffd3758f6377abbc23bf3d9bdc1a0dda4a6e7f8dbdd579fa1ff6d7e1');
    const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
    const data = new TextEncoder().encode('hello noble');
    
    const ciphertext = chacha.encrypt(data);
    const data_ = chacha.decrypt(ciphertext);
  3. Understand nonce collision risks and strategies

    main

    A nonce (Initialization Vector) must be unique for every encryption performed with the same key. If a (key, nonce) pair is repeated, the encryption can be broken.

    Strategies for Nonce Uniqueness

    1. Counters: Increment a value (e.g., 0, 1, 2...) for each message. This is safe for chacha20 and salsa20 but difficult in decentralized or unsynchronized systems where state cannot be easily stored.
    2. Random Nonces: Generate a random value for each encryption.
      • Risk: Small nonces (like the 96-bit/12-byte nonces used in AES-GCM and ChaCha20) have a high collision probability. For example, using random 12-byte nonces with AES-GCM limits you to approximately 2**23 (8 million) messages before the chance of a collision reaches a significant level.
      • Solution: Use XChaCha or XSalsa20, which use 192-bit nonces, making random generation safe.

    Summary of Nonce Safety

    Cipher TypeNonce StrategyRecommendation
    ChaCha20 / AES-GCMCounterSafe
    ChaCha20 / AES-GCMRandomRisky (Collision risk)
    XChaCha / XSalsa20RandomSafe
    AES-GCM-SIVAnySafe (Misuse resistant)
  4. Run benchmarks for performance analysis

    main

    To measure the performance of the implemented ciphers on your machine, run the benchmark script via npm. Note that benchmarks provided in the documentation were measured on an Apple M4 chip.

    npm run benchmark
  5. How to encrypt data properly

    main

    To ensure cryptographic security when using noble-ciphers, follow these best practices:

    1. Key Management

    • Entropy: Use unpredictable keys with sufficient entropy. Keys must be generated using a Cryptographically Secure Random Number Generator (CSPRNG), not Math.random().
    • Derivation: Non-random keys generated via a Key Derivation Function (KDF) are acceptable.
    • Isolation: Do not re-use keys across different protocols (e.g., do not use an ECDH key directly in AES). Use hkdf or a hash function to derive sub-keys for specific purposes.

    2. Nonce Management

    • Uniqueness: Use a new nonce for every encryption operation with the same key. Repeating a (key, nonce) pair allows attackers to decrypt data.
    • Counter vs. Random:
      • Counters: Safe for chacha20 and salsa20 if they never repeat (e.g., 01, 02...).
      • Random Nonces: Use xchacha or xsalsa20 for random nonces, as their 192-bit nonce length minimizes collision risks. Avoid using random 96-bit (12-byte) nonces with AES-GCM or ChaCha20 for large numbers of messages due to collision probability.

    3. Use Authenticated Encryption (AEAD)

    • Recommended: chacha20poly1305, GCM, GCM-SIV, ChaCha+HMAC, CTR+HMAC, CBC+HMAC.
    • Avoid: chacha20, raw CTR, or raw CBC, as these do not detect ciphertext tampering.
  6. Upgrade from v1 to v2

    main

    When upgrading from @noble/ciphers v1 to v2, be aware of the following breaking changes:

    • ESM-only: The package is now ESM-only. On Node.js v20.19+, ESM can be loaded from CommonJS.
    • Module Imports: You must use the .js extension for all module imports.
      • Old: @noble/ciphers/aes
      • New: @noble/ciphers/aes.js
    • Webcrypto Utilities: randomBytes and managedNonce have moved to utils.js.
    • Strict Inputs: ghash, poly1305, and polyval now only allow Uint8Array as hash inputs; string inputs are prohibited.
    • Utility Changes:
      • New: abytes
      • Removed: ahash, toBytes
    • Module Removals:
      • _assert (now use utils)
      • _micro (removed)
      • crypto (now use webcrypto)
    • TypeScript: The compilation target has been bumped from es2020 to es2022.
  7. Importing modules from @noble/ciphers

    main

    To ensure small bundle sizes via tree-shaking, do not use import * from '@noble/ciphers'. Instead, use sub-imports to pull in only the specific ciphers and utilities you need.

    Common sub-import paths include:

    • @noble/ciphers/aes.js for AES modes (GCM, CTR, etc.)
    • @noble/ciphers/chacha.js for ChaCha and XChaCha
    • @noble/ciphers/salsa.js for Salsa20
    • @noble/ciphers/ff1.js for format-preserving encryption
    • @noble/ciphers/utils.js for helper functions like randomBytes and bytesToHex.
    import { gcm, gcmsiv } from '@noble/ciphers/aes.js';
    import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha.js';
    import { xsalsa20poly1305 } from '@noble/ciphers/salsa.js';
    import { bytesToHex, hexToBytes, managedNonce, randomBytes } from '@noble/ciphers/utils.js';
  8. Install @noble/ciphers

    main

    You can install the library using npm or add it via JSR for Deno.

    Note for React Native users: You may need to install a polyfill for getRandomValues, such as react-native-get-random-values.

    npm install @noble/ciphers
    # or
    deno add jsr:@noble/ciphers
  9. Pick the right cipher for your use case

    main

    When choosing a cipher in noble-ciphers, consider the following recommendations based on your requirements:

    • For speed and random nonces: Use XChaCha20-Poly1305. It is highly performant and its extended nonce length makes it safe to use with randomly generated nonces.
    • For nonce-misuse resistance: Use AES-GCM-SIV. This is ideal if you are concerned about accidentally repeating a (key, nonce) pair, as it provides resistance against such errors.
    • As a fallback: Use AES-GCM if the above options are unavailable.

    Always prefer Authenticated Encryption with Associated Data (AEAD) modes (like chacha20poly1305, GCM, or GCM-SIV) over unauthenticated modes (like raw chacha20, CTR, or CBC) to prevent bit-flipping and ciphertext substitution attacks.

  10. Developer workflow: Build, Test, and Lint

    main

    If you are contributing to or developing with the library, use the following commands:

    • Build and Test: npm install && npm run build && npm test
    • Linting: npm run check or npm run format
    • Benchmarking: npm run benchmark
    • Bundling: npm run bundle (builds a single file)
    npm install && npm run build && npm test
  11. Derive an encryption key from a password

    main

    Never convert a password directly into a Uint8Array for use as a key. Instead, use a Key Derivation Function (KDF) like scrypt (from @noble/hashes) to stretch the password into a high-entropy key. Always use a salt (an application-specific secret) during this process.

    import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
    import { managedNonce } from '@noble/ciphers/utils.js';
    import { scrypt } from '@noble/hashes/scrypt.js';
    
    const PASSWORD = 'correct-horse-battery-staple';
    const APP_SPECIFIC_SECRET = 'salt-12345678-secret';
    const SECURITY_LEVEL = 2 ** 20; // requires 1GB of RAM to calculate
    
    // Convert password into 32-byte key using scrypt
    const key = scrypt(PASSWORD, APP_SPECIFIC_SECRET, {
      N: SECURITY_LEVEL,
      r: 8,
      p: 1,
      dkLen: 32,
      maxmem: 2 ** 30 + 4096,
    });
    
    // Use random, managed nonce
    const chacha = managedNonce(xchacha20poly1305)(key);
    const data = new TextEncoder().encode('hello noble');
    const ciphertext = chacha.encrypt(data);
    const data_ = chacha.decrypt(ciphertext);
  12. Reuse arrays for input and output to avoid allocations

    main

    To optimize performance and avoid extra memory allocations, you can pass existing Uint8Array buffers to encrypt and decrypt.

    Note: Some ciphers may not support unaligned Uint8Arrays (where byteOffset % 4 !== 0) as destination buffers, which can negate performance benefits.

    import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
    import { randomBytes } from '@noble/ciphers/utils.js';
    
    const key = randomBytes(32);
    const nonce = randomBytes(12);
    const chacha = chacha20poly1305(key, nonce);
    
    const input = new TextEncoder().encode('hello noble');
    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
    
    // Encrypt into `buf` using `start` as input
    chacha.encrypt(start, buf); 
    // Decrypt from `buf` into `start`
    chacha.decrypt(buf, start);