Criptografía Aplicada

Fundamentos, implementaciones y mejores prácticas para proteger datos e información en aplicaciones modernas

¿Qué es la Criptografía Aplicada?

La Criptografía Aplicada es la ciencia práctica de proteger la información mediante técnicas matemáticas y algoritmos que transforman datos legibles en formatos aparentemente ininteligibles, y viceversa. En el desarrollo de software, estas técnicas son fundamentales para garantizar la confidencialidad, integridad, autenticidad y no repudio de los datos.

¿Por qué es importante?

En un mundo digital interconectado, la criptografía es la primera línea de defensa contra amenazas como el robo de datos, la suplantación de identidad y la manipulación de información. Según estudios recientes, el 43% de las brechas de seguridad podrían haberse evitado con una implementación adecuada de técnicas criptográficas.

Fundamentos de Criptografía

Cifrado Simétrico

El cifrado simétrico utiliza la misma clave para cifrar y descifrar datos. Es rápido y eficiente para grandes volúmenes de información, pero presenta el desafío de compartir la clave de forma segura.

Algoritmos comunes:

  • AES (Advanced Encryption Standard) - 128, 192, 256 bits
  • ChaCha20 - Alternativa moderna a AES
  • 3DES (Triple DES) - Menos usado actualmente

Características:

  • Alta velocidad de procesamiento
  • Eficiente para grandes volúmenes de datos
  • Requiere un canal seguro para intercambiar claves

Ejemplo: Código en javascript

const crypto = require('crypto');

// Función para cifrar datos con AES-256-GCM
function encryptData(plaintext, key) {
  // Generar un IV aleatorio
  const iv = crypto.randomBytes(12);
  
  // Crear cipher con AES-256-GCM
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  
  // Cifrar los datos
  let encrypted = cipher.update(plaintext, 'utf8', 'base64');
  encrypted += cipher.final('base64');
  
  // Obtener el tag de autenticación
  const authTag = cipher.getAuthTag();
  
  // Devolver IV, datos cifrados y tag de autenticación
  return {
    iv: iv.toString('base64'),
    encryptedData: encrypted,
    authTag: authTag.toString('base64')
  };
}

// Función para descifrar datos con AES-256-GCM
function decryptData(encryptedObj, key) {
  // Convertir IV y authTag de base64 a Buffer
  const iv = Buffer.from(encryptedObj.iv, 'base64');
  const authTag = Buffer.from(encryptedObj.authTag, 'base64');
  
  // Crear decipher
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
  
  // Establecer el tag de autenticación
  decipher.setAuthTag(authTag);
  
  // Descifrar los datos
  let decrypted = decipher.update(encryptedObj.encryptedData, 'base64', 'utf8');
  decrypted += decipher.final('utf8');
  
  return decrypted;
}

// Ejemplo de uso
const key = crypto.randomBytes(32); // Clave de 256 bits
const plaintext = 'Información confidencial que necesita protección';

const encrypted = encryptData(plaintext, key);
console.log('Datos cifrados:', encrypted);

const decrypted = decryptData(encrypted, key);
console.log('Datos descifrados:', decrypted);

Puntos clave de seguridad:

  • Nunca reutilizar la misma combinación de clave e IV (vector de inicialización)
  • Utilizar modos de operación seguros como GCM o CBC con HMAC
  • Generar claves con suficiente entropía (usando generadores de números aleatorios criptográficamente seguros)
  • Proteger las claves en reposo usando almacenamiento seguro de claves

Cifrado Asimétrico

El cifrado asimétrico utiliza un par de claves matemáticamente relacionadas: una pública para cifrar y una privada para descifrar. Resuelve el problema de intercambio de claves, pero es computacionalmente más costoso que el cifrado simétrico.

Algoritmos comunes:

  • RSA (Rivest-Shamir-Adleman)
  • ECC (Criptografía de Curva Elíptica)
  • ElGamal

Aplicaciones:

  • Firmas digitales
  • Intercambio seguro de claves
  • Certificados digitales
  • Infraestructura de clave pública (PKI)

Ejemplo: Código en python

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

# Generar par de claves RSA
def generate_rsa_keys():
    # Generar clave privada
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048
    )
    
    # Derivar clave pública
    public_key = private_key.public_key()
    
    # Serializar clave privada en formato PEM
    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    )
    
    # Serializar clave pública en formato PEM
    public_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    
    return private_pem, public_pem, private_key, public_key

