A fast, zero-dependency Vigenère cipher implementation in Node.js supporting Strings, Buffers, Streams, and key-cracking by frequency analysis.
Try it live in your browser → Encrypt, decrypt and crack the key, no install needed. · Watch the 30s video
More short package videos on @gykhdev. Sibling packages: @gykh/morse, @gykh/enigma, @gykh/caesar-cipher, @gykh/cat-facts. All packages: @gykh on npm.
- 🚀 Zero Dependencies: Pure native Node.js implementation.
- 🔑 Keyword cipher: Each letter of the key picks a different Caesar shift, so the same letter encrypts differently along the message.
- 🔄 Multi-Format Support: Encrypt and decrypt Strings, Buffers, and Node.js Streams.
- 🌊 Chunk-safe streams: The key position carries across chunks, so streamed output matches encrypting all at once.
- 🕵️ Cracking:
crack(str)recovers an unknown key from English ciphertext using the index of coincidence and chi-squared frequency analysis. - 📦 Dual ESM & CommonJS with full TypeScript types.
pnpm add @gykh/vigenere-cipher
# or
npm install @gykh/vigenere-cipher
# or
yarn add @gykh/vigenere-cipherRequires Node.js 18 or newer.
Letters A-Z / a-z are shifted by the matching key letter (A = 0, B = 1 … Z = 25) and keep their case. Spaces, digits and punctuation are preserved and do not advance the key.
// ESM
import { encryptString, decryptString, crack } from "@gykh/vigenere-cipher";
// CommonJS
// const { encryptString, decryptString, crack } = require("@gykh/vigenere-cipher");
encryptString("ATTACKATDAWN", "LEMON"); // "LXFOPVEFRNHR"
decryptString("LXFOPVEFRNHR", "LEMON"); // "ATTACKATDAWN"
encryptString("Attack at dawn! 123", "lemon"); // "Lxfopv ef rnhr! 123"const secret = encryptString(longEnglishText, "CIPHER");
const { key, text } = crack(secret);
console.log(key); // "CIPHER"
console.log(text); // the original textCracking is statistical: it needs English text and works best with 100+ letters of ciphertext. Short messages may return a wrong key.
import { encrypt, decrypt } from "@gykh/vigenere-cipher";
import { readFile } from "fs/promises";
const buffer = await readFile("sample.txt");
const encryptedBuffer = encrypt(buffer, "LEMON");
const decryptedBuffer = decrypt(encryptedBuffer, "LEMON");
console.log(buffer.equals(decryptedBuffer)); // trueFor files or streams exceeding 1000 characters/bytes, use the streaming transform classes:
import { EncryptTransform, DecryptTransform } from "@gykh/vigenere-cipher";
import fs from "fs";
import { pipeline } from "stream/promises";
await pipeline(
fs.createReadStream("large-input.txt"),
new EncryptTransform("LEMON"),
fs.createWriteStream("large-encrypted.txt")
);
await pipeline(
fs.createReadStream("large-encrypted.txt"),
new DecryptTransform("LEMON"),
fs.createWriteStream("large-decrypted.txt")
);str(string, max 1000 chars): Plaintext.key(string, 1–100 letters A-Z, any case): Keyword.- Returns:
string
str(string, max 1000 chars): Ciphertext.key(string): Keyword used during encryption.- Returns:
string
Recovers the key of English ciphertext without knowing it.
str(string, max 1000 chars): Ciphertext.options.maxKeyLength(number, 1–100, default20): Longest key length to try.- Returns:
{ key: string, text: string }, wherekeyis uppercase.
buffer(Buffer, max 1000 bytes): Input buffer.key(string): Keyword.- Returns:
Buffer
Node.js stream.Transform subclasses. The key position carries over between chunks.
Note
The string and buffer functions and crack enforce an input limit of 1000 characters/bytes. For larger data, pipe through EncryptTransform / DecryptTransform.
- Find the key length. For each candidate length n, split the letters into n columns. When n is right, each column is a plain Caesar cipher and its index of coincidence jumps to English levels (≈ 0.066 vs ≈ 0.038 for random text). The shortest length close to the best score wins, since multiples of the key length score equally well.
- Solve each column. Try all 26 shifts per column and keep the one whose letter counts best match English (lowest chi-squared).
- Decrypt with the recovered key.
The Vigenère cipher is not secure. It resisted attack for three centuries but is broken in milliseconds today. Use it for learning, puzzles and CTFs, not for protecting data.
- Sylvester Das — Website • MiniFyn • Buy Me A Coffee
MIT © 2026 get-your-knowledge-here
