¿El objeto crypto de la API de Javascript de los navegadores puede trabajar con claves simétricas?
Sí, el objeto Web Crypto API (accesible como window.crypto) sí soporta criptografía con claves simétricas —y de hecho es uno de sus usos principales.
🔐 Algoritmos simétricos soportados
Los más comunes:
-
AES-GCM (recomendado ✅)
-
AES-CBC
-
AES-CTR
-
AES-KW (para envolver claves)
👉 Todos estos usan una misma clave para cifrar y descifrar.
🧠 Ejemplo simple (AES-GCM)
1. Generar clave
const key = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"]
);
2. Cifrar
const encoder = new TextEncoder();
const data = encoder.encode("Hola mundo");
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv,
},
key,
data
);
3. Descifrar
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv,
},
key,
encrypted
);
const decoder = new TextDecoder();
console.log(decoder.decode(decrypted));
⚠️ Cosas IMPORTANTES (muy fácil equivocarse)
1. IV único siempre
En AES-GCM:
const iv = crypto.getRandomValues(new Uint8Array(12));
👉 nunca reutilizar el mismo IV con la misma clave.
2. Las claves no son strings
No podés usar directamente:
"mi-clave-secreta"
👉 tenés que:
-
generarla (
generateKey) -
o importarla (
importKey)
3. Claves no serializables por defecto
Las CryptoKey:
-
no son JSON
-
no se pueden loggear bien
👉 si necesitás guardarlas:
crypto.subtle.exportKey(...)
🔄 Importar una clave simétrica (ej: desde password)
const key = await crypto.subtle.importKey(
"raw",
encoder.encode("mi-clave-secreta-32bytes"),
{ name: "AES-GCM" },
false,
["encrypt", "decrypt"]
);
🧩 Cuándo usar simétrico vs asimétrico
🔐 Simétrico (AES)
-
rápido ⚡
-
ideal para cifrar datos
-
requiere compartir la clave
🔑 Asimétrico (RSA, ECC)
-
más lento
-
sirve para intercambio de claves
-
no requiere compartir secreto inicial
👉 patrón típico:
-
usar RSA/ECDH para intercambiar clave
-
usar AES para cifrar datos
🧠 Conclusión
✔ Sí, el crypto del navegador soporta criptografía simétrica
✔ AES-GCM es la opción moderna recomendada
✔ Es seguro si:
-
manejás bien IVs
-
protegés la clave
Si querés, puedo armarte:
👉 un ejemplo realista tipo “cifrar datos en frontend y guardarlos en backend”
👉 o cómo derivar una clave segura desde contraseña (PBKDF2 / Argon2)
Entiendo lo que buscás: una transformación reversible (permutación/transposición), no seguridad.
Se puede hacer con la Web Crypto API, pero te conviene saber dos cosas:
-
Va a funcionar (AES-GCM/CTR/CBC),
-
pero es más pesado y engorroso que usar un algoritmo de permutación propio.
🧠 Opción A — Usar crypto (rápido de implementar)
Si te sirve algo tipo “caja negra reversible”, usá AES con:
-
clave fija
-
IV fijo (aunque sea mala práctica en seguridad, a vos no te importa)
Ejemplo simple
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const keyBytes = encoder.encode("12345678901234567890123456789012"); // 32 bytes
const key = await crypto.subtle.importKey(
"raw",
keyBytes,
{ name: "AES-GCM" },
false,
["encrypt", "decrypt"]
);
const iv = new Uint8Array(12); // fijo (todo ceros)
// cifrar
async function encode(str) {
const data = encoder.encode(str);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
data
);
return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
}
// descifrar
async function decode(str) {
const bytes = Uint8Array.from(atob(str), c => c.charCodeAt(0));
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
bytes
);
return decoder.decode(decrypted);
}
✔ reversible
✔ determinístico (misma entrada → misma salida)
✔ simple
⚠️ Problemas de esta opción (aunque no te importe seguridad)
-
overhead async (promesas)
-
conversión binario ↔ string
-
tamaño crece (Base64)
-
no controlás la “forma” de la permutación
🧠 Opción B — Permutación pura (RECOMENDADO para tu caso)
Si lo que querés es literalmente:
“string mezclado pero reversible”
👉 mejor usar una permutación determinística de índices
Ejemplo: permutación basada en seed
function createPermutation(n, seed = 123) {
const arr = Array.from({ length: n }, (_, i) => i);
let random = mulberry32(seed);
for (let i = n - 1; i > 0; i--) {
const j = Math.floor(random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function mulberry32(a) {
return function () {
a |= 0;
a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
Codificar
function encode(str, seed = 123) {
const perm = createPermutation(str.length, seed);
return perm.map(i => str[i]).join("");
}
Decodificar
function decode(str, seed = 123) {
const perm = createPermutation(str.length, seed);
const result = Array(str.length);
perm.forEach((p, i) => {
result[p] = str[i];
});
return result.join("");
}
🔥 Ventajas de esta opción
✔ 100% reversible
✔ síncrono
✔ rápido
✔ control total
✔ no crece el tamaño
✔ sin binarios
🧠 Insight importante
Lo que estás buscando conceptualmente es:
-
permutación → reordenar posiciones
-
transposición → reorganizar estructura
👉 AES hace mucho más (difusión + sustitución), pero vos solo necesitás una parte de eso.
⚖️ Conclusión
-
✔ Sí, podés usar
crypto→ funciona como “mezclador reversible” -
🚫 Pero no es lo ideal para tu objetivo
👉 Para tu caso, lo correcto es:
permutación determinística basada en seed
Si querés, puedo:
👉 hacerte una versión más “pro” (por bloques, tipo cifrado clásico)
👉 o una que además cambie caracteres (no solo posiciones) para que quede más “ofuscado” visualmente
Buena pregunta—te explico sin vueltas qué quise decir con eso.
🧠 Qué es “por bloques, tipo cifrado clásico”
Hasta ahora te mostré una permutación simple de toda la string.
Eso funciona, pero tiene limitaciones:
-
si cambia un carácter → cambia toda la salida
-
patrones pueden ser visibles en strings largas
-
no escala tan bien si querés más “mezcla”
👉 “Por bloques” significa:
dividir la string en partes fijas (bloques) y transformar cada bloque de forma independiente
Esto es parecido a cómo funcionan algoritmos como AES (pero sin la parte cripto pesada).
🔍 Ejemplo simple
String:
"ABCDEFGHIJKL"
Bloques de 4:
ABCD | EFGH | IJKL
Aplicás una permutación en cada bloque:
BADC | HGFE | JLIK
💡 ¿Qué mejora esto?
✔ más control
✔ más “mezcla visual”
✔ podés combinar:
-
permutación (posiciones)
-
sustitución (cambiar letras)
🚀 Ejemplo concreto (lo “pro” que te decía)
1. Permutación fija de bloque
const PERM = [2, 0, 3, 1]; // tamaño 4
2. Encode
function encode(str) {
const size = PERM.length;
let result = "";
for (let i = 0; i < str.length; i += size) {
const block = str.slice(i, i + size).padEnd(size, "_");
const permuted = PERM.map(j => block[j]).join("");
result += permuted;
}
return result;
}
3. Decode
function invertPerm(perm) {
const inv = [];
perm.forEach((p, i) => (inv[p] = i));
return inv;
}
const INV = invertPerm(PERM);
function decode(str) {
const size = INV.length;
let result = "";
for (let i = 0; i < str.length; i += size) {
const block = str.slice(i, i + size);
const original = INV.map(j => block[j]).join("");
result += original;
}
return result.replace(/_+$/, "");
}
🔥 Resultado
encode("HOLAESTOESPRUEBA")
// → string mezclado
decode(...)
→ "HOLAESTOESPRUEBA"
🧠 Diferencia con lo anterior
| Método | Qué hace | Complejidad |
|---|---|---|
| Permutación global | mezcla toda la string | simple |
| Por bloques | mezcla por partes | más control |
| Crypto (AES) | mezcla + difunde | overkill para vos |
🧩 Cuándo usar “por bloques”
Tiene sentido si:
-
querés algo más “desordenado” visualmente
-
querés controlar el patrón
-
no querés depender de APIs async (
crypto) -
querés algo determinístico y rápido
⚠️ Importante
Esto sigue siendo:
❌ NO seguridad
✅ solo transformación reversible
🧠 TL;DR
Lo que te ofrecía era:
una versión más estructurada de tu permutación
inspirada en cifrados reales, pero simple, síncrona y controlable
Si querés, puedo subir un nivel más:
👉 combinar permutación + sustitución (tipo Caesar/XOR)
→ eso ya queda mucho más “irreconocible” visualmente sin usar crypto.
Perfecto—vamos a armar una transformación reversible, síncrona y “ofuscada” visualmente combinando:
-
🔀 Permutación por bloques
-
🔁 Sustitución por carácter (tipo XOR con keystream)
Todo determinístico con una seed (tu “clave”), sin usar crypto.
🧠 Idea
Para cada bloque:
-
Generamos un keystream pseudoaleatorio (depende de la seed)
-
Aplicamos XOR a cada carácter → cambia los bytes
-
Aplicamos permutación fija → cambia posiciones
Para decodificar: invertimos orden
→ despermuto → XOR otra vez
🧱 Implementación completa
// PRNG determinístico (rápido y suficiente para ofuscación)
function mulberry32(a) {
return function () {
a |= 0;
a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// Permutación fija de bloque (tamaño 4 en este ejemplo)
const PERM = [2, 0, 3, 1];
// Inversa de la permutación
function invertPerm(perm) {
const inv = [];
perm.forEach((p, i) => (inv[p] = i));
return inv;
}
const INV = invertPerm(PERM);
// Convierte string <-> bytes
const enc = new TextEncoder();
const dec = new TextDecoder();
// Genera keystream de n bytes
function keystream(n, seed) {
const rand = mulberry32(seed);
const ks = new Uint8Array(n);
for (let i = 0; i < n; i++) {
ks[i] = Math.floor(rand() * 256);
}
return ks;
}
// XOR de bytes
function xorBytes(data, ks) {
const out = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
out[i] = data[i] ^ ks[i];
}
return out;
}
// Encode
function encode(str, seed = 1234) {
const bytes = enc.encode(str);
const size = PERM.length;
const out = [];
for (let i = 0; i < bytes.length; i += size) {
const block = bytes.slice(i, i + size);
// pad si hace falta
const padded = new Uint8Array(size);
padded.set(block);
// keystream dependiente de posición de bloque
const ks = keystream(size, seed + i);
// 1. XOR
const xored = xorBytes(padded, ks);
// 2. permutar
const permuted = PERM.map(j => xored[j]);
out.push(...permuted);
}
// base64 para string seguro
return btoa(String.fromCharCode(...out));
}
// Decode
function decode(str, seed = 1234) {
const bytes = Uint8Array.from(atob(str), c => c.charCodeAt(0));
const size = INV.length;
const out = [];
for (let i = 0; i < bytes.length; i += size) {
const block = bytes.slice(i, i + size);
// 1. des-permutar
const unperm = INV.map(j => block[j]);
// keystream igual que encode
const ks = keystream(size, seed + i);
// 2. XOR (revierte)
const original = xorBytes(unperm, ks);
out.push(...original);
}
return dec.decode(new Uint8Array(out)).replace(/\0+$/, "");
}
🔥 Ejemplo
const encoded = encode("Hola mundo!", 42);
console.log(encoded);
const decoded = decode(encoded, 42);
console.log(decoded); // "Hola mundo!"
💡 Qué lograste con esto
✔ reversible
✔ determinístico
✔ síncrono
✔ mezcla posiciones + contenido
✔ visualmente “ruido” (no reconocible)
⚙️ Cómo tunearlo
🔹 Más “ofuscación”
-
aumentá tamaño de bloque:
const PERM = [3,1,6,0,2,7,5,4]; // tamaño 8
🔹 Más variación
-
cambiar seed por bloque:
seed + i * 997
🔹 Más simple (sin base64)
Si solo manejás ASCII podés devolver string directo, pero base64 es más seguro.
⚠️ Limitaciones (claras)
-
❌ NO es criptografía segura
-
❌ reversible fácilmente si conocen algoritmo + seed
-
❌ no resiste análisis serio
👉 pero para:
-
ofuscación
-
IDs
-
evitar lectura casual
✔ es perfecto
🧠 TL;DR
Es básicamente un “mini cifrado casero”:
-
XOR (cambia bytes)
-
permutación (cambia posiciones)
-
seed (control)
Si querés, puedo darte una versión:
👉 que funcione solo con caracteres imprimibles (sin base64)
👉 o una que produzca strings tipo “slug” (solo letras y números)
Perfecto—vamos a adaptar el esquema para que la salida sea sólo alfanumérica ([a-zA-Z0-9]) y siga siendo reversible.
La idea:
-
Mantenemos tu pipeline (XOR + permutación por bloques)
-
Pero en vez de Base64 usamos una codificación Base62 (0–9, a–z, A–Z)
🧠 Clave del enfoque
-
Tus bytes “ofuscados” → los convertimos a un número grande
-
Ese número → lo codificamos en Base62
-
Para decodificar: Base62 → bytes → deshacer permutación + XOR
👉 Resultado: string tipo slug, sin símbolos raros
🧱 Implementación
// ===== Base62 =====
const ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
function base62Encode(bytes) {
let num = 0n;
for (let b of bytes) {
num = (num << 8n) | BigInt(b);
}
let str = "";
while (num > 0n) {
const rem = num % 62n;
str = ALPHABET[Number(rem)] + str;
num = num / 62n;
}
return str || "0";
}
function base62Decode(str) {
let num = 0n;
for (let c of str) {
const val = BigInt(ALPHABET.indexOf(c));
num = num * 62n + val;
}
// reconstruir bytes
const bytes = [];
while (num > 0n) {
bytes.unshift(Number(num & 0xffn));
num >>= 8n;
}
return new Uint8Array(bytes);
}
🔀 Núcleo (igual que antes, pero separado)
function mulberry32(a) {
return function () {
a |= 0;
a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const PERM = [2, 0, 3, 1];
function invertPerm(perm) {
const inv = [];
perm.forEach((p, i) => (inv[p] = i));
return inv;
}
const INV = invertPerm(PERM);
function keystream(n, seed) {
const rand = mulberry32(seed);
const ks = new Uint8Array(n);
for (let i = 0; i < n; i++) {
ks[i] = Math.floor(rand() * 256);
}
return ks;
}
function xorBytes(data, ks) {
const out = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
out[i] = data[i] ^ ks[i];
}
return out;
}
🔐 Encode (slug)
const enc = new TextEncoder();
function encodeSlug(str, seed = 1234) {
const bytes = enc.encode(str);
const size = PERM.length;
const out = [];
for (let i = 0; i < bytes.length; i += size) {
const block = bytes.slice(i, i + size);
const padded = new Uint8Array(size);
padded.set(block);
const ks = keystream(size, seed + i);
const xored = xorBytes(padded, ks);
const permuted = PERM.map(j => xored[j]);
out.push(...permuted);
}
return base62Encode(new Uint8Array(out));
}
🔓 Decode
const dec = new TextDecoder();
function decodeSlug(str, seed = 1234) {
const bytes = base62Decode(str);
const size = INV.length;
const out = [];
for (let i = 0; i < bytes.length; i += size) {
const block = bytes.slice(i, i + size);
const unperm = INV.map(j => block[j]);
const ks = keystream(size, seed + i);
const original = xorBytes(unperm, ks);
out.push(...original);
}
return dec.decode(new Uint8Array(out)).replace(/\0+$/, "");
}
🔥 Ejemplo
const slug = encodeSlug("Hola mundo!", 42);
console.log(slug); // ej: "3fA9kL2x..."
const original = decodeSlug(slug, 42);
console.log(original); // "Hola mundo!"
⚠️ Detalles IMPORTANTES
1. Longitud variable
Base62:
-
no mantiene longitud fija
-
mismo input → mismo output (determinístico)
2. Padding implícito
Estamos usando \0 como padding:
.replace(/\0+$/, "")
3. 100% alfanumérico
✔ sin / + = (como Base64)
✔ ideal para URLs, IDs, slugs
🚀 Opciones para mejorar
Si querés subir nivel:
🔹 longitud fija
→ agregar prefijo con tamaño original
🔹 más “ruido”
→ bloque más grande (8 o 16)
🔹 evitar BigInt (performance)
→ usar base62 incremental (más complejo)
🧠 TL;DR
Convertimos tu “cifrado casero” en:
-
bytes ofuscados
-
→ Base62
-
→ slug reversible
Si querés, puedo hacerte una versión:
👉 más corta (compacta)
👉 o que genere slugs de longitud fija tipo ID (ej: 12 chars siempre)
Perfecto—te dejo la misma implementación completamente tipada en TypeScript, cuidando:
-
tipos explícitos (
Uint8Array,number,string) -
funciones puras
-
sin
any -
compatible con
strict
🧱 Implementación TypeScript
// ===== Base62 =====
const ALPHABET: string =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const BASE = BigInt(ALPHABET.length);
export function base62Encode(bytes: Uint8Array): string {
let num: bigint = 0n;
for (const b of bytes) {
num = (num << 8n) | BigInt(b);
}
let str: string = "";
while (num > 0n) {
const rem: bigint = num % BASE;
str = ALPHABET[Number(rem)] + str;
num = num / BASE;
}
return str || "0";
}
export function base62Decode(str: string): Uint8Array {
let num: bigint = 0n;
for (const c of str) {
const idx: number = ALPHABET.indexOf(c);
if (idx === -1) {
throw new Error(`Invalid Base62 character: ${c}`);
}
num = num * BASE + BigInt(idx);
}
const bytes: number[] = [];
while (num > 0n) {
bytes.unshift(Number(num & 0xffn));
num >>= 8n;
}
return new Uint8Array(bytes);
}
🔀 Núcleo (PRNG + utils)
export type Permutation = readonly number[];
export function mulberry32(seed: number): () => number {
let a = seed | 0;
return (): number => {
a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
export function invertPerm(perm: Permutation): number[] {
const inv: number[] = new Array(perm.length);
perm.forEach((p: number, i: number) => {
inv[p] = i;
});
return inv;
}
export function keystream(length: number, seed: number): Uint8Array {
const rand = mulberry32(seed);
const ks = new Uint8Array(length);
for (let i = 0; i < length; i++) {
ks[i] = Math.floor(rand() * 256);
}
return ks;
}
export function xorBytes(
data: Uint8Array,
ks: Uint8Array
): Uint8Array {
if (data.length !== ks.length) {
throw new Error("Data and keystream must have same length");
}
const out = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
out[i] = data[i] ^ ks[i];
}
return out;
}
🔐 Configuración del algoritmo
export interface ObfuscatorConfig {
seed: number;
perm: Permutation;
}
export function createInverse(config: ObfuscatorConfig): number[] {
return invertPerm(config.perm);
}
🔒 Encode (slug)
const encoder: TextEncoder = new TextEncoder();
export function encodeSlug(
input: string,
config: ObfuscatorConfig
): string {
const bytes: Uint8Array = encoder.encode(input);
const { seed, perm } = config;
const size: number = perm.length;
const output: number[] = [];
for (let i = 0; i < bytes.length; i += size) {
const block: Uint8Array = bytes.slice(i, i + size);
const padded: Uint8Array = new Uint8Array(size);
padded.set(block);
const ks: Uint8Array = keystream(size, seed + i);
const xored: Uint8Array = xorBytes(padded, ks);
const permuted: number[] = perm.map((j: number) => xored[j]);
output.push(...permuted);
}
return base62Encode(new Uint8Array(output));
}
🔓 Decode
const decoder: TextDecoder = new TextDecoder();
export function decodeSlug(
slug: string,
config: ObfuscatorConfig
): string {
const bytes: Uint8Array = base62Decode(slug);
const { seed, perm } = config;
const inv: number[] = invertPerm(perm);
const size: number = inv.length;
const output: number[] = [];
for (let i = 0; i < bytes.length; i += size) {
const block: Uint8Array = bytes.slice(i, i + size);
const unperm: number[] = inv.map((j: number) => block[j]);
const ks: Uint8Array = keystream(size, seed + i);
const original: Uint8Array = xorBytes(
new Uint8Array(unperm),
ks
);
output.push(...original);
}
return decoder
.decode(new Uint8Array(output))
.replace(/\0+$/, "");
}
🔥 Uso tipado
const config: ObfuscatorConfig = {
seed: 42,
perm: [2, 0, 3, 1] as const
};
const slug: string = encodeSlug("Hola mundo!", config);
const original: string = decodeSlug(slug, config);
🧠 Notas de tipado (importantes)
✔ Permutation
type Permutation = readonly number[];
→ permite as const (más seguro)
✔ Sin any
Todo está:
-
tipado explícitamente
-
chequeado en runtime donde importa (ej: base62 inválido)
✔ Seguridad de tamaños
if (data.length !== ks.length)
→ evita bugs silenciosos
🚀 Si querés subir aún más el nivel
Te puedo agregar:
-
✅ branded types para diferenciar
Slugvsstring -
✅ validación de permutación (que no tenga duplicados)
-
✅ versión sin BigInt (más performante)
-
✅ versión con longitud fija (IDs tipo 12 chars)
Solo decime 👍
Buena pregunta—acá está la respuesta clara:
Sí: si querés distintos outputs para el mismo input, tenés que variar algo
→ normalmente el seed
→ o la permutación
→ o agregar un nonce/salt
Pero no todas las opciones son iguales 👇
🧠 Opción 1 (RECOMENDADA): variar el seed
encodeSlug("Hola", { seed: 1, perm })
encodeSlug("Hola", { seed: 2, perm })
✔ simple
✔ barato
✔ no cambia estructura
✔ fácil de revertir (si conocés el seed)
👉 Es lo más parecido a un “IV lógico” en tu sistema.
🧠 Opción 2: variar la permutación
perm1 = [2,0,3,1]
perm2 = [1,3,0,2]
✔ también funciona
❌ más incómodo de manejar
❌ tenés que guardar/transmitir la permutación
👉 útil si querés “modos” distintos, no tanto para variación por mensaje.
🧠 Opción 3 (LA MEJOR en diseño): agregar un nonce/salt
Esto es lo más limpio conceptualmente:
const nonce = 12345;
encodeSlug("Hola", {
seed: baseSeed + nonce,
perm
});
👉 pero además podés incluir el nonce en el output
Ejemplo:
"abc123XYZ"
donde:
-
"abc"= nonce codificado -
"123XYZ"= payload
🔥 Diseño recomendado (nivel sólido)
seed fijo + nonce variable por mensaje
const baseSeed = 42;
function encodeWithNonce(input: string) {
const nonce = Math.floor(Math.random() * 1e9);
const slug = encodeSlug(input, {
seed: baseSeed + nonce,
perm
});
return nonce.toString(36) + "-" + slug;
}
Decode:
function decodeWithNonce(str: string) {
const [nonceStr, slug] = str.split("-");
const nonce = parseInt(nonceStr, 36);
return decodeSlug(slug, {
seed: baseSeed + nonce,
perm
});
}
⚖️ Comparación rápida
| Estrategia | ¿Cambia output? | Complejidad | Recomendación |
|---|---|---|---|
| Cambiar seed | ✅ | baja | ⭐⭐⭐⭐⭐ |
| Cambiar perm | ✅ | media | ⭐⭐ |
| Nonce + seed | ✅ | media | ⭐⭐⭐⭐⭐ |
🧠 Insight importante
Tu sistema es determinístico:
misma entrada + mismo seed + misma perm → mismo output
👉 Para romper esa determinismo:
-
necesitás introducir entropía externa (seed/nonce)
🚨 Detalle sutil (importante)
Si usás solo Math.random() sin guardar el valor:
→ no podés decodificar después
👉 siempre:
-
o guardás el nonce
-
o lo embeddeás en el slug
🧩 TL;DR
✔ Sí, necesitás variar algo
✔ Lo mejor: seed + nonce por mensaje
✔ La permutación dejala fija
Si querés, puedo darte una versión:
👉 que integre el nonce dentro del slug sin separadores (más compacto, tipo ID real)
No exactamente—están muy relacionadas, pero no son lo mismo.
🧠 Diferencia clara
🔀 Permutación
Reordenar elementos según cualquier orden posible.
- Es un concepto general matemático
-
Puede aplicarse a:
- posiciones
- valores
- cualquier conjunto
Ejemplo:
ABC → CAB
Eso es una permutación.
🔁 Transposición
Reordenar posiciones de elementos, normalmente dentro de una estructura.
- Es un caso particular de permutación
-
En práctica (especialmente en cifrado clásico):
- no cambia los valores
- solo cambia dónde están
Ejemplo:
ABCDEF → ADBECF
🎯 En tu contexto (tu código)
Lo que estás haciendo tiene dos cosas distintas:
1. Permutación (posicional)
const PERM = [2, 0, 3, 1];
👉 Eso es:
- una permutación de índices
- usada como transposición por bloques
✔ técnicamente: permutación
✔ en cripto clásica: transposición
2. Sustitución (XOR)
byte ^ keystream
👉 eso:
- cambia el valor
- no la posición
🔥 Insight importante
En criptografía clásica hay dos categorías:
| Tipo | Qué hace |
|---|---|
| Transposición | cambia posiciones |
| Sustitución | cambia valores |
👉 Vos estás combinando ambas:
transposición + sustitución
🧠 Entonces…
✔ Toda transposición es una permutación
❌ No toda permutación es una transposición
🧩 TL;DR
- Permutación = cualquier reordenamiento
- Transposición = permutación aplicada a posiciones (sin cambiar valores)
-
Tu sistema usa:
- ✔ transposición (PERM)
- ✔ sustitución (XOR)
Si querés, puedo mostrarte cómo lo que hiciste se parece (simplificado) a cómo funcionan cifrados reales como AES (difusión + sustitución), que es justo esta misma idea pero mucho más sofisticada.
No hay comentarios:
Publicar un comentario