ChaCha20 Poly1305 Authenticated Cipher
The `chacha` package provides an implementation of the ChaCha20 Poly1305 authenticated encryption algorithm, designed to be compatible with Node.js's `crypto.createCipheriv()` and `createDecipheriv()` API for AES-GCM mode. It uses the more recent IETF draft for ChaCha20-Poly1305 AEAD, which features a 96-bit nonce, distinct from earlier drafts implemented in systems like BoringSSL (though it offers a 'Legacy Aead' for compatibility). The library supports both a pure JavaScript implementation and optional native bindings for performance in Node.js environments, automatically falling back to pure JS where native bindings are unavailable or explicitly opted out. It exposes APIs for the full AEAD, the ChaCha20 stream cipher, and the Poly1305 message authentication code independently. The current stable version is 2.1.0, and while a specific release cadence isn't published, the active GitHub repository and continuous integration suggest ongoing maintenance and stability.
Common errors
-
Error: Mac mismatch
cause The authentication tag provided to `decipher.setAuthTag()` does not match the original tag generated during encryption, or no tag was set.fixVerify that the correct authentication tag (obtained from `cipher.getAuthTag()`) is being passed to `decipher.setAuthTag()`. Ensure `setAuthTag` is called before any `update()` operations during decryption. -
TypeError: decipher.setAAD is not a function
cause Attempting to call `setAAD()` or `setAuthTag()` after data has already been passed to `update()` or `final()`.fixEnsure that `cipher.setAAD()` and `decipher.setAAD()` (and `decipher.setAuthTag()`) are called immediately after creating the cipher/decipher instance and before any `update()` or `final()` calls. -
ReferenceError: chacha is not defined (or similar import error in ESM)
cause Attempting to use ES module `import chacha from 'chacha'` syntax in a CommonJS module context, or vice-versa.fixIf in a CommonJS module, use `const chacha = require('chacha');`. If in an ES module, use `import * as chacha from 'chacha';` and then access methods via the `chacha` object (e.g., `chacha.createCipher`). -
Error: key must be 32 bytes (or similar length mismatch for key/nonce)
cause The provided key or nonce `Buffer` does not match the required length (32 bytes for key, 12 bytes for nonce).fixEnsure the key is a 32-byte Buffer (256 bits) and the nonce is a 12-byte Buffer (96 bits). Generate them securely using a cryptographically strong random number generator.
Warnings
- breaking The ChaCha20-Poly1305 AEAD standard has evolved. This library primarily implements the more recent IETF draft (longer nonce, shorter counter, specific tag generation). Older implementations, like those in BoringSSL, followed an earlier draft. Mixing versions will lead to authentication failures.
- gotcha The `setAAD(data)` method on cipher/decipher objects MUST be called before any calls to `update()` or `final()`. Calling it out of order will result in incorrect authentication or decryption errors.
- gotcha The `getAuthTag()` method on a cipher object MUST be called only AFTER all plaintext data has been processed (i.e., after the final `update()` call and `final()`). Calling it prematurely will result in an incomplete or incorrect tag.
- gotcha When decrypting, `decipher.setAuthTag(tag)` MUST be called before any calls to `update()`. If the tag is not set, or if it does not match the tag generated during encryption, the decryption process will throw an 'Error: Mac mismatch' or similar authentication failure.
- gotcha In Node.js, the package attempts to use native bindings for performance by default. If you need to force the pure JavaScript implementation (e.g., for cross-platform consistency or debugging), the standard `require('chacha')` will not suffice.
Install
-
npm install chacha -
yarn add chacha -
pnpm add chacha
Imports
- chacha
import chacha from 'chacha';
const chacha = require('chacha'); - createCipher
import { createCipher } from 'chacha';const { createCipher } = require('chacha'); - Pure JS Fallback
import chachaPureJs from 'chacha/browser';
const chachaPureJs = require('chacha/browser');
Quickstart
const chacha = require('chacha');
const { Buffer } = require('buffer');
// --- Configuration ---
// Keys and nonces must be Buffers of specific lengths
const key = Buffer.from('0123456789abcdef0123456789abcdef01234567', 'hex'); // 256-bit (32 bytes)
const nonce = Buffer.from('fedcba9876543210', 'hex'); // 96-bit (12 bytes)
const associatedData = Buffer.from('Example AAD for authentication', 'utf8');
const plaintext = Buffer.from('This is a highly confidential message that needs secure encryption!', 'utf8');
console.log('Original plaintext:', plaintext.toString('utf8'));
// --- Encryption ---
const cipher = chacha.createCipher(key, nonce);
// Set Additional Authenticated Data (AAD) BEFORE processing any data
cipher.setAAD(associatedData);
// Encrypt the plaintext in parts or all at once
const ciphertextChunks = [];
ciphertextChunks.push(cipher.update(plaintext.subarray(0, plaintext.length / 2)));
ciphertextChunks.push(cipher.update(plaintext.subarray(plaintext.length / 2)));
ciphertextChunks.push(cipher.final());
const ciphertext = Buffer.concat(ciphertextChunks);
// Get the authentication tag AFTER all data has been processed (after final())
const tag = cipher.getAuthTag();
console.log('\nEncrypted Ciphertext (hex):', ciphertext.toString('hex'));
console.log('Authentication Tag (hex):', tag.toString('hex'));
// --- Decryption ---
const decipher = chacha.createDecipher(key, nonce);
// Set AAD BEFORE processing any data (must match encryption AAD)
decipher.setAAD(associatedData);
// Set the authentication tag BEFORE processing any data (must match encryption tag)
decipher.setAuthTag(tag);
// Decrypt the ciphertext
let decryptedPlaintext;
try {
const decryptedChunks = [];
decryptedChunks.push(decipher.update(ciphertext.subarray(0, ciphertext.length / 2)));
decryptedChunks.push(decipher.update(ciphertext.subarray(ciphertext.length / 2)));
decryptedChunks.push(decipher.final());
decryptedPlaintext = Buffer.concat(decryptedChunks);
console.log('\nDecrypted Plaintext:', decryptedPlaintext.toString('utf8'));
console.log('Decryption successful:', decryptedPlaintext.equals(plaintext));
} catch (e) {
console.error('\nDecryption failed: The authentication tag or AAD did not match!', e.message);
}