La aritmética modular permite construir transformaciones fáciles de calcular pero difíciles de invertir sin una clave. Esta idea sostiene el cifrado de clave pública, el intercambio de claves, las firmas digitales y numerosas verificaciones de seguridad.
La criptografía protege información frente a personas no autorizadas. Para lograrlo necesita operaciones que sean eficientes para quienes poseen una clave, pero que no revelen esa clave a partir de los datos públicos.
Las congruencias, las potencias modulares, los números coprimos y los inversos modulares que estudiamos en los temas anteriores forman el lenguaje matemático de varios sistemas criptográficos clásicos y modernos.
La seguridad de un sistema no se limita a ocultar un mensaje. Según el caso, se buscan una o varias propiedades:
El cifrado se centra en la confidencialidad. Los hashes, los códigos de autenticación y las firmas digitales cubren otras necesidades y no son intercambiables.
Trabajar módulo m mantiene los resultados en un conjunto finito de residuos. Así, una potencia muy grande se representa por un número entre 0 y m - 1 sin perder la información relevante para el algoritmo.
La criptografía no depende de que el módulo sea secreto. En los sistemas bien diseñados, los parámetros públicos pueden conocerse; la seguridad descansa en una clave privada y en problemas matemáticos difíciles.
Elevar una base a un exponente grande y reducir módulo m es una operación central. La exponenciación por cuadrados procesa los bits del exponente y reduce después de cada multiplicación.
function moduloBigInt(a, m) {
if (m <= 0n) throw new Error("el módulo debe ser positivo");
return ((a % m) + m) % m;
}
function potenciaModBigInt(base, exponente, m) {
if (exponente < 0n || m <= 0n) throw new Error("argumentos inválidos");
let resultado = 1n;
let factor = moduloBigInt(base, m);
while (exponente > 0n) {
if (exponente % 2n === 1n) resultado = (resultado * factor) % m;
factor = (factor * factor) % m;
exponente /= 2n;
}
return resultado;
}
console.log(potenciaModBigInt(7n, 100n, 13n)); // 9nUsamos BigInt porque los parámetros criptográficos exceden ampliamente el rango de enteros seguros de Number. El algoritmo mostrado ilustra la matemática; no constituye por sí solo una implementación criptográfica de producción.
Una función criptográfica suele ser fácil de aplicar en una dirección y difícil de invertir sin información adicional. Por ejemplo, calcular una potencia modular es eficiente; recuperar un exponente desconocido a partir de la base, el módulo y el resultado puede ser muy difícil en grupos elegidos adecuadamente.
La dificultad depende de la estructura matemática y del tamaño de los parámetros. Ejemplos pequeños sirven para aprender, pero son completamente vulnerables a una búsqueda exhaustiva.
Los números primos aparecen porque, módulo un primo p, todo residuo distinto de cero tiene inverso multiplicativo. Esto hace que las operaciones no nulas se comporten de forma especialmente regular.
En otros sistemas se usa un módulo compuesto, pero se elige de modo que ciertos valores sean coprimos con él. El algoritmo extendido de Euclides permite comprobar y calcular esos inversos.
En criptografía simétrica, emisor y receptor comparten una misma clave secreta; es muy eficiente para cifrar grandes cantidades de datos. En criptografía asimétrica se usa un par de claves: una pública y una privada relacionada matemáticamente.
La aritmética modular se usa con especial notoriedad en sistemas asimétricos. Pero un protocolo seguro requiere además formatos, autenticación, aleatoriedad y protección contra ataques de implementación.
RSA es un sistema de clave pública basado en un módulo n que es producto de dos primos grandes. Se publica n junto con un exponente e y se mantiene secreto un exponente d relacionado con e.
La relación entre e y d se define usando propiedades de los enteros coprimos con n. Conocer la factorización de n permite construir d; sin esa información, hacerlo para parámetros grandes se considera difícil.
Usaremos primos deliberadamente pequeños para observar las cuentas. Estos valores no proporcionan ninguna seguridad y nunca deben usarse fuera de un ejercicio.
La clave pública del ejemplo es (3233, 17). El exponente privado es 2753. En un sistema real, los primos deben ser generados de forma segura y tener tamaños muy grandes.
Si el mensaje numérico es m = 65, ciframos elevando a e módulo n. Para recuperar el mensaje, elevamos el resultado a d módulo n.
El ejemplo muestra la operación algebraica, no un formato de cifrado seguro. RSA real necesita relleno probabilístico y reglas de codificación estandarizadas para evitar ataques conocidos.
const n = 3233n;
const e = 17n;
const d = 2753n;
const mensaje = 65n;
const cifrado = potenciaModBigInt(mensaje, e, n);
const recuperado = potenciaModBigInt(cifrado, d, n);
console.log(cifrado); // 2790n
console.log(recuperado); // 65nEste código solo debe utilizarse como demostración en el navegador o en clase. No administra claves, no implementa relleno, no genera aleatoriedad segura y no protege contra ataques de canal lateral.
El valor d no se adivina: se calcula como el inverso modular de e respecto de φ(n), o de una cantidad relacionada usada por la variante concreta del sistema.
El algoritmo extendido de Euclides permite hallar este inverso de manera eficiente. Aquí se ve directamente cómo los temas 27 y 28 se integran en una aplicación criptográfica.
El intercambio de claves Diffie-Hellman permite que dos participantes acuerden un secreto compartido sobre un canal observado por terceros. Cada uno elige un exponente privado y publica una potencia modular.
Quien observa p, g, A y B no debería poder recuperar el secreto con parámetros correctos. Para ser seguro, el intercambio debe autenticarse; sin autenticación es vulnerable a un atacante que se interpone entre las partes.
Tomemos p = 23 y g = 5. Alicia elige a = 6 y Bob elige b = 15. Todos estos valores son demasiado pequeños para brindar seguridad.
El secreto de ejemplo se expone enseguida por fuerza bruta. Su valor pedagógico está en mostrar que los dos cálculos finales son congruentes, no en proporcionar un mecanismo utilizable.
Una firma digital permite verificar que un mensaje fue autorizado por quien posee una clave privada y que no se modificó desde que se firmó. Normalmente se firma un resumen criptográfico del mensaje, no el mensaje completo.
Las firmas no son simplemente «cifrar con la clave privada». Los esquemas modernos especifican con precisión el resumen, el formato y los pasos de verificación para impedir falsificaciones.
Estos conceptos suelen confundirse, pero cumplen funciones distintas:
Aplicar la herramienta equivocada deja objetivos sin cubrir. Por ejemplo, un hash público detecta cambios accidentales, pero no impide que un atacante reemplace mensaje y hash por otros coherentes.
Las claves privadas, los valores secretos temporales y ciertos rellenos deben ser impredecibles. Un generador habitual para simulaciones, como Math.random(), no está diseñado para esta tarea.
function bytesAleatorios(cantidad) {
const bytes = new Uint8Array(cantidad);
crypto.getRandomValues(bytes);
return bytes;
}
console.log(bytesAleatorios(16));La API Web Crypto proporciona primitivas del entorno para aplicaciones web. Aun así, usar bytes aleatorios correctamente requiere respetar el protocolo y el formato definidos por el algoritmo, no inventar una clave a partir de un ejemplo.
Un algoritmo matemáticamente correcto puede ser inseguro si se implementa mal. La criptografía práctica exige más que operar módulo un número grande.
La regla profesional es no diseñar primitivas criptográficas propias. Se eligen algoritmos y bibliotecas mantenidos, con formatos e interfaces que reduzcan la posibilidad de uso incorrecto.
La seguridad de varios sistemas depende de trabajar con enteros de cientos o miles de bits. Los ejemplos de este tema usan números pequeños porque son legibles; no representan el tamaño necesario en un sistema real.
JavaScript dispone de BigInt para enteros exactos arbitrarios, pero esa capacidad no reemplaza una biblioteca criptográfica. También importan la generación de parámetros, la resistencia a ataques de tiempo y la interoperabilidad con formatos estándar.
Las operaciones modulares aparecen detrás de acciones habituales: establecer una conexión segura con un sitio web, verificar actualizaciones de software, almacenar contraseñas mediante funciones especializadas y autenticar mensajes entre servicios.
La aplicación correcta depende del objetivo de seguridad. Por ejemplo, las contraseñas no deben cifrarse con un ejemplo RSA ni resumirse con una función rápida sin un esquema específico de almacenamiento.
Math.random() para generar material criptográfico.La aritmética modular proporciona la estructura matemática de varias herramientas de seguridad, pero la criptografía práctica exige rigor en cada capa. En el próximo tema cambiaremos de perspectiva y estudiaremos el principio del palomar, una técnica de conteo para demostrar que ciertas colisiones son inevitables.