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.