Skip to content

Commit 7ed1906

Browse files
committed
[ADD] cypher code converters
1 parent f546cf6 commit 7ed1906

37 files changed

+1859
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { CipherI } from "./cipherInterface";
2+
3+
4+
export abstract class BaseCipher implements CipherI {
5+
abstract encode(plaintext: string, key?: any): string;
6+
abstract decode(ciphertext: string, key?: any): string;
7+
abstract readonly CipherName: string;
8+
9+
static readonly ALPHABET: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
10+
// Optional shared utility methods
11+
protected sanitize(text: string): string {
12+
return text.toUpperCase().replace(/[^A-Z]/g, '');
13+
}
14+
15+
protected mod(n: number, m: number): number {
16+
return ((n % m) + m) % m;
17+
}
18+
19+
protected modInverse(a: number, m: number): number {
20+
a = this.mod(a, m);
21+
for (let x = 1; x < m; x++) {
22+
if (this.mod(a * x, m) === 1) {
23+
return x;
24+
}
25+
}
26+
throw new Error("No modular inverse");
27+
}
28+
protected shiftChar(ch: string, shift: number): string {
29+
const i = BaseCipher.ALPHABET.indexOf(ch.toUpperCase());
30+
if (i === -1) {
31+
return ch;
32+
};
33+
return BaseCipher.ALPHABET[this.mod(i + shift, 26)];
34+
}
35+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface CipherI {
2+
encode(plaintext: string, key?: string | number | any): string;
3+
decode(ciphertext: string, key?: string | number | any): string;
4+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { CipherI } from "./cipherInterface";
2+
export { BaseCipher } from "./baseCipher";
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {
2+
ADFGVXCipher, AffineCipher, AtbashCipher, AutokeyCipher,
3+
BaconCipher, Base64Cipher, BaudotCipher, BeaufortCipher,
4+
BifidCipher, CaesarCipher, ColumnarCipher, DoubleTranspositionCipher,
5+
EnigmaCipher, FractionatedMorseCipher, GronsfeldCipher, KeywordCipher,
6+
MonoalphabeticCipher, MorseBasicCipher, NihilistCipher, PigpenCipher,
7+
PolybiusCipher, PortaCipher, RailFenceCipher, ROT13Cipher, RouteCipher,
8+
ScytaleCipher, TapCodeCipher, TrifidCipher, VICCipher, VigenereCipher,
9+
XORCipher
10+
} from './flavoured';
11+
12+
13+
import { BaseCipher } from "./base/baseCipher";
14+
15+
export class Cipher {
16+
private cipherMap: Record<string, BaseCipher> = {};
17+
18+
constructor() {
19+
const cipherClasses = [
20+
CaesarCipher,
21+
ROT13Cipher,
22+
AtbashCipher,
23+
MonoalphabeticCipher,
24+
KeywordCipher,
25+
AffineCipher,
26+
BaconCipher,
27+
PolybiusCipher,
28+
VigenereCipher,
29+
AutokeyCipher,
30+
BeaufortCipher,
31+
GronsfeldCipher,
32+
PortaCipher,
33+
RailFenceCipher,
34+
ColumnarCipher,
35+
RouteCipher,
36+
ScytaleCipher,
37+
DoubleTranspositionCipher,
38+
MorseBasicCipher,
39+
PigpenCipher,
40+
TapCodeCipher,
41+
ADFGVXCipher,
42+
FractionatedMorseCipher,
43+
BaudotCipher,
44+
NihilistCipher,
45+
VICCipher,
46+
BifidCipher,
47+
TrifidCipher,
48+
EnigmaCipher,
49+
XORCipher,
50+
Base64Cipher
51+
];
52+
53+
for (const CipherClass of cipherClasses) {
54+
const instance = new CipherClass();
55+
// Lowercase the key for case-insensitive lookup
56+
this.cipherMap[this.normalize(instance.CipherName)] = instance;
57+
}
58+
}
59+
60+
private normalize(name: string): string {
61+
return name.toLowerCase().replace(/\s+/g, '');
62+
}
63+
64+
encode(plaintext: string, key?: any, cipherName?: string): string {
65+
if (!cipherName) {
66+
throw new Error("Cipher name required");
67+
}
68+
const cipher = this.cipherMap[this.normalize(cipherName)];
69+
if (!cipher) {
70+
throw new Error(`Cipher "${cipherName}" not found`);
71+
}
72+
return cipher.encode(plaintext, key);
73+
}
74+
75+
decode(ciphertext: string, key?: any, cipherName?: string): string {
76+
if (!cipherName) {
77+
throw new Error("Cipher name required");
78+
}
79+
const cipher = this.cipherMap[this.normalize(cipherName)];
80+
if (!cipher) {
81+
throw new Error(`Cipher "${cipherName}" not found`);
82+
}
83+
return cipher.decode(ciphertext, key);
84+
}
85+
86+
listCiphers(): string[] {
87+
return Object.keys(this.cipherMap);
88+
}
89+
}
90+
91+
92+
export const ALLCiphers = {
93+
Cipher,
94+
CaesarCipher,
95+
ROT13Cipher,
96+
AtbashCipher,
97+
MonoalphabeticCipher,
98+
KeywordCipher,
99+
AffineCipher,
100+
BaconCipher,
101+
PolybiusCipher,
102+
VigenereCipher,
103+
AutokeyCipher,
104+
BeaufortCipher,
105+
GronsfeldCipher,
106+
PortaCipher,
107+
RailFenceCipher,
108+
ColumnarCipher,
109+
RouteCipher,
110+
ScytaleCipher,
111+
DoubleTranspositionCipher,
112+
MorseBasicCipher,
113+
PigpenCipher,
114+
TapCodeCipher,
115+
ADFGVXCipher,
116+
FractionatedMorseCipher,
117+
BaudotCipher,
118+
NihilistCipher,
119+
VICCipher,
120+
BifidCipher,
121+
TrifidCipher,
122+
EnigmaCipher,
123+
XORCipher,
124+
Base64Cipher
125+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { BaseCipher } from "../base/baseCipher";
2+
import { ColumnarCipher } from "./columnar";
3+
4+
export class ADFGVXCipher extends BaseCipher {
5+
private readonly labels = ['A', 'D', 'F', 'G', 'V', 'X'];
6+
private readonly square: string[];
7+
8+
readonly CipherName = "ADFGVX";
9+
10+
constructor() {
11+
super();
12+
// 6x6 square with A-Z and 0-9
13+
this.square = (BaseCipher.ALPHABET + '0123456789').split('');
14+
}
15+
encode(plainText: string = '', key: string = ''): string {
16+
const clean = plainText.toUpperCase();
17+
let pairs = '';
18+
for (const ch of clean) {
19+
const idx = this.square.indexOf(ch);
20+
if (idx === -1) {
21+
pairs += ch; continue;
22+
}
23+
const r = Math.floor(idx / 6), c = idx % 6;
24+
pairs += this.labels[r] + this.labels[c];
25+
}
26+
// then columnar transposition
27+
return new ColumnarCipher().encode(pairs, key);
28+
}
29+
decode(ciphertext: string = '', key: string = ''): string {
30+
const pairs = new ColumnarCipher().decode(ciphertext, key).match(/../g) || [];
31+
let out = '';
32+
for (const p of pairs) {
33+
const r = this.labels.indexOf(p[0]);
34+
const c = this.labels.indexOf(p[1]);
35+
if (r === -1 || c === -1) {
36+
out += p;
37+
} else {
38+
out += this.square[r * 6 + c];
39+
}
40+
}
41+
return out;
42+
}
43+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { BaseCipher } from "../base/baseCipher";
2+
3+
export class AffineCipher extends BaseCipher {
4+
private readonly a: number;
5+
private readonly b: number;
6+
readonly CipherName = "Affine";
7+
constructor(a: number = 5, b: number = 8) {
8+
super();
9+
this.a = a;
10+
this.b = b;
11+
if (this.gcd(this.a, 26) !== 1) {
12+
throw new Error("a must be coprime with 26");
13+
}
14+
}
15+
16+
private gcd(a: number, b: number): number {
17+
return b === 0 ? a : this.gcd(b, a % b);
18+
}
19+
20+
encode(plainText: string = ""): string {
21+
return plainText
22+
.toUpperCase()
23+
.split("")
24+
.map(ch => {
25+
const i = BaseCipher.ALPHABET.indexOf(ch);
26+
if (i === -1) {
27+
return ch;
28+
}// preserve non-letters
29+
return BaseCipher.ALPHABET[this.mod(this.a * i + this.b, 26)];
30+
})
31+
.join("");
32+
}
33+
34+
decode(cipherText: string = ""): string {
35+
const invA = this.modInverse(this.a, 26);
36+
return cipherText
37+
.toUpperCase()
38+
.split("")
39+
.map(ch => {
40+
const i = BaseCipher.ALPHABET.indexOf(ch);
41+
if (i === -1) {
42+
return ch;
43+
}
44+
return BaseCipher.ALPHABET[this.mod(invA * (i - this.b), 26)];
45+
})
46+
.join("");
47+
}
48+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { BaseCipher } from "../base/baseCipher";
2+
3+
export class AtbashCipher extends BaseCipher {
4+
readonly CipherName = "Atbash";
5+
private readonly map: Record<string, string>;
6+
7+
constructor() {
8+
super();
9+
this.map = {};
10+
11+
const alphabet = BaseCipher.ALPHABET;
12+
for (let i = 0; i < alphabet.length; i++) {
13+
this.map[alphabet[i]] = alphabet[alphabet.length - 1 - i];
14+
}
15+
}
16+
17+
encode(plainText: string = ""): string {
18+
return plainText
19+
.split("")
20+
.map(ch => this.map[ch.toUpperCase()] ?? ch)
21+
.join("");
22+
}
23+
24+
decode(cipherText: string = ""): string {
25+
// Atbash is symmetric — same as encode
26+
return this.encode(cipherText);
27+
}
28+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { BaseCipher } from "../base/baseCipher";
2+
3+
export class AutokeyCipher extends BaseCipher {
4+
readonly CipherName = "Autokey";
5+
6+
constructor() {
7+
super();
8+
}
9+
10+
encode(plainText: string = "", key: string = ""): string {
11+
const cleanPT = this.sanitize(plainText);
12+
const cleanKey = this.sanitize(key);
13+
let fullKey = (cleanKey + cleanPT).slice(0, cleanPT.length);
14+
15+
let out = "";
16+
for (let i = 0; i < cleanPT.length; i++) {
17+
const p = BaseCipher.ALPHABET.indexOf(cleanPT[i]);
18+
const k = BaseCipher.ALPHABET.indexOf(fullKey[i]);
19+
out += BaseCipher.ALPHABET[this.mod(p + k, 26)];
20+
}
21+
return out;
22+
}
23+
24+
decode(cipherText: string = "", key: string = ""): string {
25+
const cleanCT = this.sanitize(cipherText);
26+
let fullKey = this.sanitize(key).split("");
27+
28+
let out = "";
29+
for (const ch of cleanCT) {
30+
const c = BaseCipher.ALPHABET.indexOf(ch);
31+
const k = BaseCipher.ALPHABET.indexOf(fullKey[0]);
32+
const p = BaseCipher.ALPHABET[this.mod(c - k, 26)];
33+
34+
out += p;
35+
fullKey.push(p); // append plaintext to key
36+
fullKey.shift(); // maintain rolling window
37+
}
38+
return out;
39+
}
40+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { BaseCipher } from "../base/baseCipher";
2+
3+
export class BaconCipher extends BaseCipher {
4+
readonly CipherName = "Bacon";
5+
6+
constructor() {
7+
super();
8+
}
9+
10+
private encodeChar(ch: string): string {
11+
const index = BaseCipher.ALPHABET.indexOf(ch.toUpperCase());
12+
if (index === -1) {
13+
return ch;
14+
}
15+
16+
const binary = index.toString(2).padStart(5, "0");
17+
return binary.replace(/0/g, "A").replace(/1/g, "B");
18+
}
19+
20+
private decodeToken(token: string): string {
21+
if (!/^[AB]{5}$/.test(token)) {
22+
return token;
23+
}
24+
25+
const binary = token.replace(/A/g, "0").replace(/B/g, "1");
26+
const index = parseInt(binary, 2);
27+
return BaseCipher.ALPHABET[index] ?? "?";
28+
}
29+
30+
encode(plainText: string = ""): string {
31+
const tokens: string[] = [];
32+
33+
for (const ch of plainText) {
34+
tokens.push(this.encodeChar(ch));
35+
}
36+
37+
return tokens.join(" ");
38+
}
39+
40+
decode(cipherText: string = ""): string {
41+
const tokens: string[] = cipherText.split(/\s+/);
42+
const output: string[] = [];
43+
44+
for (const token of tokens) {
45+
output.push(this.decodeToken(token));
46+
}
47+
48+
return output.join("");
49+
}
50+
}

0 commit comments

Comments
 (0)