npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fast-cipher

v0.1.1

Published

A Bun, Node.js, and TypeScript implementation of the FAST format-preserving cipher

Downloads

26

Readme

FAST implementation for JavaScript

A TypeScript implementation of the FAST (Format-preserving, Additive, Symmetric Translation) cipher.

FAST is a format-preserving encryption (FPE) scheme for arbitrary radix values and fixed word lengths. This implementation encrypts data while preserving both the input length and the symbol domain, making it suitable for tokenizing or encrypting structured values such as decimal identifiers or byte-oriented records.

This implementation is intended to be fully interoperable with the other existing FAST implementations.

Installation

Install from npm:

npm install fast-cipher

With Bun:

bun add fast-cipher

This package ships as standard ESM with bundled JavaScript in dist/ and published TypeScript declarations, so the same package import works in Bun, Node.js, and TypeScript projects.

Node.js consumers should use an ESM project setup ("type": "module" in package.json or .mjs entry files). The published package target is Node.js 20 or newer.

Usage

Basic Example

import { FastCipher, calculateRecommendedParams } from "fast-cipher";

const params = calculateRecommendedParams(10, 16);
const key = new Uint8Array([
  0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
  0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
]);

const cipher = FastCipher.create(params, key);

const tweak = new Uint8Array([
  0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
]);

const plaintext = new Uint8Array([
  1, 2, 3, 4, 5, 6, 7, 8,
  9, 0, 1, 2, 3, 4, 5, 6,
]);

const ciphertext = cipher.encrypt(plaintext, tweak);
const recovered = cipher.decrypt(ciphertext, tweak);

console.log(ciphertext);
console.log(recovered);

cipher.destroy();

Input Rules

  • key must be exactly 16 bytes
  • radix must be between 4 and 256
  • wordLength must be at least 2
  • numLayers must be a positive multiple of wordLength
  • branchDist1 must be at most wordLength - 2
  • branchDist2 must be >= 1, at most wordLength - 1, and at most wordLength - branchDist1 - 1
  • plaintext and ciphertext must have length wordLength
  • every symbol in the input must be in the range [0, radix)

Tweaks

Tweaks provide domain separation. Encrypting the same plaintext with the same key and different tweaks produces different ciphertexts. The same tweak must be supplied again for decryption.

const params = calculateRecommendedParams(10, 8);
const cipher = FastCipher.create(params, key);
const plaintext = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);

const tweakA = new Uint8Array([1]);
const tweakB = new Uint8Array([2]);

const ctA = cipher.encrypt(plaintext, tweakA);
const ctB = cipher.encrypt(plaintext, tweakB);

console.log(ctA);
console.log(ctB);

API

calculateRecommendedParams(radix, wordLength, securityLevel?)

Returns a FastParams object using the FAST round tables and branch-distance rules. The returned parameters use an S-box pool size of 256 and a security level of 128 bits by default.

FastCipher.create(params, key)

Creates a cipher context, validates parameters, and derives the S-box pool from the master key.

cipher.encrypt(plaintext, tweak?)

Encrypts a Uint8Array and returns a new Uint8Array with the same length and radix domain. If tweak is omitted, the cipher uses an empty tweak.

cipher.decrypt(ciphertext, tweak?)

Decrypts a Uint8Array produced by FAST using the same parameters, key, and tweak. If tweak is omitted, the cipher uses an empty tweak.

cipher.destroy()

Zeros the stored master key material held by the cipher instance.

Errors

The package exports FastError plus the concrete error classes used for invalid inputs and parameters: InvalidBranchDistError, InvalidLengthError, InvalidParametersError, InvalidRadixError, InvalidSBoxCountError, InvalidValueError, and InvalidWordLengthError.

Token Encryption

The fast-cipher/tokens subpath exports a higher-level TokenEncryptor that scans text for known secret token formats (API keys, access tokens, etc.) and encrypts them in place using format-preserving encryption. The encrypted output has the same length, character set, and prefix as the original token.

import { TokenEncryptor } from "fast-cipher/tokens";

const key = new Uint8Array(16); // 16-byte AES key
crypto.getRandomValues(key);

const enc = new TokenEncryptor(key);

const text = "My GitHub token is ghp_ABCDEFabcdef1234567890abcdef12345678";
const encrypted = enc.encrypt(text);
const decrypted = enc.decrypt(encrypted);
// decrypted === text

enc.destroy();

Supported Token Formats

There are three kinds of built-in patterns:

Prefix-based (fully format-preserving) -- The prefix (ghp_, sk-proj-, AKIA, etc.) is preserved as-is and only the body is encrypted. The output has the same length, prefix, and character set as the input. Covers: OpenAI, Anthropic, GitHub, GitLab, AWS access keys, Stripe, Google, Twilio, npm, PyPI, Datadog, Vercel, Supabase, HuggingFace, and Grafana.

Structured (fully format-preserving) -- Tokens with internal delimiters like SendGrid (SG.<seg1>.<seg2>) and Slack (xoxb-<seg1>-<seg2>-<seg3>) encrypt each segment independently while preserving the prefix and delimiters.

Heuristic (marker-based) -- Some tokens have no fixed prefix (e.g. Fastly API tokens, AWS secret keys). These are detected using heuristics: exact length constraints, word boundary detection, Shannon entropy thresholds, and character class diversity. On encrypt, a [ENCRYPTED:<name>] marker is prepended so that decrypt() can safely identify encrypted spans without corrupting plaintext strings that happen to look token-like. The encrypted body itself is format-preserving (same length and alphabet), but the marker makes the overall output longer than the input. Heuristic patterns are active by default and can be excluded via the types filter.

Options

// Restrict to specific pattern names
const opts = { types: ["github-pat", "openai"] };
const filtered = enc.encrypt(text, opts);
// IMPORTANT: pass the same types filter to decrypt()
const restored = enc.decrypt(filtered, opts);

// Per-document tweak to break deterministic linkage
const tweaked = enc.encrypt(text, { tweak: new Uint8Array([1, 2, 3]) });
const untweaked = enc.decrypt(tweaked, { tweak: new Uint8Array([1, 2, 3]) });

Custom Patterns

import { TokenEncryptor, ALPHANUMERIC } from "fast-cipher/tokens";

const enc = new TokenEncryptor(key);

enc.register({
  kind: "simple",
  name: "my-service",
  prefix: "myapp_",
  bodyRegex: "[A-Za-z0-9]{32}",
  bodyAlphabet: ALPHANUMERIC,
  minBodyLength: 32,
});

The exported alphabets are ALPHANUMERIC, ALPHANUMERIC_LOWER, ALPHANUMERIC_UPPER, BASE64, BASE64URL, DIGITS, and HEX_LOWER.

Development

Run the formatter:

bunx biome format --write src test

Run lint/style checks:

bunx biome check src test

Run the test suite:

bun test

Run type checking:

bunx tsc --noEmit

Build the package:

bun run build

References