mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-07-12 15:36:32 +02:00
ported Jason Haddix's additions, added transform settings
This commit is contained in:
@@ -33,6 +33,17 @@
|
||||
* canDecode: false,
|
||||
* func: function(text) { ... }
|
||||
* });
|
||||
*
|
||||
* 4. Transform with user options (gear icon in UI) and optional input kind:
|
||||
*
|
||||
* export default new BaseTransformer({
|
||||
* name: 'Binary',
|
||||
* inputKind: 'textarea', // 'textarea' | 'text' — main transform input when active
|
||||
* configurableOptions: [
|
||||
* { id: 'byteSpacing', label: 'Space between bytes', type: 'boolean', default: true }
|
||||
* ],
|
||||
* func: function(text, options = {}) { ... }
|
||||
* });
|
||||
*/
|
||||
|
||||
export class BaseTransformer {
|
||||
@@ -43,12 +54,15 @@ export class BaseTransformer {
|
||||
* @param {Function} config.func - Encoding function (required)
|
||||
* @param {number} [config.priority=85] - Decoder priority (1-310)
|
||||
* @param {Object} [config.map] - Character mapping (if provided, auto-generates reverse)
|
||||
* @param {Function} [config.reverse] - Custom decoder function
|
||||
* @param {Function} [config.reverse] - Custom decoder function (text, options) — options match encode prefs
|
||||
* @param {Function} [config.preview] - Preview function (defaults to func)
|
||||
* @param {Function} [config.detector] - Custom detection function (text) => boolean
|
||||
* @param {boolean} [config.canDecode=true] - Whether this transformer can decode
|
||||
* @param {string} [config.category] - Category for organization
|
||||
* @param {string} [config.description] - Help text
|
||||
* @param {'textarea'|'text'} [config.inputKind='textarea'] - Transform input control when this transform is active
|
||||
* @param {Array<Object>} [config.configurableOptions] - Option definitions; shows gear when non-empty
|
||||
* Each: { id, label, type: 'boolean'|'select'|'text'|'number', default, options?, min?, max?, step? }
|
||||
*/
|
||||
constructor(config) {
|
||||
if (!config.name || !config.func) {
|
||||
|
||||
@@ -5,7 +5,21 @@ export default new BaseTransformer({
|
||||
name: 'ADFGX Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: 'KEYWORD', // Default transposition key
|
||||
key: 'KEYWORD',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Transposition keyword',
|
||||
type: 'text',
|
||||
default: 'KEYWORD'
|
||||
}
|
||||
],
|
||||
_transKey: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (k || this.key || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
},
|
||||
// ADFGX uses a 5x5 Polybius square with letters A, D, F, G, X as coordinates
|
||||
// Standard square (I and J share position)
|
||||
square: [
|
||||
@@ -17,8 +31,9 @@ export default new BaseTransformer({
|
||||
],
|
||||
// Coordinate labels
|
||||
coords: ['A', 'D', 'F', 'G', 'X'],
|
||||
func: function(text) {
|
||||
const transKey = (this.key || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const transKey = this._transKey(options);
|
||||
if (transKey.length === 0) return text;
|
||||
|
||||
const cleaned = text.toUpperCase().replace(/[^A-Z]/g, '');
|
||||
@@ -79,8 +94,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const transKey = (this.key || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const transKey = this._transKey(options);
|
||||
if (transKey.length === 0) return text;
|
||||
|
||||
// Only process ADFGX characters
|
||||
@@ -153,9 +169,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[adfgx]';
|
||||
const result = this.func(text.slice(0, 5));
|
||||
const result = this.func(text.slice(0, 5), options);
|
||||
return result.substring(0, 12) + (result.length > 12 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -1,32 +1,80 @@
|
||||
// affine transform
|
||||
// Wrapped in IIFE so build/build-transforms.js (which strips `export default`) assigns the
|
||||
// transformer, not a stray top-level helper (see cipher/rot8000.js).
|
||||
import BaseTransformer from '../BaseTransformer.js';
|
||||
|
||||
export default new BaseTransformer({
|
||||
|
||||
name: 'Affine Cipher (a=5,b=8)',
|
||||
priority: 60,
|
||||
a: 5, b: 8, m: 26, invA: 21, // 5*21 ≡ 1 (mod 26)
|
||||
func: function(text) {
|
||||
const {a,b,m} = this;
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code>=65 && code<=90) return String.fromCharCode(65 + ((a*(code-65)+b)%m));
|
||||
if (code>=97 && code<=122) return String.fromCharCode(97 + ((a*(code-97)+b)%m));
|
||||
return c;
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[affine]';
|
||||
return this.func(text.slice(0,8)) + (text.length>8?'...':'');
|
||||
},
|
||||
reverse: function(text) {
|
||||
const {invA,b,m} = this;
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code>=65 && code<=90) return String.fromCharCode(65 + ((invA*((code-65 - b + m)%m))%m));
|
||||
if (code>=97 && code<=122) return String.fromCharCode(97 + ((invA*((code-97 - b + m)%m))%m));
|
||||
return c;
|
||||
}).join('');
|
||||
export default (() => {
|
||||
function modInv(a, m) {
|
||||
a = ((a % m) + m) % m;
|
||||
for (let x = 1; x < m; x++) {
|
||||
if ((a * x) % m === 1) return x;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
return new BaseTransformer({
|
||||
|
||||
name: 'Affine Cipher',
|
||||
priority: 60,
|
||||
a: 5,
|
||||
b: 8,
|
||||
m: 26,
|
||||
invA: 21,
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'a',
|
||||
label: 'Multiplier a (coprime to 26)',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
min: 1,
|
||||
max: 25,
|
||||
step: 1
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
label: 'Shift b',
|
||||
type: 'number',
|
||||
default: 8,
|
||||
min: 0,
|
||||
max: 25,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
_params: function(options) {
|
||||
options = options || {};
|
||||
const m = 26;
|
||||
const a = options.a !== undefined && options.a !== '' ? Number(options.a) : this.a;
|
||||
const b = options.b !== undefined && options.b !== '' ? Number(options.b) : this.b;
|
||||
const inv = modInv(a, m);
|
||||
return { a, b, m, invA: inv };
|
||||
},
|
||||
func: function(text, options) {
|
||||
const { a, b, m } = this._params(options);
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) return String.fromCharCode(65 + ((a * (code - 65) + b) % m));
|
||||
if (code >= 97 && code <= 122) return String.fromCharCode(97 + ((a * (code - 97) + b) % m));
|
||||
return c;
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[affine]';
|
||||
return this.func(text.slice(0, 8), options) + (text.length > 8 ? '...' : '');
|
||||
},
|
||||
reverse: function(text, options) {
|
||||
const { b, m, invA } = this._params(options);
|
||||
if (invA == null) return text;
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) {
|
||||
return String.fromCharCode(65 + ((invA * ((code - 65 - b + m) % m)) % m));
|
||||
}
|
||||
if (code >= 97 && code <= 122) {
|
||||
return String.fromCharCode(97 + ((invA * ((code - 97 - b + m) % m)) % m));
|
||||
}
|
||||
return c;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -5,9 +5,24 @@ export default new BaseTransformer({
|
||||
name: 'Autokey Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: 'KEY', // Initial key
|
||||
func: function(text) {
|
||||
const key = (this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
key: 'KEY',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Priming key',
|
||||
type: 'text',
|
||||
default: 'KEY'
|
||||
}
|
||||
],
|
||||
_key: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (k || this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
},
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
let result = '';
|
||||
@@ -33,8 +48,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = (this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
let result = '';
|
||||
@@ -71,9 +87,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[autokey]';
|
||||
return this.func(text.slice(0, 8)) + (text.length > 8 ? '...' : '');
|
||||
return this.func(text.slice(0, 8), options) + (text.length > 8 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
// Autokey produces ciphertext that looks like scrambled letters
|
||||
|
||||
@@ -5,9 +5,19 @@ export default new BaseTransformer({
|
||||
name: 'Beaufort Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: 'KEY', // Default key
|
||||
func: function(text) {
|
||||
const key = (this.key || 'KEY').toUpperCase();
|
||||
key: 'KEY',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Keyword',
|
||||
type: 'text',
|
||||
default: 'KEY'
|
||||
}
|
||||
],
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const k = options.key !== undefined && options.key !== null ? String(options.key) : null;
|
||||
const key = (k || this.key || 'KEY').toUpperCase();
|
||||
const keyLength = key.length;
|
||||
let keyIndex = 0;
|
||||
|
||||
@@ -32,13 +42,12 @@ export default new BaseTransformer({
|
||||
}
|
||||
}).join('');
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Beaufort cipher is self-reciprocal (same function for encode/decode)
|
||||
return this.func(text);
|
||||
reverse: function(text, options) {
|
||||
return this.func(text, options);
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[beaufort]';
|
||||
const result = this.func(text.slice(0, 8));
|
||||
const result = this.func(text.slice(0, 8), options);
|
||||
return result.substring(0, 10) + (result.length > 10 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -5,7 +5,25 @@ export default new BaseTransformer({
|
||||
name: 'Bifid Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
period: 5, // Period for fractionation (default 5)
|
||||
period: 5,
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'period',
|
||||
label: 'Period',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
min: 2,
|
||||
max: 20,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
_period: function(options) {
|
||||
options = options || {};
|
||||
const p = options.period !== undefined && options.period !== ''
|
||||
? Number(options.period)
|
||||
: this.period;
|
||||
return Math.max(2, Math.min(30, parseInt(p, 10) || 5));
|
||||
},
|
||||
// Standard Polybius square (5x5, I and J share same cell)
|
||||
square: [
|
||||
['A', 'B', 'C', 'D', 'E'],
|
||||
@@ -14,8 +32,8 @@ export default new BaseTransformer({
|
||||
['Q', 'R', 'S', 'T', 'U'],
|
||||
['V', 'W', 'X', 'Y', 'Z']
|
||||
],
|
||||
func: function(text) {
|
||||
const period = parseInt(this.period) || 5;
|
||||
func: function(text, options) {
|
||||
const period = this._period(options);
|
||||
const cleaned = text.toUpperCase().replace(/[^A-Z]/g, '');
|
||||
if (cleaned.length === 0) return text;
|
||||
|
||||
@@ -56,8 +74,8 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const period = parseInt(this.period) || 5;
|
||||
reverse: function(text, options) {
|
||||
const period = this._period(options);
|
||||
const cleaned = text.toUpperCase().replace(/[^A-Z]/g, '');
|
||||
if (cleaned.length === 0) return text;
|
||||
|
||||
@@ -101,9 +119,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[bifid]';
|
||||
const result = this.func(text.slice(0, 5));
|
||||
const result = this.func(text.slice(0, 5), options);
|
||||
return result.substring(0, 10) + (result.length > 10 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -3,43 +3,57 @@ import BaseTransformer from '../BaseTransformer.js';
|
||||
|
||||
export default new BaseTransformer({
|
||||
|
||||
name: 'Caesar Cipher',
|
||||
name: 'Caesar Cipher',
|
||||
priority: 60,
|
||||
shift: 3, // Traditional Caesar shift is 3
|
||||
func: function(text) {
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
// Only shift letters, leave other characters unchanged
|
||||
if (code >= 65 && code <= 90) { // Uppercase letters
|
||||
return String.fromCharCode(((code - 65 + this.shift) % 26) + 65);
|
||||
} else if (code >= 97 && code <= 122) { // Lowercase letters
|
||||
return String.fromCharCode(((code - 97 + this.shift) % 26) + 97);
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[cursive]';
|
||||
return this.func(text.slice(0, 3)) + '...';
|
||||
},
|
||||
reverse: function(text) {
|
||||
// For decoding, shift in the opposite direction
|
||||
const originalShift = this.shift;
|
||||
this.shift = 26 - (this.shift % 26); // Reverse the shift
|
||||
const result = this.func(text);
|
||||
this.shift = originalShift; // Restore original shift
|
||||
return result;
|
||||
},
|
||||
// Detector: Check if text is letters-only (potential Caesar cipher)
|
||||
detector: function(text) {
|
||||
// Caesar cipher only affects letters, so check if text contains mostly letters
|
||||
// Remove punctuation, numbers, and common symbols for the ratio check
|
||||
const cleaned = text.replace(/[\s.,!?;:'"()\-&0-9]/g, '');
|
||||
// Must be mostly letters (at least 70%) and have some length
|
||||
if (cleaned.length < 5) return false;
|
||||
const letterCount = (cleaned.match(/[a-zA-Z]/g) || []).length;
|
||||
return letterCount / cleaned.length > 0.7;
|
||||
shift: 3,
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'shift',
|
||||
label: 'Shift (1–25)',
|
||||
type: 'number',
|
||||
default: 3,
|
||||
min: 1,
|
||||
max: 25,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
_encode: function(text, shift) {
|
||||
const s = ((shift % 26) + 26) % 26;
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) {
|
||||
return String.fromCharCode(((code - 65 + s) % 26) + 65);
|
||||
}
|
||||
if (code >= 97 && code <= 122) {
|
||||
return String.fromCharCode(((code - 97 + s) % 26) + 97);
|
||||
}
|
||||
return c;
|
||||
}).join('');
|
||||
},
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const shift = options.shift !== undefined && options.shift !== ''
|
||||
? Number(options.shift)
|
||||
: this.shift;
|
||||
return this._encode(text, shift);
|
||||
},
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[caesar]';
|
||||
return this.func(text.slice(0, 3), options) + '...';
|
||||
},
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const shift = options.shift !== undefined && options.shift !== ''
|
||||
? Number(options.shift)
|
||||
: this.shift;
|
||||
const s = ((shift % 26) + 26) % 26;
|
||||
return this._encode(text, 26 - s);
|
||||
},
|
||||
detector: function(text) {
|
||||
const cleaned = text.replace(/[\s.,!?;:'"()\-&0-9]/g, '');
|
||||
if (cleaned.length < 5) return false;
|
||||
const letterCount = (cleaned.match(/[a-zA-Z]/g) || []).length;
|
||||
return letterCount / cleaned.length > 0.7;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,9 +5,24 @@ export default new BaseTransformer({
|
||||
name: 'Columnar Transposition',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: 'KEY', // Default key
|
||||
func: function(text) {
|
||||
const key = (this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
key: 'KEY',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Keyword',
|
||||
type: 'text',
|
||||
default: 'KEY'
|
||||
}
|
||||
],
|
||||
_key: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (k || this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
},
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
// Remove spaces and convert to uppercase for processing
|
||||
@@ -41,8 +56,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = (this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
const keyLength = key.length;
|
||||
@@ -79,9 +95,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result.join('').replace(/X+$/, ''); // Remove padding X's
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[columnar]';
|
||||
const result = this.func(text.slice(0, 10));
|
||||
const result = this.func(text.slice(0, 10), options);
|
||||
return result.substring(0, 12) + (result.length > 12 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -5,8 +5,30 @@ export default new BaseTransformer({
|
||||
name: 'Four-Square Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key1: 'EXAMPLE', // Top-left square key
|
||||
key2: 'KEYWORD', // Bottom-right square key
|
||||
key1: 'EXAMPLE',
|
||||
key2: 'KEYWORD',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key1',
|
||||
label: 'Top-left square keyword',
|
||||
type: 'text',
|
||||
default: 'EXAMPLE'
|
||||
},
|
||||
{
|
||||
id: 'key2',
|
||||
label: 'Bottom-right square keyword',
|
||||
type: 'text',
|
||||
default: 'KEYWORD'
|
||||
}
|
||||
],
|
||||
_keys: function(options) {
|
||||
options = options || {};
|
||||
const k1 = options.key1 !== undefined && options.key1 !== null ? String(options.key1) : null;
|
||||
const k2 = options.key2 !== undefined && options.key2 !== null ? String(options.key2) : null;
|
||||
const key1 = (k1 || this.key1 || 'EXAMPLE').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
const key2 = (k2 || this.key2 || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
return { key1, key2 };
|
||||
},
|
||||
// Standard alphabet for top-right and bottom-left squares
|
||||
standardAlphabet: 'ABCDEFGHIKLMNOPQRSTUVWXYZ',
|
||||
// Create keyed squares
|
||||
@@ -95,9 +117,8 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key1 = (this.key1 || 'EXAMPLE').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
const key2 = (this.key2 || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
reverse: function(text, options) {
|
||||
const { key1, key2 } = this._keys(options);
|
||||
|
||||
if (key1.length === 0 || key2.length === 0) return text;
|
||||
|
||||
@@ -145,9 +166,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[four-square]';
|
||||
return this.func(text.slice(0, 4)) + (text.length > 4 ? '...' : '');
|
||||
return this.func(text.slice(0, 4), options) + (text.length > 4 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
// Four-Square produces scrambled text (all uppercase letters, no digits)
|
||||
|
||||
@@ -5,9 +5,24 @@ export default new BaseTransformer({
|
||||
name: 'Gronsfeld Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: '12345', // Default numeric key
|
||||
func: function(text) {
|
||||
const key = (this.key || '12345').replace(/[^0-9]/g, '');
|
||||
key: '12345',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Numeric key (digits)',
|
||||
type: 'text',
|
||||
default: '12345'
|
||||
}
|
||||
],
|
||||
_key: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (k || this.key || '12345').replace(/[^0-9]/g, '');
|
||||
},
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
let result = '';
|
||||
@@ -31,8 +46,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = (this.key || '12345').replace(/[^0-9]/g, '');
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
let result = '';
|
||||
@@ -56,9 +72,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[gronsfeld]';
|
||||
return this.func(text.slice(0, 8)) + (text.length > 8 ? '...' : '');
|
||||
return this.func(text.slice(0, 8), options) + (text.length > 8 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
// Gronsfeld produces ciphertext that looks like scrambled letters
|
||||
|
||||
@@ -5,10 +5,32 @@ export default new BaseTransformer({
|
||||
name: 'Hill Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
// Default 2x2 key matrix (must be invertible mod 26)
|
||||
key: [[3, 3], [2, 5]], // Default key matrix
|
||||
func: function(text) {
|
||||
const key = this.key || [[3, 3], [2, 5]];
|
||||
key: [[3, 3], [2, 5]],
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'matrixJson',
|
||||
label: '2×2 matrix (JSON), mod 26',
|
||||
type: 'text',
|
||||
default: '[[3,3],[2,5]]'
|
||||
}
|
||||
],
|
||||
_keyMatrix: function(options) {
|
||||
const fallback = this.key || [[3, 3], [2, 5]];
|
||||
options = options || {};
|
||||
if (options.matrixJson !== undefined && options.matrixJson !== null && String(options.matrixJson).trim() !== '') {
|
||||
try {
|
||||
const parsed = JSON.parse(String(options.matrixJson));
|
||||
if (Array.isArray(parsed) && parsed.length === 2 && parsed[0].length === 2) {
|
||||
return parsed;
|
||||
}
|
||||
} catch (e) {
|
||||
/* use fallback */
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
},
|
||||
func: function(text, options) {
|
||||
const key = this._keyMatrix(options);
|
||||
const matrixSize = key.length;
|
||||
|
||||
// Prepare text: remove non-letters, pad with X if needed
|
||||
@@ -40,8 +62,8 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = this.key || [[3, 3], [2, 5]];
|
||||
reverse: function(text, options) {
|
||||
const key = this._keyMatrix(options);
|
||||
const matrixSize = key.length;
|
||||
|
||||
// Calculate inverse matrix mod 26
|
||||
@@ -121,9 +143,9 @@ export default new BaseTransformer({
|
||||
|
||||
return (oldS % m + m) % m;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[hill]';
|
||||
const result = this.func(text.slice(0, 4));
|
||||
const result = this.func(text.slice(0, 4), options);
|
||||
return result.substring(0, 8) + '...';
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -5,7 +5,21 @@ export default new BaseTransformer({
|
||||
name: 'Nihilist Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: '12345', // Default numeric key
|
||||
key: '12345',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Numeric key',
|
||||
type: 'text',
|
||||
default: '12345'
|
||||
}
|
||||
],
|
||||
_key: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (k || this.key || '12345').replace(/[^0-9]/g, '');
|
||||
},
|
||||
// Standard Polybius square (5x5, I and J share same cell)
|
||||
square: [
|
||||
['A', 'B', 'C', 'D', 'E'],
|
||||
@@ -14,8 +28,9 @@ export default new BaseTransformer({
|
||||
['Q', 'R', 'S', 'T', 'U'],
|
||||
['V', 'W', 'X', 'Y', 'Z']
|
||||
],
|
||||
func: function(text) {
|
||||
const key = (this.key || '12345').replace(/[^0-9]/g, '');
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
const cleaned = text.toUpperCase().replace(/[^A-Z]/g, '');
|
||||
@@ -47,8 +62,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result.trim();
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = (this.key || '12345').replace(/[^0-9]/g, '');
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
// Extract two-digit numbers
|
||||
@@ -79,9 +95,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[nihilist]';
|
||||
const result = this.func(text.slice(0, 5));
|
||||
const result = this.func(text.slice(0, 5), options);
|
||||
return result.substring(0, 15) + '...';
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -5,9 +5,24 @@ export default new BaseTransformer({
|
||||
name: 'Playfair Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: 'KEYWORD', // Default key
|
||||
func: function(text) {
|
||||
const key = (this.key || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
key: 'KEYWORD',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Keyword (letters A–Z)',
|
||||
type: 'text',
|
||||
default: 'KEYWORD'
|
||||
}
|
||||
],
|
||||
_key: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (k || this.key || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
},
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
// Create Playfair square
|
||||
@@ -54,8 +69,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = (this.key || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
const alphabet = 'ABCDEFGHIKLMNOPQRSTUVWXYZ';
|
||||
@@ -97,9 +113,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[playfair]';
|
||||
const result = this.func(text.slice(0, 8));
|
||||
const result = this.func(text.slice(0, 8), options);
|
||||
return result.substring(0, 10) + (result.length > 10 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -5,9 +5,24 @@ export default new BaseTransformer({
|
||||
name: 'Porta Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: 'KEY', // Default key
|
||||
func: function(text) {
|
||||
const key = (this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
key: 'KEY',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Keyword',
|
||||
type: 'text',
|
||||
default: 'KEY'
|
||||
}
|
||||
],
|
||||
_key: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (k || this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
},
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
// Porta uses 13 reciprocal alphabets, each pair (A/B, C/D, etc.) shares a tableau
|
||||
@@ -56,9 +71,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Porta is self-reciprocal - use reverse lookup in tableau
|
||||
const key = (this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._key(options);
|
||||
if (key.length === 0) return text;
|
||||
|
||||
const tableaus = {
|
||||
@@ -114,9 +129,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[porta]';
|
||||
const result = this.func(text.slice(0, 8));
|
||||
const result = this.func(text.slice(0, 8), options);
|
||||
return result.substring(0, 10) + (result.length > 10 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -3,48 +3,69 @@ import BaseTransformer from '../BaseTransformer.js';
|
||||
|
||||
export default new BaseTransformer({
|
||||
|
||||
name: 'Rail Fence (3 Rails)',
|
||||
name: 'Rail Fence',
|
||||
priority: 60,
|
||||
rails: 3,
|
||||
func: function(text) {
|
||||
const rails = Array.from({length: this.rails}, () => []);
|
||||
let rail = 0, dir = 1;
|
||||
for (const ch of text) {
|
||||
rails[rail].push(ch);
|
||||
rail += dir;
|
||||
if (rail === 0 || rail === this.rails-1) dir *= -1;
|
||||
}
|
||||
return rails.flat().join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[rail]';
|
||||
return this.func(text.slice(0,12)) + (text.length>12?'...':'');
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Use Array.from to properly handle multi-byte UTF-8 characters
|
||||
const chars = Array.from(text);
|
||||
const len = chars.length;
|
||||
const pattern = [];
|
||||
let rail = 0, dir = 1;
|
||||
for (let i=0;i<len;i++) {
|
||||
pattern.push(rail);
|
||||
rail += dir;
|
||||
if (rail === 0 || rail === this.rails-1) dir *= -1;
|
||||
}
|
||||
const counts = Array(this.rails).fill(0);
|
||||
for (const r of pattern) counts[r]++;
|
||||
const railsArr = [];
|
||||
let idx = 0;
|
||||
for (let r=0;r<this.rails;r++) {
|
||||
railsArr[r] = chars.slice(idx, idx+counts[r]);
|
||||
idx += counts[r];
|
||||
}
|
||||
const positions = Array(this.rails).fill(0);
|
||||
let out = '';
|
||||
for (const r of pattern) {
|
||||
out += railsArr[r][positions[r]++];
|
||||
}
|
||||
return out;
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'rails',
|
||||
label: 'Number of rails',
|
||||
type: 'number',
|
||||
default: 3,
|
||||
min: 2,
|
||||
max: 20,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
_rails: function(options) {
|
||||
options = options || {};
|
||||
const r = options.rails !== undefined && options.rails !== ''
|
||||
? Number(options.rails)
|
||||
: this.rails;
|
||||
return Math.max(2, Math.min(50, Math.floor(r) || 2));
|
||||
},
|
||||
func: function(text, options) {
|
||||
const n = this._rails(options);
|
||||
const rails = Array.from({ length: n }, () => []);
|
||||
let rail = 0;
|
||||
let dir = 1;
|
||||
for (const ch of text) {
|
||||
rails[rail].push(ch);
|
||||
rail += dir;
|
||||
if (rail === 0 || rail === n - 1) dir *= -1;
|
||||
}
|
||||
return rails.flat().join('');
|
||||
},
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[rail]';
|
||||
return this.func(text.slice(0, 12), options) + (text.length > 12 ? '...' : '');
|
||||
},
|
||||
reverse: function(text, options) {
|
||||
const n = this._rails(options);
|
||||
const chars = Array.from(text);
|
||||
const len = chars.length;
|
||||
const pattern = [];
|
||||
let rail = 0;
|
||||
let dir = 1;
|
||||
for (let i = 0; i < len; i++) {
|
||||
pattern.push(rail);
|
||||
rail += dir;
|
||||
if (rail === 0 || rail === n - 1) dir *= -1;
|
||||
}
|
||||
const counts = Array(n).fill(0);
|
||||
for (const r of pattern) counts[r]++;
|
||||
const railsArr = [];
|
||||
let idx = 0;
|
||||
for (let r = 0; r < n; r++) {
|
||||
railsArr[r] = chars.slice(idx, idx + counts[r]);
|
||||
idx += counts[r];
|
||||
}
|
||||
const positions = Array(n).fill(0);
|
||||
let out = '';
|
||||
for (const r of pattern) {
|
||||
out += railsArr[r][positions[r]++];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,87 +1,73 @@
|
||||
// ROT8000 cipher transform (Unicode rotation)
|
||||
// Wrapped in IIFE so build/build-transforms.js (which strips `export default`) produces
|
||||
// transforms['rot8000'] = (() => { ... return new BaseTransformer(...) })();
|
||||
// Top-level helper functions alone would otherwise bind to the first `function`, not the transformer.
|
||||
import BaseTransformer from '../BaseTransformer.js';
|
||||
|
||||
// Build valid character codes once (excludes control chars and surrogates)
|
||||
function buildValidCodes() {
|
||||
const validCodes = [];
|
||||
for (let i = 0; i <= 0xFFFF; i++) {
|
||||
// Skip control characters (0x0000-0x001F)
|
||||
if (i >= 0x0000 && i <= 0x001F) continue;
|
||||
// Skip DEL and some controls (0x007F-0x00A0)
|
||||
if (i >= 0x007F && i <= 0x00A0) continue;
|
||||
// Skip surrogate pairs (0xD800-0xDFFF)
|
||||
if (i >= 0xD800 && i <= 0xDFFF) continue;
|
||||
validCodes.push(i);
|
||||
}
|
||||
return validCodes;
|
||||
}
|
||||
|
||||
// Cache the valid codes and mappings
|
||||
let cachedValidCodes = null;
|
||||
let cachedCodeToIndex = null;
|
||||
let cachedShift = null;
|
||||
|
||||
function getRot8000Data() {
|
||||
if (!cachedValidCodes) {
|
||||
cachedValidCodes = buildValidCodes();
|
||||
// ROT8000 uses a shift that makes it self-reciprocal (applying twice returns original)
|
||||
// The shift should be approximately 0x8000 (32768), which is half the BMP
|
||||
// For self-reciprocity: (index + shift + shift) % validCount == index
|
||||
// This means: (2 * shift) % validCount == 0, so shift = validCount / 2
|
||||
cachedShift = Math.floor(cachedValidCodes.length / 2);
|
||||
cachedCodeToIndex = new Map();
|
||||
cachedValidCodes.forEach((code, index) => {
|
||||
cachedCodeToIndex.set(code, index);
|
||||
});
|
||||
}
|
||||
return { validCodes: cachedValidCodes, codeToIndex: cachedCodeToIndex, shift: cachedShift };
|
||||
}
|
||||
|
||||
export default new BaseTransformer({
|
||||
name: 'ROT8000',
|
||||
priority: 50,
|
||||
category: 'cipher',
|
||||
func: function(text) {
|
||||
// ROT8000 rotates Unicode BMP characters (0x0000-0xFFFF)
|
||||
// Excludes control characters: U+0000-U+001F, U+007F-U+00A0, U+D800-U+DFFF
|
||||
// Shift is half the valid range for self-reciprocity
|
||||
|
||||
const { validCodes, codeToIndex, shift } = getRot8000Data();
|
||||
const validCount = validCodes.length;
|
||||
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const code = text.charCodeAt(i);
|
||||
|
||||
// Check if character is in valid range
|
||||
if (codeToIndex.has(code)) {
|
||||
const index = codeToIndex.get(code);
|
||||
const rotatedIndex = (index + shift) % validCount;
|
||||
const rotatedCode = validCodes[rotatedIndex];
|
||||
result += String.fromCharCode(rotatedCode);
|
||||
} else {
|
||||
// Keep invalid characters as-is (spaces, emojis, etc.)
|
||||
result += text[i];
|
||||
}
|
||||
export default (() => {
|
||||
function buildValidCodes() {
|
||||
const validCodes = [];
|
||||
for (let i = 0; i <= 0xFFFF; i++) {
|
||||
if (i >= 0x0000 && i <= 0x001F) continue;
|
||||
if (i >= 0x007F && i <= 0x00A0) continue;
|
||||
if (i >= 0xD800 && i <= 0xDFFF) continue;
|
||||
validCodes.push(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
// ROT8000 is self-reciprocal (rotating by 0x8000 twice = full rotation)
|
||||
return this.func(text);
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[rot8000]';
|
||||
return this.func(text.slice(0, 8)) + (text.length > 8 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
// ROT8000 produces characters in various Unicode ranges
|
||||
// Check for non-ASCII characters that aren't typical text
|
||||
const hasNonAscii = /[^\x00-\x7F]/.test(text);
|
||||
const hasControlChars = /[\x00-\x1F]/.test(text);
|
||||
return hasNonAscii && text.length >= 5;
|
||||
return validCodes;
|
||||
}
|
||||
});
|
||||
|
||||
let cachedValidCodes = null;
|
||||
let cachedCodeToIndex = null;
|
||||
let cachedShift = null;
|
||||
|
||||
function getRot8000Data() {
|
||||
if (!cachedValidCodes) {
|
||||
cachedValidCodes = buildValidCodes();
|
||||
cachedShift = Math.floor(cachedValidCodes.length / 2);
|
||||
cachedCodeToIndex = new Map();
|
||||
cachedValidCodes.forEach((code, index) => {
|
||||
cachedCodeToIndex.set(code, index);
|
||||
});
|
||||
}
|
||||
return { validCodes: cachedValidCodes, codeToIndex: cachedCodeToIndex, shift: cachedShift };
|
||||
}
|
||||
|
||||
return new BaseTransformer({
|
||||
name: 'ROT8000',
|
||||
priority: 50,
|
||||
category: 'cipher',
|
||||
func: function(text) {
|
||||
const { validCodes, codeToIndex, shift } = getRot8000Data();
|
||||
const validCount = validCodes.length;
|
||||
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const code = text.charCodeAt(i);
|
||||
|
||||
if (codeToIndex.has(code)) {
|
||||
const index = codeToIndex.get(code);
|
||||
const rotatedIndex = (index + shift) % validCount;
|
||||
const rotatedCode = validCodes[rotatedIndex];
|
||||
result += String.fromCharCode(rotatedCode);
|
||||
} else {
|
||||
result += text[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[rot8000]';
|
||||
return this.func(text.slice(0, 8)) + (text.length > 8 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
const hasNonAscii = /[^\x00-\x7F]/.test(text);
|
||||
const hasControlChars = /[\x00-\x1F]/.test(text);
|
||||
return hasNonAscii && text.length >= 5;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -5,9 +5,27 @@ export default new BaseTransformer({
|
||||
name: 'Scytale Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key: 5, // Default number of columns (wrapping width)
|
||||
func: function(text) {
|
||||
const key = parseInt(this.key) || 5;
|
||||
key: 5,
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'columns',
|
||||
label: 'Columns (rod width)',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
min: 2,
|
||||
max: 40,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
_cols: function(options) {
|
||||
options = options || {};
|
||||
const c = options.columns !== undefined && options.columns !== ''
|
||||
? Number(options.columns)
|
||||
: this.key;
|
||||
return parseInt(c, 10) || 5;
|
||||
},
|
||||
func: function(text, options) {
|
||||
const key = this._cols(options);
|
||||
if (key < 2) return text;
|
||||
|
||||
// Remove spaces for encoding
|
||||
@@ -39,8 +57,8 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = parseInt(this.key) || 5;
|
||||
reverse: function(text, options) {
|
||||
const key = this._cols(options);
|
||||
if (key < 2) return text;
|
||||
|
||||
const cleaned = text.replace(/\s/g, '').toUpperCase();
|
||||
@@ -74,9 +92,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[scytale]';
|
||||
const result = this.func(text.slice(0, 10));
|
||||
const result = this.func(text.slice(0, 10), options);
|
||||
return result.substring(0, 12) + (result.length > 12 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -5,7 +5,25 @@ export default new BaseTransformer({
|
||||
name: 'Trifid Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
period: 5, // Period for fractionation (default 5)
|
||||
period: 5,
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'period',
|
||||
label: 'Period',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
min: 2,
|
||||
max: 20,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
_period: function(options) {
|
||||
options = options || {};
|
||||
const p = options.period !== undefined && options.period !== ''
|
||||
? Number(options.period)
|
||||
: this.period;
|
||||
return Math.max(2, Math.min(30, parseInt(p, 10) || 5));
|
||||
},
|
||||
// Trifid uses a 3x3x3 cube (27 positions for A-Z and space/punctuation)
|
||||
// Structure: 3 layers, each with 3 rows and 3 columns
|
||||
cube: [
|
||||
@@ -16,8 +34,8 @@ export default new BaseTransformer({
|
||||
// Layer 2
|
||||
[['S', 'T', 'U'], ['V', 'W', 'X'], ['Y', 'Z', ' ']]
|
||||
],
|
||||
func: function(text) {
|
||||
const period = parseInt(this.period) || 5;
|
||||
func: function(text, options) {
|
||||
const period = this._period(options);
|
||||
const cleaned = text.toUpperCase().replace(/[^A-Z ]/g, '');
|
||||
if (cleaned.length === 0) return text;
|
||||
|
||||
@@ -70,8 +88,8 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const period = parseInt(this.period) || 5;
|
||||
reverse: function(text, options) {
|
||||
const period = this._period(options);
|
||||
const cleaned = text.toUpperCase().replace(/[^A-Z ]/g, '');
|
||||
if (cleaned.length === 0) return text;
|
||||
|
||||
@@ -128,9 +146,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[trifid]';
|
||||
const result = this.func(text.slice(0, 5));
|
||||
const result = this.func(text.slice(0, 5), options);
|
||||
return result.substring(0, 10) + (result.length > 10 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -5,8 +5,30 @@ export default new BaseTransformer({
|
||||
name: 'Two-Square Cipher',
|
||||
priority: 60,
|
||||
category: 'cipher',
|
||||
key1: 'EXAMPLE', // Top square key
|
||||
key2: 'KEYWORD', // Bottom square key
|
||||
key1: 'EXAMPLE',
|
||||
key2: 'KEYWORD',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key1',
|
||||
label: 'Top square keyword',
|
||||
type: 'text',
|
||||
default: 'EXAMPLE'
|
||||
},
|
||||
{
|
||||
id: 'key2',
|
||||
label: 'Bottom square keyword',
|
||||
type: 'text',
|
||||
default: 'KEYWORD'
|
||||
}
|
||||
],
|
||||
_keys: function(options) {
|
||||
options = options || {};
|
||||
const k1 = options.key1 !== undefined && options.key1 !== null ? String(options.key1) : null;
|
||||
const k2 = options.key2 !== undefined && options.key2 !== null ? String(options.key2) : null;
|
||||
const key1 = (k1 || this.key1 || 'EXAMPLE').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
const key2 = (k2 || this.key2 || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
return { key1, key2 };
|
||||
},
|
||||
// Standard alphabet for reference
|
||||
standardAlphabet: 'ABCDEFGHIKLMNOPQRSTUVWXYZ',
|
||||
// Create keyed square
|
||||
@@ -42,9 +64,8 @@ export default new BaseTransformer({
|
||||
}
|
||||
return square;
|
||||
},
|
||||
func: function(text) {
|
||||
const key1 = (this.key1 || 'EXAMPLE').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
const key2 = (this.key2 || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
func: function(text, options) {
|
||||
const { key1, key2 } = this._keys(options);
|
||||
|
||||
if (key1.length === 0 || key2.length === 0) return text;
|
||||
|
||||
@@ -93,9 +114,8 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key1 = (this.key1 || 'EXAMPLE').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
const key2 = (this.key2 || 'KEYWORD').toUpperCase().replace(/[^A-Z]/g, '').replace(/J/g, 'I');
|
||||
reverse: function(text, options) {
|
||||
const { key1, key2 } = this._keys(options);
|
||||
|
||||
if (key1.length === 0 || key2.length === 0) return text;
|
||||
|
||||
@@ -143,9 +163,9 @@ export default new BaseTransformer({
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[two-square]';
|
||||
return this.func(text.slice(0, 4)) + (text.length > 4 ? '...' : '');
|
||||
return this.func(text.slice(0, 4), options) + (text.length > 4 ? '...' : '');
|
||||
},
|
||||
detector: function(text) {
|
||||
// Two-Square produces scrambled text (all uppercase letters, no digits)
|
||||
|
||||
@@ -3,40 +3,70 @@ import BaseTransformer from '../BaseTransformer.js';
|
||||
|
||||
export default new BaseTransformer({
|
||||
|
||||
name: 'Vigenère Cipher',
|
||||
name: 'Vigenère Cipher',
|
||||
priority: 60,
|
||||
key: 'KEY',
|
||||
func: function(text) {
|
||||
const key = this.key;
|
||||
let out = '';
|
||||
let j = 0;
|
||||
for (let i=0;i<text.length;i++) {
|
||||
const c = text[i];
|
||||
const code = c.charCodeAt(0);
|
||||
const k = key[j % key.length].toUpperCase().charCodeAt(0) - 65;
|
||||
if (code >= 65 && code <= 90) { out += String.fromCharCode(65 + ((code-65 + k)%26)); j++; }
|
||||
else if (code >= 97 && code <= 122) { out += String.fromCharCode(97 + ((code-97 + k)%26)); j++; }
|
||||
else out += c;
|
||||
}
|
||||
return out;
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[Vigenère]';
|
||||
return this.func(text.slice(0,8)) + (text.length>8?'...':'');
|
||||
},
|
||||
reverse: function(text) {
|
||||
const key = this.key;
|
||||
let out = '';
|
||||
let j = 0;
|
||||
for (let i=0;i<text.length;i++) {
|
||||
const c = text[i];
|
||||
const code = c.charCodeAt(0);
|
||||
const k = key[j % key.length].toUpperCase().charCodeAt(0) - 65;
|
||||
if (code >= 65 && code <= 90) { out += String.fromCharCode(65 + ((code-65 + 26 - (k%26))%26)); j++; }
|
||||
else if (code >= 97 && code <= 122) { out += String.fromCharCode(97 + ((code-97 + 26 - (k%26))%26)); j++; }
|
||||
else out += c;
|
||||
}
|
||||
return out;
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'Keyword',
|
||||
type: 'text',
|
||||
default: 'KEY'
|
||||
}
|
||||
],
|
||||
_keyStr: function(options) {
|
||||
const optionsKey = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return (optionsKey || this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
|
||||
},
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._keyStr(options);
|
||||
if (key.length === 0) return text;
|
||||
let out = '';
|
||||
let j = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
const code = c.charCodeAt(0);
|
||||
const k = key[j % key.length].toUpperCase().charCodeAt(0) - 65;
|
||||
if (code >= 65 && code <= 90) {
|
||||
out += String.fromCharCode(65 + ((code - 65 + k) % 26));
|
||||
j++;
|
||||
} else if (code >= 97 && code <= 122) {
|
||||
out += String.fromCharCode(97 + ((code - 97 + k) % 26));
|
||||
j++;
|
||||
} else {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[Vigenère]';
|
||||
return this.func(text.slice(0, 8), options) + (text.length > 8 ? '...' : '');
|
||||
},
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const key = this._keyStr(options);
|
||||
if (key.length === 0) return text;
|
||||
let out = '';
|
||||
let j = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
const code = c.charCodeAt(0);
|
||||
const k = key[j % key.length].toUpperCase().charCodeAt(0) - 65;
|
||||
if (code >= 65 && code <= 90) {
|
||||
out += String.fromCharCode(65 + ((code - 65 + 26 - (k % 26)) % 26));
|
||||
j++;
|
||||
} else if (code >= 97 && code <= 122) {
|
||||
out += String.fromCharCode(97 + ((code - 97 + 26 - (k % 26)) % 26));
|
||||
j++;
|
||||
} else {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,9 +5,23 @@ export default new BaseTransformer({
|
||||
name: 'XOR Cipher',
|
||||
priority: 70,
|
||||
category: 'cipher',
|
||||
key: 'KEY', // Default key
|
||||
func: function(text) {
|
||||
const key = this.key || 'KEY';
|
||||
key: 'KEY',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'key',
|
||||
label: 'XOR key (string)',
|
||||
type: 'text',
|
||||
default: 'KEY'
|
||||
}
|
||||
],
|
||||
_key: function(options) {
|
||||
const k = options && options.key !== undefined && options.key !== null
|
||||
? String(options.key)
|
||||
: null;
|
||||
return k || this.key || 'KEY';
|
||||
},
|
||||
func: function(text, options) {
|
||||
const key = this._key(options || {});
|
||||
const keyBytes = new TextEncoder().encode(key);
|
||||
const textBytes = new TextEncoder().encode(text);
|
||||
const result = new Uint8Array(textBytes.length);
|
||||
@@ -21,12 +35,11 @@ export default new BaseTransformer({
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
},
|
||||
reverse: function(text) {
|
||||
// XOR is self-reciprocal, but we need to convert from hex first
|
||||
reverse: function(text, options) {
|
||||
try {
|
||||
const hexBytes = text.match(/.{1,2}/g) || [];
|
||||
const bytes = new Uint8Array(hexBytes.map(h => parseInt(h, 16)));
|
||||
const key = this.key || 'KEY';
|
||||
const key = this._key(options || {});
|
||||
const keyBytes = new TextEncoder().encode(key);
|
||||
const result = new Uint8Array(bytes.length);
|
||||
|
||||
@@ -39,9 +52,9 @@ export default new BaseTransformer({
|
||||
return text;
|
||||
}
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[xor]';
|
||||
const result = this.func(text.slice(0, 4));
|
||||
const result = this.func(text.slice(0, 4), options);
|
||||
return result.substring(0, 12) + '...';
|
||||
},
|
||||
detector: function(text) {
|
||||
|
||||
@@ -4,6 +4,15 @@ import BaseTransformer from '../BaseTransformer.js';
|
||||
export default new BaseTransformer({
|
||||
name: 'Binary',
|
||||
priority: 300,
|
||||
inputKind: 'textarea',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'byteSpacing',
|
||||
label: 'Space between bytes',
|
||||
type: 'boolean',
|
||||
default: true
|
||||
}
|
||||
],
|
||||
// Detector: Only 0s, 1s, and spaces
|
||||
detector: function(text) {
|
||||
const cleaned = text.trim();
|
||||
@@ -11,18 +20,21 @@ export default new BaseTransformer({
|
||||
return noSpaces.length >= 8 && /^[01\s]+$/.test(cleaned);
|
||||
},
|
||||
|
||||
func: function(text) {
|
||||
// Use TextEncoder to properly handle UTF-8 (including emoji)
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const spacing = options.byteSpacing !== false;
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(text);
|
||||
return Array.from(bytes).map(b => b.toString(2).padStart(8, '0')).join(' ');
|
||||
const bits = Array.from(bytes).map(b => b.toString(2).padStart(8, '0'));
|
||||
return spacing ? bits.join(' ') : bits.join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[binary]';
|
||||
const full = this.func(text);
|
||||
const full = this.func(text, options);
|
||||
return full.substring(0, 24) + (full.length > 24 ? '...' : '');
|
||||
},
|
||||
reverse: function(text) {
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
// Remove spaces and ensure we have valid binary
|
||||
const binText = text.replace(/\s+/g, '');
|
||||
const bytes = [];
|
||||
|
||||
@@ -4,24 +4,36 @@ import BaseTransformer from '../BaseTransformer.js';
|
||||
export default new BaseTransformer({
|
||||
name: 'Hexadecimal',
|
||||
priority: 290,
|
||||
inputKind: 'textarea',
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'pairSpacing',
|
||||
label: 'Space between byte pairs',
|
||||
type: 'boolean',
|
||||
default: true
|
||||
}
|
||||
],
|
||||
// Detector: Only hex characters (0-9, A-F)
|
||||
detector: function(text) {
|
||||
const cleaned = text.trim().replace(/\s/g, '');
|
||||
return cleaned.length >= 4 && /^[0-9A-Fa-f]+$/.test(cleaned);
|
||||
},
|
||||
|
||||
func: function(text) {
|
||||
// Use TextEncoder to properly handle UTF-8 (including emoji)
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const spacing = options.pairSpacing !== false;
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(text);
|
||||
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(' ');
|
||||
const pairs = Array.from(bytes).map(b => b.toString(16).padStart(2, '0'));
|
||||
return spacing ? pairs.join(' ') : pairs.join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[hex]';
|
||||
const full = this.func(text);
|
||||
const full = this.func(text, options);
|
||||
return full.substring(0, 20) + (full.length > 20 ? '...' : '');
|
||||
},
|
||||
reverse: function(text) {
|
||||
reverse: function(text, options) {
|
||||
options = options || {};
|
||||
const hexText = text.replace(/\s+/g, '');
|
||||
const bytes = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user