# Cifrar datos con clave pública
def encrypt_with_rsa(public_key, plaintext):
    ciphertext = public_key.encrypt(
        plaintext.encode('utf-8'),
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return ciphertext

# Descifrar datos con clave privada
def decrypt_with_rsa(private_key, ciphertext):
    plaintext = private_key.decrypt(
        ciphertext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return plaintext.decode('utf-8')

# Ejemplo de uso
private_pem, public_pem, private_key, public_key = generate_rsa_keys()

# Mensaje a cifrar
message = "Mensaje confidencial para cifrado asimétrico"

# Cifrar con clave pública
encrypted = encrypt_with_rsa(public_key, message)
print(f"Mensaje cifrado: {encrypted.hex()}")

# Descifrar con clave privada
decrypted = decrypt_with_rsa(private_key, encrypted)
print(f"Mensaje descifrado: {decrypted}")

Puntos clave de seguridad:

  • Usar tamaños de clave adecuados: mínimo 2048 bits para RSA, 256 bits para ECC
  • Implementar padding seguro como OAEP para RSA
  • Proteger las claves privadas con medidas adicionales de seguridad
  • Utilizar bibliotecas criptográficas probadas en lugar de implementaciones propias

Funciones Hash Criptográficas

Las funciones hash criptográficas transforman datos de entrada de cualquier tamaño en una cadena de salida de longitud fija. Son fundamentales para verificar la integridad de los datos y almacenar contraseñas de forma segura.

Algoritmos comunes:

  • SHA-256, SHA-384, SHA-512 (familia SHA-2)
  • SHA3-256, SHA3-512 (familia SHA-3)
  • BLAKE2, BLAKE3
  • MD5, SHA-1 (obsoletos, no usar)

Propiedades:

  • Unidireccionalidad: imposible recuperar la entrada desde el hash
  • Determinismo: la misma entrada siempre produce el mismo hash
  • Efecto avalancha: pequeños cambios en la entrada producen hashes completamente diferentes
  • Resistencia a colisiones: difícil encontrar dos entradas con el mismo hash

Ejemplo: Código en javascript

const argon2 = require('argon2');

// Función para hashear una contraseña con Argon2id
async function hashPassword(password) {
  try {
    // Configuración recomendada para Argon2id
    const hash = await argon2.hash(password, {
      type: argon2.argon2id,      // Variante más segura
      memoryCost: 65536,          // 64 MiB en KB
      timeCost: 3,                // Número de iteraciones
      parallelism: 4,             // Grado de paralelismo
      hashLength: 32,             // Longitud del hash en bytes
    });
    
    return hash;
  } catch (error) {
    console.error('Error al hashear la contraseña:', error);
    throw error;
  }
}

// Función para verificar una contraseña contra un hash almacenado
async function verifyPassword(hash, password) {
  try {
    return await argon2.verify(hash, password);
  } catch (error) {
    console.error('Error al verificar la contraseña:', error);
    return false;
  }
}

// Ejemplo de uso
async function main() {
  const password = 'Contraseña.Segura.123';
  
  // Hashear la contraseña para almacenarla
  const hash = await hashPassword(password);
  console.log('Hash generado:', hash);
  
  // Verificar contraseña correcta
  const isValid = await verifyPassword(hash, password);
  console.log('¿Contraseña válida?', isValid); // true
  
  // Verificar contraseña incorrecta
  const isInvalid = await verifyPassword(hash, 'ContraseñaIncorrecta');
  console.log('¿Contraseña incorrecta válida?', isInvalid); // false
}

main();

Puntos clave de seguridad:

  • Nunca almacenar contraseñas en texto plano o con hashes simples (MD5, SHA-1)
  • Usar funciones de derivación de claves como Argon2, bcrypt o PBKDF2 para contraseñas
  • Añadir un salt único para cada contraseña (las funciones modernas lo hacen automáticamente)
  • Ajustar los factores de coste (tiempo, memoria) según las necesidades de seguridad

Protocolos Criptográficos

TLS/SSL y HTTPS

TLS (Transport Layer Security) y su predecesor SSL (Secure Sockets Layer) son protocolos que proporcionan comunicaciones seguras en Internet. HTTPS es HTTP sobre TLS/SSL, asegurando que todas las comunicaciones entre el navegador y el servidor web estén cifradas.

Componentes clave:

  • Certificados X.509
  • Intercambio de claves (RSA, DH, ECDHE)
  • Cifrado simétrico para datos (AES, ChaCha20)
  • Funciones hash para integridad (SHA-256, SHA-384)

Beneficios:

  • Confidencialidad de datos transmitidos
  • Integridad de datos
  • Autenticación del servidor (y opcionalmente del cliente)
  • Protección contra ataques de intermediario (MitM)

Ejemplo: Código en javascript

const https = require('https');
const fs = require('fs');
const express = require('express');

const app = express();

// Middleware y rutas
app.get('/', (req, res) => {
  res.send('¡Servidor HTTPS seguro funcionando!');
});

// Opciones de TLS con configuración segura
const options = {
  key: fs.readFileSync('ruta/a/clave-privada.key'),
  cert: fs.readFileSync('ruta/a/certificado.crt'),
  // Configuración moderna y segura de TLS
  minVersion: 'TLSv1.2',
  // Cifrados recomendados (orden de preferencia)
  ciphers: [
    'TLS_AES_256_GCM_SHA384',
    'TLS_CHACHA20_POLY1305_SHA256',
    'TLS_AES_128_GCM_SHA256',
    'ECDHE-RSA-AES256-GCM-SHA384',
    'ECDHE-RSA-AES128-GCM-SHA256',
    'ECDHE-RSA-CHACHA20-POLY1305'
  ].join(':'),
  // Habilitar Perfect Forward Secrecy
  honorCipherOrder: true,
  // Opciones HSTS
  maxAge: 31536000, // 1 año en segundos
  includeSubDomains: true,
  preload: true
};

// Crear servidor HTTPS
const server = https.createServer(options, app);

// Iniciar servidor
server.listen(443, () => {
  console.log('Servidor HTTPS ejecutándose en el puerto 443');
});

// Redirigir HTTP a HTTPS
const http = require('http');
http.createServer((req, res) => {
  res.writeHead(301, { 'Location': 'https://' + req.headers.host + req.url });
  res.end();
}).listen(80);

Puntos clave de seguridad:

  • Usar TLS 1.2 o superior (TLS 1.0 y 1.1 están obsoletos)
  • Configurar cifrados seguros y deshabilitar algoritmos débiles
  • Implementar HSTS (HTTP Strict Transport Security)
  • Utilizar certificados válidos de autoridades de certificación confiables
  • Configurar Perfect Forward Secrecy (PFS) para proteger datos pasados

Autenticación y Autorización

Los protocolos de autenticación y autorización permiten verificar la identidad de usuarios y sistemas, y determinar sus niveles de acceso. Estos protocolos son fundamentales para la seguridad de aplicaciones web y APIs.

Protocolos comunes:

  • OAuth 2.0
  • OpenID Connect
  • SAML (Security Assertion Markup Language)
  • JWT (JSON Web Tokens)

Componentes clave:

  • Tokens de acceso y actualización
  • Firmas digitales
  • Flujos de autenticación
  • Gestión de sesiones

Ejemplo: Código en javascript

const jwt = require('jsonwebtoken');
const crypto = require('crypto');

// Generar una clave secreta segura (en producción, almacenar de forma segura)
const generateSecretKey = () => {
  return crypto.randomBytes(64).toString('hex');
};

// Clave secreta para firmar y verificar tokens
const secretKey = generateSecretKey();

// Función para crear un token JWT
function createToken(payload, options = {}) {
  // Configuración predeterminada segura
  const defaultOptions = {
    expiresIn: '1h',        // Tiempo de expiración corto
    algorithm: 'HS256',     // Algoritmo de firma
    issuer: 'mi-aplicacion', // Emisor del token
    audience: 'mi-api',     // Audiencia del token
    jwtid: crypto.randomBytes(16).toString('hex') // ID único para prevenir reutilización
  };
  
  // Combinar opciones predeterminadas con las proporcionadas
  const tokenOptions = { ...defaultOptions, ...options };
  
  // Crear y firmar el token
  return jwt.sign(payload, secretKey, tokenOptions);
}

// Función para verificar un token JWT
function verifyToken(token) {
  try {
    // Verificar y decodificar el token
    const decoded = jwt.verify(token, secretKey, {
      algorithms: ['HS256'],      // Limitar a algoritmos seguros
      issuer: 'mi-aplicacion',    // Verificar emisor
      audience: 'mi-api',         // Verificar audiencia
      clockTolerance: 30          // Tolerancia de 30 segundos para diferencias de reloj
    });
    
    return { valid: true, payload: decoded };
  } catch (error) {
    return { 
      valid: false, 
      error: error.name, 
      message: error.message 
    };
  }
}

// Ejemplo de uso
const userData = {
  id: '12345',
  username: 'usuario_ejemplo',
  role: 'user'
};

// Crear token
const token = createToken(userData);
console.log('Token JWT:', token);

// Verificar token válido
const verification = verifyToken(token);
console.log('Verificación:', verification);

// Verificar token manipulado
const tampered = token.slice(0, -5) + 'xxxxx';
const tamperedVerification = verifyToken(tampered);
console.log('Verificación de token manipulado:', tamperedVerification);

Puntos clave de seguridad:

  • Usar tiempos de expiración cortos para tokens de acceso
  • Implementar rotación de tokens con tokens de actualización
  • Validar todos los campos del token (emisor, audiencia, tiempo)
  • Almacenar claves secretas de forma segura y rotarlas periódicamente
  • Implementar revocación de tokens para sesiones comprometidas

Criptografía en Blockchain y Contratos Inteligentes

Fundamentos Criptográficos en Blockchain

Blockchain es una tecnología de registro distribuido que garantiza la inmutabilidad y transparencia de la información mediante criptografía y consenso descentralizado. Su diseño resistente a manipulaciones lo hace ideal para almacenar transacciones, registros digitales y datos auditables

Componentes clave:

  • Funciones hash criptográficas (SHA-256, Keccak-256)
  • Firmas digitales (ECDSA, EdDSA)
  • Merkle Trees para verificación de datos
  • Algoritmos de consenso (PoW, PoS)

Beneficios:

  • Integridad e inmutabilidad de los datos
  • Verificación descentralizada
  • Resistencia a la censura y manipulación
  • Transparencia en entornos distribuidos

Ejemplo: Código en javascript

const crypto = require('crypto');

// Función que aplica SHA-256 a un string
function sha256(data) {
  return crypto.createHash('sha256').update(data).digest('hex');
}

// Simulamos un conjunto de transacciones dentro de un bloque
const transactions = ["Tx1", "Tx2", "Tx3", "Tx4"];

// Ciframos cada transacción con SHA-256
const hashedTxs = transactions.map(sha256);

// Función recursiva para construir la raíz de Merkle
function buildMerkleRoot(hashes) {
  // Caso base: si solo queda un hash, es la raíz
  if (hashes.length === 1) return hashes[0];

  const newLevel = [];
  for (let i = 0; i < hashes.length; i += 2) {
    // Tomamos pares de hashes y los concatenamos
    const left = hashes[i];
    const right = hashes[i + 1] || left; // Duplicamos si hay número impar

    // Hasheamos el resultado y lo agregamos al nuevo nivel
    newLevel.push(sha256(left + right));
  }

  // Repetimos hasta obtener una sola raíz
  return buildMerkleRoot(newLevel);
}

const merkleRoot = buildMerkleRoot(hashedTxs);
console.log("Merkle Root:", merkleRoot);

Puntos clave de seguridad:

  • Evitar algoritmos hash obsoletos (como SHA-1)
  • Usar claves privadas almacenadas en entornos seguros
  • Verificar correctamente las firmas de transacciones
  • Auditar los contratos inteligentes para evitar errores lógicos

Contratos Inteligentes y Seguridad Criptográfica

Son programas autoejecutables desplegados en una blockchain que gestionan acuerdos de forma automática y sin intermediarios. Su lógica codificada permite garantizar cumplimiento, transparencia y seguridad en diversas aplicaciones como finanzas, logística o identidad digital.

Tecnologías involucradas:

  • Lenguajes como Solidity o Vyper
  • Firmas digitales (ECDSA)
  • Direcciones derivadas de claves públicas
  • Transacciones criptográficamente firmadas

Aplicaciones prácticas:

  • Sistemas DeFi y préstamos automáticos
  • Mercados NFT
  • Automatización de pagos
  • Gobernanza descentralizada (DAO)

Ejemplo: Código en solidity

pragma solidity ^0.8.0;

contract SecureWallet {
    // Dirección del propietario de la wallet
    address public owner;

    // Eventos para registrar depósitos y transferencias
    event Deposited(address indexed from, uint amount);
    event Transferred(address indexed to, uint amount);

    // Constructor: se guarda el creador como dueño
    constructor() {
        owner = msg.sender;
    }

    // Modificador para restringir acceso solo al dueño
    modifier onlyOwner() {
        require(msg.sender == owner, "No autorizado");
        _;
    }

    // Función para recibir fondos
    function deposit() external payable {
        emit Deposited(msg.sender, msg.value);
    }

    // Función que transfiere fondos solo si es el dueño
    function transferFunds(address payable to, uint amount) external onlyOwner {
        require(address(this).balance >= amount, "Fondos insuficientes");
        to.transfer(amount);
        emit Transferred(to, amount);
    }

    // Obtener el balance actual del contrato
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

Puntos clave de seguridad:

  • Validar entradas y salidas del contrato
  • Evitar ataques de reentrancia
  • Limitar el uso de funciones críticas a propietarios
  • Usar herramientas como Slither o MythX para análisis

Criptografía Avanzada

Criptografía de Curva Elíptica (ECC)

La Criptografía de Curva Elíptica (ECC) ofrece el mismo nivel de seguridad que RSA pero con claves mucho más pequeñas, lo que la hace ideal para dispositivos con recursos limitados y aplicaciones móviles.

Ventajas:

  • Claves más pequeñas (256 bits ECC ≈ 3072 bits RSA)
  • Menor consumo de recursos computacionales
  • Mejor rendimiento en dispositivos móviles
  • Ideal para IoT y sistemas embebidos

Curvas populares:

  • Curve25519 / X25519 (intercambio de claves)
  • Ed25519 (firmas digitales)
  • P-256 (NIST)
  • secp256k1 (utilizada en Bitcoin y blockchain)

Ejemplo: Código en javascript

//Ejemplo: Firma digital con Ed25519

const nacl = require('tweetnacl');
const util = require('tweetnacl-util');

// Convertir string a Uint8Array y viceversa
const { encodeUTF8, decodeUTF8, encodeBase64, decodeBase64 } = util;

// Función para generar par de claves Ed25519
function generateKeyPair() {
  const keyPair = nacl.sign.keyPair();
  return {
    publicKey: encodeBase64(keyPair.publicKey),
    secretKey: encodeBase64(keyPair.secretKey)
  };
}

// Función para firmar un mensaje
function signMessage(message, secretKeyBase64) {
  const messageUint8 = decodeUTF8(message);
  const secretKey = decodeBase64(secretKeyBase64);
  const signature = nacl.sign.detached(messageUint8, secretKey);
  return encodeBase64(signature);
}

// Función para verificar una firma
function verifySignature(message, signature, publicKeyBase64) {
  const messageUint8 = decodeUTF8(message);
  const signatureUint8 = decodeBase64(signature);
  const publicKey = decodeBase64(publicKeyBase64);
  
  return nacl.sign.detached.verify(messageUint8, signatureUint8, publicKey);
}

// Ejemplo de uso
const message = 'Mensaje importante que necesita ser firmado';

// Generar par de claves
const keyPair = generateKeyPair();
console.log('Clave pública:', keyPair.publicKey);
console.log('Clave privada:', keyPair.secretKey);

// Firmar mensaje
const signature = signMessage(message, keyPair.secretKey);
console.log('Firma:', signature);

// Verificar firma (debería ser true)
const isValid = verifySignature(message, signature, keyPair.publicKey);
console.log('¿Firma válida?', isValid);

// Verificar firma con mensaje modificado (debería ser false)
const isValidModified = verifySignature(message + ' modificado', signature, keyPair.publicKey);
console.log('¿Firma válida con mensaje modificado?', isValidModified);

Puntos clave de seguridad:

  • Elegir curvas seguras y estandarizadas (Curve25519, P-256)
  • Utilizar implementaciones validadas y bibliotecas probadas
  • Proteger las claves privadas con el mismo cuidado que en RSA
  • Verificar la generación de números aleatorios para la creación de claves

Cifrado Homomórfico

Permite realizar operaciones matemáticas directamente sobre datos cifrados, sin necesidad de descifrarlos previamente. Esto posibilita el procesamiento seguro de información sensible en entornos no confiables, como servicios en la nube.

Tipos de cifrado homomórfico:

  • Parcialmente homomórfico (PHE)
  • Homomórfico por capas (Somewhat HE)
  • Totalmente homomórfico (FHE)

Ventajas:

  • Procesamiento seguro de datos sensibles
  • Aplicaciones en salud, finanzas y datos privados
  • Reducción de exposición de datos a terceros
  • Cálculo seguro en la nube

Ejemplo: Código en python

from phe import paillier

# Generamos un par de claves públicas/privadas
public_key, private_key = paillier.generate_paillier_keypair()

# Simulamos salarios confidenciales
salaries = [3500, 4200, 3900]

# Ciframos cada salario con la clave pública
encrypted_salaries = [public_key.encrypt(s) for s in salaries]

# Sumamos los salarios cifrados (sin descifrar)
total_encrypted = sum(encrypted_salaries)

# Desciframos el total para obtener la suma real
total_decrypted = private_key.decrypt(total_encrypted)

# Calculamos el promedio (de forma local)
average_salary = total_decrypted / len(salaries)

print("Total descifrado:", total_decrypted)
print("Promedio salarial:", average_salary)

Puntos clave de seguridad:

  • Los algoritmos homomórficos requieren mayor tiempo de procesamiento
  • Evitar filtrado lateral de datos en el entorno de ejecución
  • Usar bibliotecas confiables y auditadas
  • Proteger las claves privadas con medidas adicionales

Computación Multipartita Segura (SMPC)

Ppermite a varias partes colaborar en el cálculo de una función sin revelar sus datos privados entre ellas. Es fundamental en escenarios donde se requiere cooperación con alta privacidad, como análisis entre empresas o votaciones digitales.

Técnicas comunes:

  • Secret sharing (Shamir)
  • Oblivious Transfer
  • Garbled Circuits
  • ZK-SNARKs y ZK-STARKs

Usos frecuentes:

  • Votaciones seguras
  • Subastas sin revelación de pujas
  • Cómputos colaborativos privados
  • Protección de datos en IA federada

Ejemplo: Código en python

from mpyc.runtime import mpc

# Inicializamos un tipo seguro entero (32 bits por defecto)
secint = mpc.SecInt()

# Arrancamos el entorno seguro
await mpc.start()

# Votos individuales simulados (1 = sí, 0 = no), ocultos en forma cifrada
votes = [secint(1), secint(0), secint(1), secint(1)]

# Suma segura de los votos (sin conocer su valor individual)
total_votes = sum(votes)

# Obtenemos el resultado de la suma revelando solo el total
result = await mpc.output(total_votes)

print("Votos a favor:", result, "de", len(votes))

# Cerramos el entorno seguro
await mpc.shutdown()

Puntos clave de seguridad:

  • La implementación debe seguir un modelo de amenaza claro
  • Minimizar fugas de información durante la comunicación
  • Utilizar canales seguros entre participantes
  • Verificar que las librerías estén bien mantenidas

Criptografía Cuántica y Post-Cuántica

La computación cuántica representa una amenaza para muchos algoritmos criptográficos actuales. La criptografía post-cuántica desarrolla algoritmos resistentes a ataques de computadoras cuánticas.

Algoritmos vulnerables a ataques cuánticos:

  • RSA (factorización de números)
  • ECC (logaritmo discreto)
  • Diffie-Hellman (logaritmo discreto)

Algoritmos post-cuánticos:

  • Lattice-based (NTRU, CRYSTALS-Kyber)
  • Hash-based (SPHINCS+)
  • Code-based (McEliece)
  • Multivariate-based (Rainbow)

Ejemplo: Código en javascript

// Nota: Este es un ejemplo conceptual, ya que las implementaciones
// de criptografía post-cuántica aún están en desarrollo y estandarización

const kyber = require('kyber-crystals');

// Generar par de claves Kyber
function generateKyberKeys() {
  // Kyber-768 ofrece seguridad de nivel 3 (equivalente a AES-192)
  const keyPair = kyber.keyPair(768);
  return {
    publicKey: keyPair.publicKey,
    secretKey: keyPair.secretKey
  };
}

// Encapsular una clave compartida usando la clave pública
function encapsulate(publicKey) {
  const result = kyber.encapsulate(publicKey);
  return {
    sharedSecret: result.sharedSecret, // Clave simétrica para cifrado
    ciphertext: result.ciphertext      // Datos para que el receptor derive la misma clave
  };
}

// Desencapsular para obtener la misma clave compartida
function decapsulate(ciphertext, secretKey) {
  const sharedSecret = kyber.decapsulate(ciphertext, secretKey);
  return sharedSecret;
}

// Ejemplo de uso
async function secureExchange() {
  // Generar claves para el receptor
  const receiverKeys = generateKyberKeys();
  
  // Remitente encapsula una clave compartida
  const { sharedSecret: senderSharedSecret, ciphertext } = encapsulate(receiverKeys.publicKey);
  
  // Receptor desencapsula para obtener la misma clave compartida
  const receiverSharedSecret = decapsulate(ciphertext, receiverKeys.secretKey);
  
  // Ambas partes ahora tienen la misma clave secreta
  console.log('¿Claves iguales?', Buffer.compare(senderSharedSecret, receiverSharedSecret) === 0);
  
  // Esta clave compartida puede usarse para cifrado simétrico (AES)
  // const aesKey = sharedSecret.slice(0, 32); // 256 bits para AES-256
}

secureExchange();

Puntos clave de seguridad:

  • Mantenerse informado sobre los estándares emergentes del NIST
  • Implementar criptografía híbrida (tradicional + post-cuántica)
  • Diseñar sistemas con agilidad criptográfica para facilitar la transición
  • Evaluar el impacto en el rendimiento de los algoritmos post-cuánticos
  • Comenzar a planificar la migración de sistemas críticos

Implementaciones Prácticas

Firmas Digitales

Las firmas digitales proporcionan autenticidad, integridad y no repudio a los documentos electrónicos. Combinan criptografía asimétrica y funciones hash para crear un mecanismo que verifica tanto el origen como la integridad de los datos.

Proceso de firma:

  • Generar un hash del documento original
  • Cifrar el hash con la clave privada del firmante
  • Distribuir el documento junto con la firma
  • El receptor verifica usando la clave pública del firmante

Aplicaciones:

  • Documentos legales electrónicos
  • Certificados digitales
  • Código de software (verificación de integridad)
  • Transacciones financieras
  • Correos electrónicos firmados (S/MIME, PGP)

Ejemplo: Código en java

import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;

public class DigitalSignatureExample {

    // Generar par de claves RSA
    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048); // Tamaño de clave recomendado
        return keyPairGenerator.generateKeyPair();
    }

    // Firmar datos con clave privada
    public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    // Verificar firma con clave pública
    public static boolean verify(byte[] data, byte[] signatureBytes, PublicKey publicKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(signatureBytes);
    }

    public static void main(String[] args) {
        try {
            // Generar par de claves
            KeyPair keyPair = generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();

            // Datos a firmar (podría ser un archivo)
            String message = "Este es un documento importante que necesita ser firmado digitalmente";
            byte[] data = message.getBytes();

            // Firmar los datos
            byte[] signatureBytes = sign(data, privateKey);
            String signatureBase64 = Base64.getEncoder().encodeToString(signatureBytes);
            System.out.println("Firma digital: " + signatureBase64);

            // Verificar la firma (simulando recepción)
            boolean isValid = verify(data, signatureBytes, publicKey);
            System.out.println("¿Firma válida? " + isValid);

            // Verificar con datos modificados (debe fallar)
            String modifiedMessage = message + " con modificación no autorizada";
            byte[] modifiedData = modifiedMessage.getBytes();
            boolean isValidModified = verify(modifiedData, signatureBytes, publicKey);
            System.out.println("¿Firma válida con datos modificados? " + isValidModified);

        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Puntos clave de seguridad:

  • Utilizar algoritmos de firma robustos como RSA-PSS, ECDSA o Ed25519
  • Proteger las claves privadas con medidas estrictas de seguridad
  • Implementar validación completa de certificados en la cadena de confianza
  • Usar funciones hash seguras (SHA-256 o superior) en el proceso de firma
  • Considerar la revocación de certificados en caso de compromiso de claves

Secure Shell (SSH)

SSH (Secure Shell) es un protocolo criptográfico para operar servicios de red de forma segura sobre una red no segura. Proporciona un canal cifrado para la comunicación de datos, autenticación fuerte y gestión de sesiones seguras.

Características principales:

  • Autenticación por clave pública/privada
  • Cifrado de datos en tránsito
  • Integridad de datos mediante MACs
  • Reenvío de puertos (port forwarding)
  • Túneles seguros para otros protocolos

Aplicaciones comunes:

  • Administración remota de servidores
  • Transferencia segura de archivos (SFTP, SCP)
  • Túneles para bases de datos y servicios internos
  • Automatización de despliegues (CI/CD)
  • Acceso seguro a repositorios Git

Ejemplo: Código en python

import paramiko
import os
import socket
import sys

class SecureShellClient:
    def __init__(self, hostname, port=22, username=None, key_filename=None, password=None):
        self.hostname = hostname
        self.port = port
        self.username = username
        self.key_filename = key_filename
        self.password = password
        self.client = None
        self.session_log = []
    
    def connect(self):
        """Establece una conexión SSH segura con el servidor"""
        try:
            # Inicializar cliente SSH
            self.client = paramiko.SSHClient()
            
            # Configuración de seguridad para host keys
            # En producción, usar 'system' o implementar verificación personalizada
            self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            
            # Registrar intento de conexión
            self.log_action(f"Conectando a {self.hostname}:{self.port} como {self.username}")
            
            # Conectar usando autenticación por clave o contraseña
            if self.key_filename:
                self.client.connect(
                    hostname=self.hostname,
                    port=self.port,
                    username=self.username,
                    key_filename=self.key_filename,
                    timeout=10
                )
                self.log_action("Autenticado con clave privada")
            elif self.password:
                self.client.connect(
                    hostname=self.hostname,
                    port=self.port,
                    username=self.username,
                    password=self.password,
                    timeout=10
                )
                self.log_action("Autenticado con contraseña")
            else:
                raise ValueError("Se requiere clave privada o contraseña para la autenticación")
                
            return True
            
        except paramiko.AuthenticationException:
            self.log_action("Error: Falló la autenticación")
            return False
        except paramiko.SSHException as e:
            self.log_action(f"Error SSH: {str(e)}")
            return False
        except socket.error as e:
            self.log_action(f"Error de conexión: {str(e)}")
            return False
    
    def execute_command(self, command):
        """Ejecuta un comando en el servidor remoto"""
        if not self.client:
            raise RuntimeError("No hay conexión SSH establecida")
        
        self.log_action(f"Ejecutando comando: {command}")
        
        # Crear canal para el comando
        stdin, stdout, stderr = self.client.exec_command(command)
        
        # Obtener resultados
        output = stdout.read().decode('utf-8')
        error = stderr.read().decode('utf-8')
        
        if error:
            self.log_action(f"Error en comando: {error}")
        
        return output
    
    def upload_file(self, local_path, remote_path):
        """Sube un archivo al servidor remoto usando SFTP"""
        if not self.client:
            raise RuntimeError("No hay conexión SSH establecida")
        
        self.log_action(f"Subiendo archivo: {local_path} -> {remote_path}")
        
        try:
            # Abrir sesión SFTP
            sftp = self.client.open_sftp()
            
            # Subir archivo
            sftp.put(local_path, remote_path)
            
            # Cerrar sesión SFTP
            sftp.close()
            
            return True
        except Exception as e:
            self.log_action(f"Error al subir archivo: {str(e)}")
            return False
    
    def download_file(self, remote_path, local_path):
        """Descarga un archivo del servidor remoto usando SFTP"""
        if not self.client:
            raise RuntimeError("No hay conexión SSH establecida")
        
        self.log_action(f"Descargando archivo: {remote_path} -> {local_path}")
        
        try:
            # Abrir sesión SFTP
            sftp = self.client.open_sftp()
            
            # Descargar archivo
            sftp.get(remote_path, local_path)
            
            # Cerrar sesión SFTP
            sftp.close()
            
            return True
        except Exception as e:
            self.log_action(f"Error al descargar archivo: {str(e)}")
            return False
    
    def close(self):
        """Cierra la conexión SSH"""
        if self.client:
            self.client.close()
            self.log_action("Conexión SSH cerrada")
            self.client = None
    
    def log_action(self, message):
        """Registra acciones para auditoría"""
        self.session_log.append(message)
        print(message)

# Ejemplo de uso
if __name__ == "__main__":
    # Conexión con clave privada (más seguro)
    ssh = SecureShellClient(
        hostname="ejemplo.servidor.com",
        username="usuario",
        key_filename="~/.ssh/id_rsa"
    )
    
    # Alternativa: conexión con contraseña (menos seguro)
    # ssh = SecureShellClient(
    #     hostname="ejemplo.servidor.com",
    #     username="usuario",
    #     password="contraseña_segura"
    # )
    
    if ssh.connect():
        # Ejecutar comandos
        output = ssh.execute_command("ls -la")
        print(f"Resultado del comando:\n{output}")
        
        # Transferir archivos
        ssh.upload_file("archivo_local.txt", "/ruta/remota/archivo.txt")
        ssh.download_file("/ruta/remota/datos.csv", "datos_locales.csv")
        
        # Cerrar conexión
        ssh.close()

Puntos clave de seguridad:

  • Usar autenticación por clave pública en lugar de contraseñas
  • Deshabilitar el acceso root directo
  • Implementar autenticación de dos factores (2FA)
  • Limitar los intentos de inicio de sesión y usar listas blancas de IP
  • Mantener el software SSH actualizado para parchar vulnerabilidades
  • Configurar algoritmos criptográficos seguros y deshabilitar los obsoletos

Zero-Knowledge Proofs (ZKP)

Las pruebas de conocimiento cero (Zero-Knowledge Proofs) son protocolos criptográficos que permiten a una parte (el probador) demostrar a otra parte (el verificador) que conoce un valor o secreto, sin revelar ninguna información sobre el secreto mismo.

Propiedades clave:

  • Completitud: Si la afirmación es verdadera, el verificador quedará convencido
  • Solidez: Si la afirmación es falsa, el probador no podrá convencer al verificador
  • Conocimiento cero: El verificador no aprende nada más que la veracidad de la afirmación

Aplicaciones:

  • Autenticación sin revelar contraseñas
  • Transacciones privadas en blockchain
  • Votación electrónica anónima
  • Verificación de identidad preservando la privacidad
  • Auditorías confidenciales

Ejemplo: Código en javascript

const crypto = require('crypto');

/**
 * Implementación simplificada de una prueba de conocimiento cero para verificar
 * que un usuario conoce una contraseña sin revelarla.
 * 
 * Este es un ejemplo educativo que ilustra el concepto de ZKP.
 * Para aplicaciones reales, use bibliotecas criptográficas especializadas.
 */
class PasswordZKP {
  /**
   * Configuración inicial para el protocolo
   * @param {string} password - La contraseña que el probador conoce
   */
  constructor(password) {
    // Generar un salt aleatorio para el hash
    this.salt = crypto.randomBytes(16).toString('hex');
    
    // Calcular el hash de la contraseña (esto se almacenaría en el servidor)
    this.passwordHash = this.hashPassword(password);
    
    // Número de rondas para la verificación
    this.rounds = 10;
  }
  
  /**
   * Función para hashear la contraseña con el salt
   * @param {string} password - La contraseña a hashear
   * @returns {string} - El hash resultante en formato hex
   */
  hashPassword(password) {
    return crypto.createHash('sha256')
      .update(password + this.salt)
      .digest('hex');
  }
  
  /**
   * Genera un desafío aleatorio para una ronda de verificación
   * @returns {string} - Un desafío aleatorio
   */
  generateChallenge() {
    return crypto.randomBytes(32).toString('hex');
  }
  
  /**
   * El probador genera un compromiso basado en la contraseña y un valor aleatorio
   * @param {string} password - La contraseña del usuario
   * @returns {Object} - El compromiso y el valor aleatorio usado
   */
  generateCommitment(password) {
    // Generar un valor aleatorio (nonce) para esta ronda
    const nonce = crypto.randomBytes(32).toString('hex');
    
    // Crear un compromiso combinando la contraseña con el nonce
    const commitment = crypto.createHash('sha256')
      .update(password + nonce)
      .digest('hex');
    
    return { commitment, nonce };
  }
  
  /**
   * El probador genera una respuesta al desafío
   * @param {string} password - La contraseña del usuario
   * @param {string} challenge - El desafío recibido
   * @param {string} nonce - El nonce usado en el compromiso
   * @returns {string} - La respuesta al desafío
   */
  generateResponse(password, challenge, nonce) {
    // Combinar la contraseña, el nonce y el desafío
    return crypto.createHash('sha256')
      .update(password + nonce + challenge)
      .digest('hex');
  }
  
  /**
   * El verificador comprueba si la respuesta es correcta
   * @param {string} commitment - El compromiso inicial
   * @param {string} challenge - El desafío enviado
   * @param {string} response - La respuesta recibida
   * @returns {boolean} - True si la verificación es exitosa
   */
  verifyResponse(commitment, challenge, response) {
    // En una implementación real, el verificador reconstruiría la respuesta
    // usando el hash almacenado de la contraseña, el desafío y el compromiso
    
    // Para simplificar, aquí solo verificamos que la respuesta sea consistente
    // con el compromiso y el desafío
    
    // Nota: Esta es una simplificación y no es una verdadera ZKP criptográficamente segura
    const expectedPattern = commitment.substring(0, 8) + challenge.substring(0, 8);
    return response.startsWith(expectedPattern);
  }
  
  /**
   * Ejecuta el protocolo completo de verificación
   * @param {string} password - La contraseña que el probador afirma conocer
   * @returns {boolean} - True si la verificación es exitosa
   */
  verify(password) {
    // Verificar primero si el hash de la contraseña coincide
    // Esto simula la verificación tradicional (no ZKP)
    const providedHash = this.hashPassword(password);
    if (providedHash !== this.passwordHash) {
      console.log("❌ Verificación tradicional fallida: Hash incorrecto");
      return false;
    }
    
    console.log("✅ Verificación tradicional exitosa: Hash correcto");
    console.log("Iniciando protocolo de conocimiento cero...");
    
    // Ejecutar múltiples rondas de verificación ZKP
    let allRoundsSuccessful = true;
    
    for (let round = 1; round <= this.rounds; round++) {
      console.log(`
Ronda ${round}/${this.rounds}:`);
      
      // Paso 1: El probador genera un compromiso
      const { commitment, nonce } = this.generateCommitment(password);
      console.log(`- Probador envía compromiso: ${commitment.substring(0, 16)}...`);
      
      // Paso 2: El verificador envía un desafío
      const challenge = this.generateChallenge();
      console.log(`- Verificador envía desafío: ${challenge.substring(0, 16)}...`);
      
      // Paso 3: El probador genera una respuesta al desafío
      const response = this.generateResponse(password, challenge, nonce);
      console.log(`- Probador envía respuesta: ${response.substring(0, 16)}...`);
      
      // Paso 4: El verificador comprueba la respuesta
      const isValid = this.verifyResponse(commitment, challenge, response);
      
      if (isValid) {
        console.log(`✅ Ronda ${round}: Verificación exitosa`);
      } else {
        console.log(`❌ Ronda ${round}: Verificación fallida`);
        allRoundsSuccessful = false;
        break;
      }
    }
    
    return allRoundsSuccessful;
  }
}

// Ejemplo de uso
const secretPassword = "mi_contraseña_secreta";

// Configurar el protocolo
const zkp = new PasswordZKP(secretPassword);

// Verificar con la contraseña correcta
console.log("Verificando con contraseña correcta:");
const isValidCorrect = zkp.verify(secretPassword);
console.log(`
Resultado final: ${isValidCorrect ? "✅ Verificación exitosa" : "❌ Verificación fallida"}`);

// Verificar con una contraseña incorrecta
console.log("
-----------------------------------
");
console.log("Verificando con contraseña incorrecta:");
const isValidIncorrect = zkp.verify("contraseña_incorrecta");
console.log(`
Resultado final: ${isValidIncorrect ? "✅ Verificación exitosa" : "❌ Verificación fallida"}`);

Puntos clave de seguridad:

  • Utilizar bibliotecas especializadas para implementaciones reales de ZKP
  • Considerar protocolos estandarizados como zk-SNARKs o zk-STARKs para aplicaciones críticas
  • Implementar verificación de integridad para todos los mensajes intercambiados
  • Asegurar que el canal de comunicación entre probador y verificador sea seguro
  • Realizar auditorías de seguridad en implementaciones de ZKP

Mejores Prácticas en Criptografía

Principios Fundamentales

  • No implementes tu propia criptografía: Utiliza bibliotecas probadas y auditadas por expertos.
  • Mantén el software actualizado: Las actualizaciones suelen incluir parches de seguridad críticos.
  • Principio de Kerckhoffs: La seguridad debe depender solo del secreto de la clave, no del algoritmo.
  • Defensa en profundidad: Implementa múltiples capas de protección criptográfica.

Gestión de Claves

  • Generación segura: Utiliza generadores de números aleatorios criptográficamente seguros (CSPRNG).
  • Almacenamiento protegido: Usa módulos de seguridad hardware (HSM) o enclaves seguros cuando sea posible.
  • Rotación periódica: Cambia las claves regularmente para limitar el impacto de posibles compromisos.
  • Plan de recuperación: Implementa procedimientos para recuperación de claves en caso de pérdida.

Implementación Segura

  • Validación de entradas: Verifica y sanitiza todas las entradas antes de procesarlas criptográficamente.
  • Protección contra ataques de canal lateral: Evita fugas de información a través de tiempos de ejecución o consumo de energía.
  • Manejo de errores seguro: No reveles información sensible en mensajes de error.
  • Auditoría y logging: Registra eventos criptográficos importantes sin exponer datos sensibles.

Adaptabilidad y Futuro

  • Agilidad criptográfica: Diseña sistemas que permitan cambiar algoritmos sin reescribir toda la aplicación.
  • Preparación post-cuántica: Comienza a planificar la migración a algoritmos resistentes a computación cuántica.
  • Seguimiento de estándares: Mantente al día con las recomendaciones de NIST, IETF y otras organizaciones.
  • Evaluación continua: Realiza auditorías de seguridad y pruebas de penetración regularmente.

Estadísticas relacionadas a la Criptografía

94%

de las organizaciones han experimentado una brecha de seguridad relacionada con criptografía débil

43%

de las brechas de seguridad podrían haberse evitado con implementaciones criptográficas adecuadas

67%

de las organizaciones no tienen un plan de migración a criptografía post-cuántica