Spaces:
Sleeping
Sleeping
| from __future__ import annotations # tuple[...] compatible con Python 3.8+ | |
| import gradio as gr | |
| import secrets | |
| import string | |
| import base64 | |
| import math | |
| import json | |
| import os | |
| import sys | |
| import tempfile | |
| from datetime import datetime | |
| from PIL import Image | |
| import io | |
| from cryptography.hazmat.primitives.ciphers.aead import AESGCM | |
| from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey | |
| from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey | |
| from cryptography.hazmat.primitives import serialization | |
| from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | |
| from cryptography.hazmat.primitives import hashes | |
| from cryptography.hazmat.backends import default_backend | |
| import nacl.pwhash | |
| import nacl.signing | |
| import nacl.public | |
| VERSION = "2.1.0" | |
| PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" | |
| RNG_SOURCE = "os.urandom / secrets (CSPRNG)" | |
| # SΓmbolo ΓΊnico definido en un solo lugar β ya no duplicado | |
| SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?" | |
| DICEWARE_WORDLIST = ['abaco', 'abeja', 'abierto', 'abogado', 'abono', 'abrazo', 'abrir', 'abuelo', 'abuso', 'acabar', 'academia', 'acceso', 'accion', 'aceite', 'acero', 'acertar', 'aclarar', 'acoger', 'acordar', 'acorde', 'acortar', 'acoso', 'acostar', 'acuerdo', 'acusar', 'adaptar', 'adecuar', 'adelante', 'ademas', 'adentro', 'adoptar', 'adorar', 'adquirir', 'adulto', 'adverso', 'advertir', 'afan', 'afectar', 'aficion', 'afirmar', 'afligir', 'afrontar', 'agasajar', 'agente', 'agitar', 'agonia', 'agotar', 'agradar', 'agravar', 'agregar', 'agrio', 'aguacate', 'aguantar', 'aguila', 'aguja', 'ahijado', 'ahogar', 'ahora', 'ahorro', 'aire', 'aislar', 'ajedrez', 'ajeno', 'ajustar', 'alabanza', 'alambre', 'alarma', 'alba', 'albergar', 'alcance', 'aldea', 'aleatorio', 'alegria', 'alejar', 'alerta', 'alfombra', 'alga', 'algodon', 'alguien', 'alguno', 'alianza', 'aliento', 'alinear', 'aliviar', 'allanar', 'alma', 'almendra', 'almohada', 'alojar', 'alquiler', 'altar', 'alterar', 'altivo', 'alto', 'alumno', 'alzar', 'amable', 'amante', 'amar', 'ambicion', 'ambos', 'amenaza', 'ameno', 'amigo', 'amistad', 'amor', 'amparo', 'ampolla', 'analogo', 'ancho', 'anciano', 'andar', 'anecdota', 'angel', 'angosto', 'anillo', 'animo', 'aniversario', 'anoche', 'ansia', 'antena', 'antes', 'antiguo', 'anual', 'anuncio', 'anadir', 'apagar', 'aparato', 'apartar', 'apellido', 'apenas', 'apetito', 'aplicar', 'apodo', 'aportar', 'apoyar', 'aprender', 'apretar', 'aprobado', 'aprovechar', 'aptitud', 'apuro', 'aquel', 'aquello', 'arana', 'arar', 'arbol', 'arco', 'arder', 'ardilla', 'arena', 'argucia', 'arido', 'arma', 'armario', 'aroma', 'arpon', 'arquitecto', 'arrancar', 'arreglar', 'arriba', 'arriesgar', 'arrojar', 'arroyo', 'arte', 'artico', 'artista', 'arzobispo', 'asado', 'asaltar', 'ascender', 'asco', 'asegurar', 'asentar', 'asesor', 'asfalto', 'asiento', 'asilo', 'asistir', 'asma', 'asno', 'asombro', 'aspecto', 'aspirar', 'astilla', 'astro', 'astuto', 'asumir', 'asunto', 'atacar', 'atajo', 'ataque', 'atar', 'ataud', 'atencion', 'atenerse', 'aterrizar', 'atlas', 'atmosfera', 'atomo', 'atras', 'atrever', 'atributo', 'audaz', 'audio', 'aumentar', 'aunque', 'ausente', 'autentico', 'autobus', 'autor', 'auxilio', 'avance', 'avaro', 'avena', 'aventura', 'averiguar', 'avestruz', 'avion', 'avisar', 'aviso', 'axila', 'ayer', 'ayuda', 'ayunar', 'azahar', 'azar', 'azote', 'azucar', 'azul', 'babero', 'babor', 'bache', 'bailar', 'baja', 'bajar', 'balanza', 'balcon', 'balde', 'balsa', 'banco', 'banda', 'bandera', 'bano', 'barato', 'barba', 'barco', 'barrer', 'barrio', 'barro', 'basar', 'basura', 'batalla', 'bateria', 'batir', 'bazar', 'bebe', 'beber', 'belleza', 'bendecir', 'beneficio', 'besar', 'bestia', 'biblioteca', 'bicho', 'bien', 'bigote', 'billete', 'binario', 'biologia', 'blanco', 'bloque', 'blusa', 'boa', 'bobo', 'boca', 'boda', 'bodega', 'bolero', 'bolsa', 'bomba', 'bondad', 'bonito', 'borde', 'borrar', 'bosque', 'bota', 'botella', 'boton', 'boxear', 'bravo', 'brazo', 'brecha', 'breve', 'brillo', 'brindar', 'brisa', 'brocha', 'broma', 'bronce', 'brote', 'brujula', 'bruto', 'bueno', 'buey', 'bufalo', 'bufanda', 'bugle', 'buitre', 'bulto', 'buque', 'burbuja', 'burla', 'burro', 'buscar', 'butaca', 'buzon', 'caballo', 'cabeza', 'cable', 'cabra', 'cacao', 'cacerola', 'cachorro', 'cada', 'cadena', 'caer', 'cafe', 'caja', 'calamar', 'calcular', 'caldo', 'calentar', 'calidad', 'callar', 'calma', 'calor', 'calvo', 'cama', 'cambiar', 'camello', 'camino', 'caminar', 'camisa', 'campana', 'campo', 'canal', 'cancion', 'candado', 'cangrejo', 'canoa', 'cansar', 'cantar', 'cantidad', 'capa', 'capaz', 'capitan', 'cara', 'carbon', 'carcel', 'cardinal', 'carecer', 'carga', 'cargo', 'carino', 'carne', 'carpeta', 'carrera', 'carro', 'carta', 'cartera', 'casa', 'casar', 'cascada', 'casco', 'caso', 'castigo', 'castillo', 'casual', 'catalogo', 'catarata', 'catorce', 'causa', 'cavar', 'cazar', 'cebolla', 'ceder', 'ceguera', 'ceja', 'celda', 'celebrar', 'celoso', 'celsius', 'cemento', 'cenar', 'ceniza', 'centro', 'cepillo', 'cerca', 'cerebro', 'ceremonia', 'cero', 'cerrar', 'cerro', 'certeza', 'cesar', 'champu', 'chance', 'chapa', 'charlar', 'chico', 'chile', 'chimenea', 'chino', 'chip', 'chiste', 'chocolate', 'chofer', 'choque', 'ciclon', 'cielo', 'ciencia', 'cifra', 'cima', 'cinco', 'cine', 'cinta', 'cinturon', 'circo', 'ciruela', 'cisne', 'cita', 'ciudad', 'civil', 'claro', 'clase', 'clave', 'clavar', 'clima', 'clinica', 'club', 'cobertor', 'cobrar', 'cobre', 'cocina', 'coco', 'codigo', 'coger', 'coincidir', 'cola', 'colcha', 'colegio', 'colgar', 'colina', 'collar', 'colmena', 'color', 'columna', 'comarca', 'combatir', 'comedia', 'comenzar', 'comer', 'cometa', 'comida', 'comite', 'como', 'compania', 'comparar', 'compas', 'competir', 'completo', 'comprar', 'comprender', 'computar', 'comun', 'conceder', 'concepto', 'concierto', 'conde', 'condicion', 'conducir', 'conejo', 'conexion', 'confesar', 'confianza', 'confundir', 'congelar', 'conocer', 'consejo', 'consola', 'constante', 'consumir', 'contar', 'contento', 'contigo', 'continuar', 'contra', 'control', 'convencer', 'copia', 'coquetear', 'coraje', 'corazon', 'corbata', 'corcho', 'cordero', 'cordial', 'corona', 'corral', 'correr', 'cortar', 'corte', 'cosa', 'costa', 'costumbre', 'coyote', 'crear', 'crecer', 'creer', 'crema', 'cresta', 'criar', 'crimen', 'cristal', 'criticar', 'croqueta', 'cruzar', 'cuadro', 'cual', 'cuando', 'cuanto', 'cuarto', 'cuatro', 'cubierta', 'cubo', 'cucaracha', 'cuchara', 'cuchillo', 'cuello', 'cuenta', 'cuerda', 'cuerno', 'cuerpo', 'cuesta', 'cueva', 'cuidar', 'culebra', 'culpa', 'cultivar', 'cumbre', 'cumple', 'cuna', 'cupon', 'cura', 'curioso', 'curso', 'curva', 'cuyo', 'dama', 'danar', 'danza', 'dar', 'dato', 'deber', 'decidir', 'decir', 'dedo', 'defender', 'definir', 'dejar', 'delgado', 'delito', 'demas', 'demora', 'dentro', 'departamento', 'depender', 'deporte', 'deposito', 'derrota', 'desayuno', 'descanso', 'descender', 'desear', 'desfile', 'deseo', 'desierto', 'deslizar', 'desnudo', 'despacio', 'despedir', 'despues', 'destino', 'detalle', 'detener', 'detras', 'devolver', 'dia', 'diablo', 'dialogo', 'diamante', 'diana', 'diario', 'dibujar', 'diccionario', 'diente', 'diez', 'diferente', 'dificil', 'digital', 'digno', 'dilema', 'dinero', 'dios', 'direccion', 'dirigir', 'disciplina', 'disco', 'diseno', 'disfrutar', 'disparar', 'distancia', 'distinto', 'diverso', 'dividir', 'divino', 'doblar', 'docena', 'doctor', 'dolar', 'domingo', 'donar', 'donde', 'dorado', 'dormir', 'dos', 'dosis', 'dragon', 'drama', 'droga', 'duda', 'dulce', 'duque', 'durar', 'duro', 'ebano', 'ebrio', 'echar', 'eco', 'edad', 'edificio', 'editar', 'educar', 'efecto', 'ejemplo', 'ejercito', 'elaborar', 'eleccion', 'elegir', 'elefante', 'elegante', 'elemento', 'elevar', 'eliminar', 'ella', 'ello', 'embarazo', 'emergencia', 'emisor', 'emocion', 'empatar', 'empezar', 'emplear', 'empresa', 'enano', 'encajar', 'encender', 'encima', 'encontrar', 'encuentro', 'energia', 'enfermo', 'enfrentar', 'enganar', 'enigma', 'enojar', 'ensayo', 'enseguida', 'ensuciar', 'entender', 'entero', 'entonces', 'entrada', 'entrar', 'entre', 'entregar', 'entusiasmo', 'enviar', 'envidia', 'epoca', 'equilibrio', 'equipo', 'equivocar', 'erizo', 'error', 'escalar', 'escapar', 'escena', 'escoba', 'escoger', 'esconder', 'escribir', 'escudo', 'escuela', 'ese', 'esencia', 'esfera', 'esfuerzo', 'eslabon', 'esmeralda', 'espacio', 'espalda', 'espanto', 'especial', 'especie', 'espejo', 'espera', 'esperanza', 'espia', 'espiga', 'espina', 'espiritu', 'esposa', 'esposo', 'esquina', 'establo', 'estado', 'estallar', 'estante', 'estar', 'estatua', 'este', 'estilo', 'estima', 'estornudar', 'estrella', 'estrecho', 'estructura', 'estudiar', 'eterno', 'etiqueta', 'evadir', 'evento', 'evitar', 'exacto', 'examen', 'exceso', 'excusa', 'exigir', 'exilio', 'exito', 'explicar', 'explotar', 'exponer', 'expresar', 'extender', 'extra', 'extrano', 'extremo', 'fabrica', 'fabula', 'fachada', 'facil', 'factor', 'faena', 'faja', 'falda', 'fallar', 'falso', 'falta', 'fama', 'familia', 'fanatico', 'fantasma', 'farol', 'faro', 'farsa', 'fase', 'fatal', 'fauna', 'favor', 'fax', 'febrero', 'fecha', 'felicidad', 'feliz', 'fenix', 'feo', 'feria', 'feroz', 'ferrocarril', 'festival', 'fiable', 'fibra', 'ficcion', 'ficha', 'fidelidad', 'fiebre', 'fiel', 'fiesta', 'figura', 'fijar', 'filamento', 'filosofia', 'filtro', 'fin', 'final', 'finanza', 'fingir', 'firma', 'firme', 'fiscal', 'fisica', 'flaco', 'flauta', 'flecha', 'flexible', 'flor', 'flotar', 'fluir', 'foco', 'fogata', 'fogon', 'folleto', 'fondo', 'forma', 'formar', 'foro', 'fortuna', 'forzar', 'fosa', 'foto', 'fracaso', 'fragancia', 'frase', 'fraude', 'frecuente', 'fregar', 'freno', 'frente', 'fresa', 'fresco', 'frijol', 'frontera', 'fruta', 'fuego', 'fuente', 'fuerte', 'fuerza', 'fuga', 'fumar', 'funcion', 'funda', 'fundar', 'funeral', 'furioso', 'futbol', 'futuro', 'gabinete', 'gafas', 'gala', 'galeria', 'gallina', 'gallo', 'ganar', 'ganga', 'ganso', 'garaje', 'garantia', 'garra', 'gas', 'gastar', 'gato', 'gelatina', 'gemelo', 'gema', 'genio', 'gente', 'geografia', 'gerente', 'gesto', 'gigante', 'gimnasio', 'girar', 'gitano', 'glaciar', 'globo', 'gloria', 'gobernar', 'gol', 'golondrina', 'golpear', 'goma', 'gordo', 'gorila', 'gorra', 'gota', 'gozar', 'grabadora', 'gracia', 'graduar', 'grafico', 'granada', 'grande', 'grano', 'grasa', 'gratis', 'grave', 'griego', 'grillo', 'gripe', 'gris', 'gritar', 'grito', 'grueso', 'grupo', 'guante', 'guapo', 'guardar', 'guerra', 'guia', 'guion', 'guisante', 'guitarra', 'gusano', 'gusto', 'haba', 'habil', 'habitar', 'habito', 'hablar', 'hacer', 'hacha', 'hada', 'halcon', 'hallar', 'hambre', 'harina', 'hasta', 'hebilla', 'hecho', 'helado', 'helicoptero', 'heredar', 'herida', 'hermano', 'heroe', 'herramienta', 'hervir', 'hielo', 'hierba', 'hierro', 'higo', 'hija', 'hilo', 'himno', 'hinojo', 'historia', 'hogar', 'hoja', 'hombre', 'hongo', 'honor', 'hora', 'horizonte', 'hormiga', 'horno', 'horror', 'hospital', 'hostal', 'hotel', 'hoy', 'hucha', 'hueco', 'huevo', 'huir', 'humanidad', 'humano', 'humedad', 'humilde', 'humo', 'hundir', 'huracan', 'huso', 'idea', 'idioma', 'idolo', 'iglesia', 'igual', 'ilegal', 'imagen', 'imitar', 'impedir', 'imperio', 'imponer', 'importar', 'imposible', 'impulso', 'incendio', 'incluir', 'indicar', 'indigo', 'industria', 'infantil', 'inferior', 'infinito', 'inflar', 'informe', 'ingenio', 'iniciar', 'inmenso', 'inodoro', 'insecto', 'insistir', 'instante', 'insulto', 'inteligente', 'intencion', 'interes', 'interno', 'intimar', 'introducir', 'inundar', 'invadir', 'inventar', 'invierno', 'invitar', 'inyectar', 'ir', 'ira', 'iris', 'isla', 'israel', 'italia', 'izquierda', 'jabali', 'jabon', 'jactarse', 'jade', 'jamas', 'jamon', 'jardin', 'jarra', 'jaula', 'jefe', 'jengibre', 'jinete', 'jirafa', 'jornada', 'joven', 'joya', 'jubilar', 'juez', 'jugador', 'jugar', 'jugo', 'juguete', 'juicio', 'julio', 'junio', 'juntar', 'jupiter', 'jurar', 'justicia', 'justo', 'juvenil', 'laberinto', 'labio', 'labor', 'lado', 'lagarto', 'lago', 'lagrima', 'lamentar', 'lampara', 'lana', 'langosta', 'lanza', 'lanzar', 'lapiz', 'largo', 'lastima', 'lata', 'latigo', 'latir', 'lavar', 'lazo', 'leccion', 'leche', 'lector', 'leer', 'legumbre', 'lejos', 'lengua', 'lenteja', 'lentes', 'leon', 'leopardo', 'lesion', 'letra', 'levantar', 'ley', 'leyenda', 'libertad', 'libra', 'libro', 'licor', 'lider', 'liebre', 'liga', 'ligero', 'limite', 'limon', 'limpiar', 'lindo', 'linea', 'lingote', 'lirio', 'liso', 'lista', 'listo', 'litera', 'litro', 'llama', 'llanto', 'llave', 'llegar', 'llenar', 'llevar', 'llorar', 'llover', 'lluvia', 'lobo', 'local', 'loco', 'logica', 'lograr', 'lomo', 'lona', 'longitud', 'loro', 'loteria', 'luchar', 'luciernaga', 'lugar', 'lujo', 'luna', 'lunes', 'luz', 'macho', 'madera', 'madre', 'madrugada', 'maestro', 'magia', 'magnifico', 'maiz', 'mal', 'maldad', 'maleta', 'malicia', 'malla', 'malo', 'mama', 'mamut', 'mancha', 'mandar', 'manejar', 'manga', 'mano', 'manso', 'manta', 'mantel', 'mantener', 'manzana', 'manana', 'mapa', 'maquina', 'mar', 'maravilla', 'marca', 'marcha', 'marco', 'mareo', 'margen', 'marido', 'marina', 'mariposa', 'marmol', 'marron', 'martes', 'martillo', 'mas', 'mascara', 'masivo', 'matar', 'materia', 'matrimonio', 'maximo', 'mayo', 'mayor', 'mecanico', 'medalla', 'mediano', 'medicina', 'medida', 'medio', 'medir', 'medusa', 'mejilla', 'mejor', 'melocoton', 'melodia', 'membrana', 'memoria', 'mencionar', 'mendigo', 'mensaje', 'menta', 'mente', 'mentira', 'mentor', 'menu', 'mercado', 'mercurio', 'merecer', 'merienda', 'mes', 'mesa', 'meta', 'metal', 'meter', 'metodo', 'metro', 'mezclar', 'mi', 'microbio', 'miedo', 'miel', 'miembro', 'mientras', 'migrar', 'mil', 'milagro', 'militar', 'millon', 'mimo', 'mina', 'minero', 'minimo', 'ministro', 'minuto', 'mio', 'mirar', 'mirlo', 'misa', 'misil', 'mismo', 'misterio', 'mitad', 'mito', 'moda', 'modelo', 'modesto', 'modo', 'mojar', 'moler', 'molestar', 'molino', 'momento', 'monarca', 'moneda', 'monitor', 'mono', 'montana', 'montar', 'monumento', 'morado', 'morder', 'moreno', 'morir', 'mosca', 'mostaza', 'mostrar', 'motivo', 'mover', 'movil', 'mucho', 'mudar', 'mueble', 'muerte', 'muestra', 'mujer', 'mula', 'multa', 'multiple', 'multitud', 'mundo', 'muralla', 'murcielago', 'muro', 'musculo', 'museo', 'musica', 'muslo', 'mutuo', 'nacer', 'nacion', 'nadar', 'naipe', 'naranja', 'nariz', 'nata', 'naturaleza', 'nave', 'navidad', 'neblina', 'necesitar', 'negar', 'negocio', 'negro', 'nervio', 'neto', 'neutro', 'nevar', 'nevera', 'ni', 'nido', 'niebla', 'nieto', 'ninguno', 'nino', 'nivel', 'no', 'noche', 'nombrar', 'nombre', 'noria', 'norma', 'norte', 'nota', 'notar', 'noticia', 'novato', 'novecientos', 'novela', 'noveno', 'noviembre', 'novio', 'nube', 'nuca', 'nucleo', 'nudillo', 'nudo', 'nuera', 'nueve', 'nuevo', 'numero', 'nunca', 'nutria', 'oasis', 'obedecer', 'obispo', 'objeto', 'obra', 'obrero', 'observar', 'obstaculo', 'obtener', 'ocasion', 'oceano', 'ochenta', 'ocho', 'ocio', 'ocre', 'octubre', 'ocultar', 'ocupar', 'ocurrir', 'odiar', 'odio', 'oeste', 'ofender', 'oferta', 'oficial', 'oficio', 'ogro', 'oido', 'oir', 'ojo', 'ola', 'oler', 'olfato', 'oliva', 'olvidar', 'ombligo', 'onda', 'onza', 'opaco', 'opcion', 'operar', 'opinar', 'oponer', 'optar', 'oracion', 'orar', 'orbita', 'orden', 'oreja', 'organo', 'orgullo', 'oriente', 'origen', 'orilla', 'oro', 'orquesta', 'ortiga', 'oso', 'otono', 'otro', 'ovalo', 'ovario', 'ovillo', 'oxigeno', 'oye', 'ozono', 'paciencia', 'pacto', 'padre', 'pagar', 'pagina', 'pais', 'paja', 'pajaro', 'palabra', 'palacio', 'paladar', 'palanca', 'palma', 'palmera', 'paloma', 'palpar', 'pan', 'panal', 'pantera', 'pantalla', 'pantalon', 'panza', 'papa', 'papel', 'par', 'parar', 'parasol', 'pared', 'pariente', 'parir', 'parlamento', 'paro', 'parpadear', 'parte', 'partir', 'pasado', 'pasar', 'paseo', 'pasillo', 'paso', 'pasta', 'pastel', 'pasto', 'pata', 'patio', 'patria', 'pausa', 'pavo', 'payaso', 'peaje', 'pecado', 'peces', 'pedal', 'pedir', 'pegar', 'peine', 'pelar', 'pelicula', 'peligro', 'pellejo', 'pelo', 'pelota', 'pensar', 'peon', 'peor', 'pepino', 'pequeno', 'pera', 'percha', 'perder', 'perdon', 'perfil', 'perico', 'periodo', 'perla', 'permitir', 'perro', 'persona', 'pesa', 'pescar', 'peso', 'pestana', 'pez', 'picar', 'pico', 'pie', 'piedra', 'piel', 'pierna', 'pila', 'pillar', 'pilotar', 'piloto', 'pimienta', 'pino', 'pintar', 'pintor', 'pina', 'pipa', 'pirata', 'pisar', 'piscina', 'piso', 'pista', 'pizarra', 'placer', 'plan', 'planeta', 'planta', 'plata', 'plato', 'playa', 'plaza', 'plazo', 'plegar', 'pleno', 'pluma', 'poblado', 'pobre', 'poco', 'poder', 'poema', 'poeta', 'polen', 'policia', 'pollo', 'polo', 'polvo', 'poner', 'por', 'porque', 'portar', 'posar', 'poseer', 'posible', 'postal', 'postre', 'potro', 'pozo', 'prado', 'precio', 'predicar', 'preferir', 'pregunta', 'premio', 'prenda', 'preocupar', 'preparar', 'presentar', 'preso', 'prestar', 'pretender', 'prima', 'primavera', 'primo', 'princesa', 'principal', 'prisa', 'privado', 'probar', 'proceso', 'producir', 'profesor', 'programa', 'progreso', 'promesa', 'prometer', 'pronombre', 'pronto', 'propina', 'propio', 'proteger', 'protestar', 'provecho', 'proverbio', 'provincia', 'proximo', 'proyecto', 'prueba', 'publicar', 'puchero', 'pudrir', 'pueblo', 'puente', 'puerta', 'pues', 'puesto', 'pulga', 'pulir', 'pulmon', 'pulpo', 'pulso', 'punta', 'punto', 'puno', 'pupitre', 'pure', 'quebrar', 'quedar', 'queja', 'quemar', 'querer', 'queso', 'quien', 'quieto', 'quimica', 'quince', 'quinto', 'quiosco', 'quitar', 'rabia', 'rabo', 'radiar', 'radio', 'raiz', 'rama', 'ramo', 'rana', 'rango', 'rapido', 'raptar', 'raro', 'rasgo', 'rata', 'raton', 'raya', 'rayo', 'raza', 'razon', 'reaccion', 'real', 'rebanada', 'rebelde', 'recado', 'receta', 'rechazar', 'recibir', 'recoger', 'recomendar', 'recordar', 'recorrer', 'recuerdo', 'red', 'redondo', 'reducir', 'referir', 'reflejo', 'reforma', 'refugio', 'regalar', 'regalo', 'regimen', 'regla', 'regresar', 'rehen', 'reina', 'reir', 'reja', 'relampago', 'relatar', 'reloj', 'remar', 'remedio', 'remo', 'rendir', 'renovar', 'renta', 'reparar', 'repartir', 'repetir', 'reposar', 'reptil', 'res', 'resbalar', 'rescatar', 'resfriado', 'resolver', 'respetar', 'responder', 'restar', 'resto', 'resultado', 'retirar', 'retorno', 'retrato', 'reunion', 'reunir', 'revelar', 'reventar', 'revisar', 'rey', 'rezar', 'rico', 'riego', 'rienda', 'riesgo', 'rincon', 'rio', 'riqueza', 'risa', 'ritmo', 'rito', 'rival', 'robar', 'roble', 'robot', 'roca', 'roce', 'rodar', 'rodilla', 'rogar', 'rojo', 'romper', 'roncar', 'ronda', 'ropa', 'rosa', 'rostro', 'rotar', 'roto', 'rubio', 'rueda', 'ruido', 'ruina', 'rumbo', 'rumor', 'ruta', 'sabado', 'saber', 'sabio', 'sabor', 'sacar', 'sacerdote', 'saco', 'sacudir', 'sagaz', 'sagrado', 'sal', 'sala', 'salario', 'salchicha', 'salida', 'salir', 'salmon', 'salon', 'salsa', 'saltar', 'salto', 'salud', 'saludo', 'salvaje', 'sangre', 'sano', 'santo', 'sarten', 'sastre', 'saturno', 'sauce', 'sauna', 'saxofon', 'sazon', 'secar', 'seccion', 'secreto', 'secuencia', 'sed', 'seda', 'segundo', 'seguir', 'seguridad', 'seguro', 'seis', 'sello', 'selva', 'semana', 'sembrar', 'semejante', 'semilla', 'seno', 'sensato', 'sentar', 'sentido', 'sentir', 'senal', 'senor', 'separar', 'sepultar', 'ser', 'serie', 'serio', 'serpiente', 'servir', 'sesion', 'seta', 'setenta', 'sexo', 'sexto', 'si', 'sibarita', 'sicario', 'sidra', 'siempre', 'sierra', 'siete', 'siga', 'siglo', 'significar', 'siguiente', 'silbar', 'silencio', 'silla', 'silueta', 'simbolo', 'simpatico', 'simple', 'sin', 'sino', 'sirena', 'sistema', 'sitio', 'sobrar', 'sobre', 'sobrino', 'socavar', 'socio', 'soda', 'sofocar', 'sol', 'soldado', 'soledad', 'solicitar', 'solo', 'soltar', 'solucion', 'sombrero', 'sombrilla', 'sonar', 'sonido', 'sonreir', 'sopa', 'soplar', 'soportar', 'sordo', 'sorpresa', 'sorteo', 'sospechar', 'sostener', 'suave', 'subir', 'suceder', 'sucio', 'sudar', 'suegro', 'suelo', 'sueno', 'suerte', 'sufrir', 'suizo', 'sumar', 'sumergir', 'superar', 'superior', 'suplicar', 'suponer', 'surgir', 'surtir', 'suspirar', 'sustantivo', 'susto', 'tabaco', 'tabla', 'tacto', 'tajo', 'tal', 'talar', 'talento', 'talla', 'tallo', 'tamano', 'tambor', 'tan', 'tango', 'tanque', 'tapa', 'tapar', 'tarde', 'tarea', 'tarifa', 'tarjeta', 'tarta', 'tasa', 'taza', 'teatro', 'techo', 'tecla', 'tecnica', 'tedio', 'tejer', 'tejido', 'tela', 'telefono', 'tema', 'temer', 'temor', 'temporada', 'temprano', 'tenaz', 'tender', 'tenedor', 'tener', 'tenis', 'tenso', 'teoria', 'tercer', 'terminar', 'terremoto', 'terreno', 'terrible', 'tesoro', 'testigo', 'texto', 'tiempo', 'tienda', 'tierno', 'tierra', 'tigre', 'tijera', 'timido', 'tinta', 'tio', 'tipo', 'tirar', 'tiro', 'tiza', 'toalla', 'tobillo', 'tocar', 'todavia', 'todo', 'toldo', 'tomar', 'tono', 'tonto', 'topo', 'tormenta', 'tornillo', 'toro', 'torre', 'tortuga', 'tos', 'toser', 'tostar', 'total', 'trabajar', 'trabajo', 'traducir', 'traer', 'trafico', 'tragedia', 'trago', 'traicion', 'traje', 'trama', 'trampa', 'tranquilo', 'transito', 'trapo', 'tratar', 'traves', 'trebol', 'trece', 'tregua', 'tren', 'tres', 'tribu', 'trigo', 'triste', 'triunfar', 'trofeo', 'trompeta', 'tronco', 'tropa', 'trote', 'trucha', 'tu', 'tubo', 'tumba', 'tumor', 'tunel', 'turismo', 'turno', 'tutor', 'ubicar', 'ufano', 'ultimo', 'umbral', 'unanimidad', 'uncion', 'undecimo', 'ungir', 'unico', 'unidad', 'unir', 'universo', 'uno', 'untar', 'una', 'urbano', 'urgente', 'usar', 'uso', 'usted', 'usual', 'utensilio', 'util', 'uva', 'vaca', 'vacaciones', 'vacio', 'vade', 'vagabundo', 'vagon', 'vainilla', 'valer', 'valiente', 'valle', 'valor', 'vals', 'vampiro', 'vanidad', 'vano', 'vapor', 'vara', 'variable', 'varon', 'vaso', 'vecino', 'veda', 'vegetal', 'veinte', 'vela', 'velar', 'velero', 'velocidad', 'venado', 'vender', 'veneno', 'venerar', 'venganza', 'venir', 'ventana', 'ventilar', 'ver', 'verano', 'verbo', 'verdad', 'verde', 'verdugo', 'vereda', 'vergonzoso', 'verificar', 'verso', 'verter', 'vestido', 'vestir', 'vez', 'via', 'viaje', 'vibora', 'vicio', 'victima', 'victoria', 'vid', 'vida', 'vidrio', 'viejo', 'viento', 'vientre', 'viernes', 'vigilar', 'vigor', 'villa', 'vinagre', 'vino', 'violencia', 'virgen', 'virtud', 'virus', 'visita', 'vista', 'vital', 'viuda', 'vivir', 'vocal', 'vocero', 'volador', 'volcan', 'volar', 'volcar', 'volver', 'volumen', 'voluntad', 'vomitar', 'votar', 'voz', 'vuelo', 'vuelta', 'yacer', 'yacimiento', 'yate', 'yema', 'yerno', 'yeso', 'yo', 'yodo', 'yogur', 'yugo', 'zafiro', 'zanahoria', 'zapato', 'zarza', 'zona', 'zorro', 'zumo', 'zurdo'] | |
| # βββ UTILS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_charset_size(upper, nums, syms): | |
| size = 26 | |
| if upper: size += 26 | |
| if nums: size += 10 | |
| if syms: size += len(SYMBOLS) | |
| return size | |
| def get_recommended_length(target_bits, charset_size): | |
| if charset_size <= 0: | |
| return 0 | |
| return math.ceil(target_bits / math.log2(charset_size)) | |
| def calculate_entropy(password): | |
| if not password: | |
| return 0 | |
| cs = 0 | |
| if any(c in string.ascii_lowercase for c in password): cs += 26 | |
| if any(c in string.ascii_uppercase for c in password): cs += 26 | |
| if any(c in string.digits for c in password): cs += 10 | |
| if any(c in string.punctuation for c in password): cs += 32 | |
| return round(len(password) * math.log2(cs), 2) if cs else 0 | |
| def check_strength(entropy): | |
| if entropy < 28: return "Muy debil", "#dc2626", "Segundos" | |
| elif entropy < 36: return "Debil", "#ea580c", "Minutos" | |
| elif entropy < 60: return "Media", "#ca8a04", "Horas/Dias" | |
| elif entropy < 80: return "Fuerte", "#16a34a", "Anos" | |
| elif entropy < 128: return "Muy fuerte", "#2563eb", "Decadas" | |
| elif entropy < 256: return "Extrema", "#7c3aed", "Siglos" | |
| else: return "Militar (256+)", "#581c87", "Milenios" | |
| # βββ CONTRASEΓAS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_password_rigorous(length=16, use_uppercase=True, use_numbers=True, use_symbols=True): | |
| """ | |
| Genera la contraseΓ±a completa de forma aleatoria pura, | |
| luego verifica que cumpla los requisitos β sin insertar | |
| caracteres forzados que romperΓan la distribuciΓ³n uniforme. | |
| """ | |
| characters = string.ascii_lowercase | |
| if use_uppercase: characters += string.ascii_uppercase | |
| if use_numbers: characters += string.digits | |
| if use_symbols: characters += SYMBOLS | |
| for _ in range(10_000): | |
| pwd = "".join(secrets.choice(characters) for _ in range(length)) | |
| ok_lower = any(c in string.ascii_lowercase for c in pwd) | |
| ok_upper = (not use_uppercase) or any(c in string.ascii_uppercase for c in pwd) | |
| ok_num = (not use_numbers) or any(c in string.digits for c in pwd) | |
| ok_sym = (not use_symbols) or any(c in SYMBOLS for c in pwd) | |
| if ok_lower and ok_upper and ok_num and ok_sym: | |
| return pwd | |
| # fallback estadΓsticamente improbable | |
| return "".join(secrets.choice(characters) for _ in range(length)) | |
| def generate_password_256_bits(upper=True, nums=True, syms=True): | |
| cs = get_charset_size(upper, nums, syms) | |
| length = get_recommended_length(256, cs) | |
| return generate_password_rigorous(length, upper, nums, syms), length | |
| def generate_password_128_bits(upper=True, nums=True, syms=True): | |
| cs = get_charset_size(upper, nums, syms) | |
| length = get_recommended_length(128, cs) | |
| return generate_password_rigorous(length, upper, nums, syms), length | |
| # βββ TOKENS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_token(bits=256, format_type="hex"): | |
| num_bytes = bits // 8 | |
| rb = secrets.token_bytes(num_bytes) | |
| if format_type == "hex": | |
| return rb.hex() | |
| elif format_type == "base64": | |
| return base64.b64encode(rb).decode() | |
| elif format_type == "base64url": | |
| return base64.urlsafe_b64encode(rb).decode().rstrip("=") | |
| elif format_type == "binary": | |
| return bin(int.from_bytes(rb, "big"))[2:].zfill(bits) | |
| elif format_type == "uuid_style": | |
| h = rb.hex() | |
| if bits == 256: | |
| return f"{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:32]}-{h[32:36]}-{h[36:48]}-{h[48:52]}-{h[52:64]}" | |
| elif bits == 128: | |
| return f"{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:32]}" | |
| return h | |
| return rb.hex() | |
| # βββ CLAVES ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_aes_key(): | |
| key = secrets.token_bytes(32) | |
| return { | |
| "hex": key.hex(), | |
| "base64": base64.b64encode(key).decode(), | |
| "base64url": base64.urlsafe_b64encode(key).decode().rstrip("="), | |
| "bytes": len(key), | |
| "bits": len(key) * 8, | |
| } | |
| def generate_hmac_key(bits=256): | |
| key = secrets.token_bytes(bits // 8) | |
| return {"hex": key.hex(), "base64": base64.b64encode(key).decode(), "bits": bits} | |
| # βββ AES-256-GCM (cifrado real) ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def aes_gcm_encrypt(plaintext, password=""): | |
| try: | |
| if password: | |
| salt = secrets.token_bytes(32) | |
| kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=600_000) | |
| key = kdf.derive(password.encode()) | |
| key_source = f"Derivada de contraseΓ±a (PBKDF2-SHA256, 600k iters)\nSalt: {salt.hex()}" | |
| else: | |
| key = secrets.token_bytes(32) | |
| key_source = "Clave aleatoria de 256 bits (CSPRNG)" | |
| aesgcm = AESGCM(key) | |
| nonce = secrets.token_bytes(12) # 96 bits β tamaΓ±o estΓ‘ndar GCM | |
| ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None) | |
| return { | |
| "ciphertext": base64.b64encode(nonce + ciphertext).decode(), | |
| "key_hex": key.hex(), | |
| "key_b64": base64.b64encode(key).decode(), | |
| "key_source": key_source, | |
| "nonce": nonce.hex(), | |
| "algorithm": "AES-256-GCM", | |
| "mode": "Authenticated Encryption (AEAD)", | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def aes_gcm_decrypt(ciphertext_b64, key_hex): | |
| try: | |
| key = bytes.fromhex(key_hex) | |
| data = base64.b64decode(ciphertext_b64) | |
| nonce = data[:12] | |
| ct = data[12:] | |
| aesgcm = AESGCM(key) | |
| plaintext = aesgcm.decrypt(nonce, ct, None) | |
| return {"plaintext": plaintext.decode(), "success": True} | |
| except Exception as e: | |
| return {"error": str(e), "success": False} | |
| # βββ ESTEGANOGRAFΓA AES-256-GCM + LSB ββββββββββββββββββββββββββββββββββββββββ | |
| END_MARKER = "1111111111111110" # 0xFFFE en binario β marcador de fin | |
| def steg_hide(image_bytes: bytes, message: str, password: str = "") -> "tuple[bytes, dict]": | |
| """ | |
| 1. Cifra el mensaje con AES-256-GCM (clave aleatoria o derivada de password) | |
| 2. Convierte el ciphertext Base64 a bits | |
| 3. Incrusta los bits en el LSB de los canales RGB del PNG | |
| Retorna (png_bytes, metadata_dict) | |
| """ | |
| # Cifrar primero | |
| enc = aes_gcm_encrypt(message, password) | |
| if "error" in enc: | |
| return None, {"error": enc["error"]} | |
| payload = enc["ciphertext"] # string Base64 | |
| key_hex = enc["key_hex"] | |
| binary = "".join(format(b, "08b") for b in payload.encode("utf-8")) | |
| binary += END_MARKER | |
| img = Image.open(io.BytesIO(image_bytes)).convert("RGB") | |
| pixels = list(img.getdata()) | |
| # Capacidad: 3 bits por pixel (R, G, B) | |
| capacity_bits = len(pixels) * 3 | |
| if len(binary) > capacity_bits: | |
| return None, {"error": f"Imagen demasiado pequeΓ±a. Necesitas {len(binary)} bits, capacidad: {capacity_bits}"} | |
| new_pixels = [] | |
| bit_idx = 0 | |
| for pixel in pixels: | |
| r, g, b = pixel | |
| if bit_idx < len(binary): | |
| r = (r & 0xFE) | int(binary[bit_idx]); bit_idx += 1 | |
| if bit_idx < len(binary): | |
| g = (g & 0xFE) | int(binary[bit_idx]); bit_idx += 1 | |
| if bit_idx < len(binary): | |
| b = (b & 0xFE) | int(binary[bit_idx]); bit_idx += 1 | |
| new_pixels.append((r, g, b)) | |
| img.putdata(new_pixels) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG") | |
| return buf.getvalue(), { | |
| "key_hex": key_hex, | |
| "key_b64": enc["key_b64"], | |
| "nonce": enc["nonce"], | |
| "payload_bytes": len(payload.encode()), | |
| "bits_used": len(binary), | |
| "image_capacity": capacity_bits, | |
| "algorithm": "AES-256-GCM + LSB steganography", | |
| "key_source": enc["key_source"], | |
| } | |
| def steg_extract(image_bytes: bytes, key_hex: str) -> dict: | |
| """ | |
| 1. Extrae los bits LSB de los canales RGB | |
| 2. Reconstruye el payload Base64 hasta el END_MARKER | |
| 3. Descifra con AES-256-GCM usando key_hex | |
| """ | |
| img = Image.open(io.BytesIO(image_bytes)).convert("RGB") | |
| pixels = list(img.getdata()) | |
| bits = "" | |
| for pixel in pixels: | |
| for channel in pixel[:3]: | |
| bits += str(channel & 1) | |
| # Buscar END_MARKER reconstruyendo byte a byte | |
| chars = [] | |
| for i in range(0, len(bits) - 7, 8): | |
| byte = bits[i:i+8] | |
| chars.append(chr(int(byte, 2))) | |
| # Comprobar si los ΓΊltimos 16 bits de chars forman el END_MARKER | |
| if len(chars) >= 2: | |
| last_two = "".join(format(ord(c), "08b") for c in chars[-2:]) | |
| if last_two == END_MARKER: | |
| chars = chars[:-2] # quitar los 2 chars del marcador | |
| break | |
| if not chars: | |
| return {"error": "No se encontrΓ³ marcador de fin. ΒΏImagen sin datos ocultos?"} | |
| payload_b64 = "".join(chars) | |
| # Descifrar | |
| dec = aes_gcm_decrypt(payload_b64, key_hex) | |
| if not dec.get("success"): | |
| return {"error": f"Descifrado fallido: {dec.get('error', 'clave incorrecta')}"} | |
| return {"plaintext": dec["plaintext"], "success": True} | |
| # βββ ARGON2id ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def argon2id_hash(password): | |
| try: | |
| h = nacl.pwhash.str( | |
| password.encode(), | |
| opslimit=nacl.pwhash.OPSLIMIT_MODERATE, | |
| memlimit=nacl.pwhash.MEMLIMIT_MODERATE, | |
| ) | |
| return { | |
| "hash": h.decode(), | |
| "algorithm": "Argon2id", | |
| "params": "opslimit=MODERATE, memlimit=MODERATE", | |
| "note": "Formato estΓ‘ndar: $argon2id$v=19$m=...$salt$hash", | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def argon2id_verify(password, hash_str): | |
| try: | |
| nacl.pwhash.verify(hash_str.encode(), password.encode()) | |
| return {"valid": True} | |
| except Exception as e: | |
| return {"valid": False, "error": str(e)} | |
| def derive_key_argon2(password, salt=None, length=32): | |
| try: | |
| if salt is None: | |
| salt = secrets.token_bytes(16) | |
| key = nacl.pwhash.kdf( | |
| length, password.encode(), salt, | |
| opslimit=nacl.pwhash.OPSLIMIT_MODERATE, | |
| memlimit=nacl.pwhash.MEMLIMIT_MODERATE, | |
| alg=nacl.pwhash.ALG_ARGON2ID13, | |
| ) | |
| return { | |
| "key_hex": key.hex(), | |
| "key_b64": base64.b64encode(key).decode(), | |
| "salt": salt.hex(), | |
| "length": length, | |
| "algorithm": "Argon2id (KDF)", | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| # βββ DICEWARE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_diceware(num_words=6, separator="-"): | |
| words = [secrets.choice(DICEWARE_WORDLIST) for _ in range(num_words)] | |
| passphrase = separator.join(words) | |
| entropy = round(num_words * math.log2(len(DICEWARE_WORDLIST)), 2) | |
| return { | |
| "passphrase": passphrase, | |
| "words": num_words, | |
| "entropy": entropy, | |
| "wordlist_size": len(DICEWARE_WORDLIST), | |
| "bits_per_word": round(math.log2(len(DICEWARE_WORDLIST)), 2), | |
| } | |
| # βββ Ed25519 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_ed25519_keys(): | |
| try: | |
| priv_key = Ed25519PrivateKey.generate() | |
| pub_key = priv_key.public_key() | |
| priv_pem = priv_key.private_bytes( | |
| serialization.Encoding.PEM, | |
| serialization.PrivateFormat.PKCS8, | |
| serialization.NoEncryption(), | |
| ).decode() | |
| pub_pem = pub_key.public_bytes( | |
| serialization.Encoding.PEM, | |
| serialization.PublicFormat.SubjectPublicKeyInfo, | |
| ).decode() | |
| priv_hex = priv_key.private_bytes( | |
| serialization.Encoding.Raw, | |
| serialization.PrivateFormat.Raw, | |
| serialization.NoEncryption(), | |
| ).hex() | |
| pub_hex = pub_key.public_bytes( | |
| serialization.Encoding.Raw, | |
| serialization.PublicFormat.Raw, | |
| ).hex() | |
| return { | |
| "private_pem": priv_pem, | |
| "public_pem": pub_pem, | |
| "private_hex": priv_hex, | |
| "public_hex": pub_hex, | |
| "algorithm": "Ed25519", | |
| "key_size": "256 bits", | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def ed25519_sign(message, private_key_hex): | |
| try: | |
| priv = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(private_key_hex)) | |
| sig = priv.sign(message.encode()) | |
| return { | |
| "signature": sig.hex(), | |
| "signature_b64": base64.b64encode(sig).decode(), | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def ed25519_verify(message, signature_hex, public_key_hex): | |
| try: | |
| pub = Ed25519PublicKey.from_public_bytes(bytes.fromhex(public_key_hex)) | |
| pub.verify(bytes.fromhex(signature_hex), message.encode()) | |
| return {"valid": True, "message": "β Firma vΓ‘lida"} | |
| except Exception as e: | |
| return {"valid": False, "message": f"β Firma invΓ‘lida: {e}"} | |
| # βββ X25519 ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_x25519_keys(): | |
| try: | |
| priv = X25519PrivateKey.generate() | |
| pub = priv.public_key() | |
| return { | |
| "private_hex": priv.private_bytes( | |
| serialization.Encoding.Raw, serialization.PrivateFormat.Raw, | |
| serialization.NoEncryption()).hex(), | |
| "public_hex": pub.public_bytes( | |
| serialization.Encoding.Raw, serialization.PublicFormat.Raw).hex(), | |
| "algorithm": "X25519 (ECDH)", | |
| "key_size": "256 bits", | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def x25519_shared_secret(private_hex, public_hex): | |
| try: | |
| priv = X25519PrivateKey.from_private_bytes(bytes.fromhex(private_hex)) | |
| pub = X25519PublicKey.from_public_bytes(bytes.fromhex(public_hex)) | |
| shared = priv.exchange(pub) | |
| return { | |
| "shared_secret_hex": shared.hex(), | |
| "shared_secret_b64": base64.b64encode(shared).decode(), | |
| "length": len(shared), | |
| "bits": len(shared) * 8, | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| # βββ API SECRETS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_jwt_secret(): | |
| s = secrets.token_urlsafe(43) | |
| return {"secret": s, "length_chars": len(s), "estimated_bits": round(len(s) * 6, 1), "type": "JWT Secret"} | |
| def generate_api_key(): | |
| prefix = "sk_" + secrets.token_hex(4) | |
| key = secrets.token_urlsafe(32) | |
| return {"api_key": f"{prefix}_{key}", "prefix": prefix, "type": "API Key"} | |
| def generate_session_token(): | |
| t = secrets.token_urlsafe(32) | |
| return {"token": t, "length": len(t), "estimated_bits": round(len(t) * 6, 1), "type": "Session Token"} | |
| def generate_oauth_secret(): | |
| s = secrets.token_urlsafe(48) | |
| return {"secret": s, "length": len(s), "estimated_bits": round(len(s) * 6, 1), "type": "OAuth 2.0 Secret"} | |
| # βββ AUDITORΓA βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_crypto_tests(): | |
| results = [] | |
| # T1 β CSPRNG bΓ‘sico | |
| try: | |
| assert len(secrets.token_bytes(32)) == 32 | |
| results.append("β T1 secrets.token_bytes(32) β 32 bytes OK") | |
| except Exception as e: | |
| results.append(f"β T1 {e}") | |
| # T2 β ContraseΓ±a con distribuciΓ³n uniforme rigurosa | |
| try: | |
| pwd = generate_password_rigorous(20, True, True, True) | |
| ent = calculate_entropy(pwd) | |
| # verifica que TODOS los charsets estΓ©n presentes (sin inserciΓ³n forzada) | |
| assert any(c in string.ascii_lowercase for c in pwd) | |
| assert any(c in string.ascii_uppercase for c in pwd) | |
| assert any(c in string.digits for c in pwd) | |
| assert any(c in SYMBOLS for c in pwd) | |
| assert ent > 100 | |
| results.append(f"β T2 Password 20 chars β {ent} bits, distribuciΓ³n uniforme OK") | |
| except Exception as e: | |
| results.append(f"β T2 {e}") | |
| # T3 β AES-256-GCM roundtrip real | |
| try: | |
| key = AESGCM.generate_key(bit_length=256) | |
| aesgcm = AESGCM(key) | |
| nonce = secrets.token_bytes(12) | |
| plaintext = "Mensaje de prueba β Γ±oΓ±o π" | |
| ct = aesgcm.encrypt(nonce, plaintext.encode(), None) | |
| pt = aesgcm.decrypt(nonce, ct, None) | |
| assert pt.decode() == plaintext | |
| results.append("β T3 AES-256-GCM encrypt/decrypt roundtrip OK") | |
| except Exception as e: | |
| results.append(f"β T3 {e}") | |
| # T4 β AES-GCM detecciΓ³n de tampering | |
| try: | |
| key = AESGCM.generate_key(bit_length=256) | |
| aesgcm = AESGCM(key) | |
| nonce = secrets.token_bytes(12) | |
| ct = aesgcm.encrypt(nonce, b"original", None) | |
| tampered = bytearray(ct); tampered[0] ^= 0xFF | |
| try: | |
| aesgcm.decrypt(nonce, bytes(tampered), None) | |
| results.append("β T4 AES-GCM deberΓa haber rechazado datos alterados") | |
| except Exception: | |
| results.append("β T4 AES-GCM detecta tampering correctamente") | |
| except Exception as e: | |
| results.append(f"β T4 {e}") | |
| # T5 β Ed25519 sign/verify | |
| try: | |
| sk = Ed25519PrivateKey.generate() | |
| pk = sk.public_key() | |
| msg = b"test message" | |
| sig = sk.sign(msg) | |
| pk.verify(sig, msg) | |
| results.append("β T5 Ed25519 sign/verify OK") | |
| except Exception as e: | |
| results.append(f"β T5 {e}") | |
| # T6 β Ed25519 rechaza firma alterada | |
| try: | |
| sk = Ed25519PrivateKey.generate() | |
| pk = sk.public_key() | |
| sig = sk.sign(b"data") | |
| bad_sig = bytearray(sig); bad_sig[0] ^= 0xFF | |
| try: | |
| pk.verify(bytes(bad_sig), b"data") | |
| results.append("β T6 Ed25519 deberΓa rechazar firma invΓ‘lida") | |
| except Exception: | |
| results.append("β T6 Ed25519 rechaza firma alterada OK") | |
| except Exception as e: | |
| results.append(f"β T6 {e}") | |
| # T7 β X25519 shared secret simΓ©trico | |
| try: | |
| sk1, pk1 = X25519PrivateKey.generate(), None | |
| sk2 = X25519PrivateKey.generate() | |
| pk1 = sk1.public_key() | |
| pk2 = sk2.public_key() | |
| assert sk1.exchange(pk2) == sk2.exchange(pk1) | |
| results.append("β T7 X25519 ECDH β shared secrets idΓ©nticos OK") | |
| except Exception as e: | |
| results.append(f"β T7 {e}") | |
| # T8 β Argon2id hash/verify | |
| try: | |
| pwd = "test_password" | |
| h = nacl.pwhash.str(pwd.encode()) | |
| nacl.pwhash.verify(h, pwd.encode()) | |
| results.append("β T8 Argon2id hash/verify OK") | |
| except Exception as e: | |
| results.append(f"β T8 {e}") | |
| # T9 β Argon2id rechaza contraseΓ±a incorrecta | |
| try: | |
| h = nacl.pwhash.str(b"correcta") | |
| try: | |
| nacl.pwhash.verify(h, b"incorrecta") | |
| results.append("β T9 Argon2id deberΓa rechazar password incorrecto") | |
| except Exception: | |
| results.append("β T9 Argon2id rechaza password incorrecto OK") | |
| except Exception as e: | |
| results.append(f"β T9 {e}") | |
| # T10 β Diceware entropΓa mΓnima | |
| try: | |
| dw = generate_diceware(6) | |
| assert dw["entropy"] > 60 | |
| results.append(f"β T10 Diceware 6 palabras β {dw['entropy']} bits OK") | |
| except Exception as e: | |
| results.append(f"β T10 {e}") | |
| # T11 β Token 256 bits hex tiene longitud correcta | |
| try: | |
| t = generate_token(256, "hex") | |
| assert len(t) == 64 # 256 bits / 4 bits_per_hex_char | |
| results.append(f"β T11 Token hex 256 bits β {len(t)} chars OK") | |
| except Exception as e: | |
| results.append(f"β T11 {e}") | |
| # T12 β PBKDF2 β AES-GCM round trip con contraseΓ±a | |
| try: | |
| enc = aes_gcm_encrypt("hello world", "mi_password") | |
| dec = aes_gcm_decrypt(enc["ciphertext"], enc["key_hex"]) | |
| assert dec["success"] and dec["plaintext"] == "hello world" | |
| results.append("β T12 PBKDF2+AES-GCM encrypt/decrypt con password OK") | |
| except Exception as e: | |
| results.append(f"β T12 {e}") | |
| passed = sum(1 for r in results if r.startswith("β ")) | |
| total = len(results) | |
| results.append(f"\n{'='*48}") | |
| results.append(f"Resultado: {passed}/{total} tests pasados") | |
| if passed == total: | |
| results.append("π Suite completa β sin fallos") | |
| return "\n".join(results) | |
| def get_system_info(): | |
| info = { | |
| "app_version": VERSION, | |
| "python_version": PYTHON_VERSION, | |
| "rng_source": RNG_SOURCE, | |
| "cryptography_lib": "cryptography (AES-256-GCM, Ed25519, X25519, PBKDF2)", | |
| "pynacl_version": f"PyNaCl {nacl.__version__} (Argon2id via libsodium)", | |
| "backend": str(default_backend()), | |
| "symbols_charset": f"{SYMBOLS!r} ({len(SYMBOLS)} chars)", | |
| "processing_note": "Todo se procesa en la instancia donde corre esta aplicaciΓ³n. Sin envΓo externo.", | |
| "timestamp": datetime.now().isoformat(), | |
| } | |
| return json.dumps(info, indent=2) | |
| # βββ CSS / UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| css = """ | |
| .mono { font-family: 'Courier New', monospace; font-size: 0.9em; background: #f8f9fa; | |
| padding: 12px; border-radius: 8px; word-break: break-all; border: 1px solid #e9ecef; } | |
| .strength-badge { font-weight: bold; padding: 8px 16px; border-radius: 6px; | |
| display: inline-block; margin-top: 6px; color: white; } | |
| .info-box { background: #e7f3ff; padding: 12px; border-radius: 8px; | |
| border-left: 4px solid #2196F3; margin: 6px 0; } | |
| .ok-box { background: #e8f5e9; padding: 12px; border-radius: 8px; | |
| border-left: 4px solid #4caf50; margin: 6px 0; } | |
| .crypto-box { background: #f3e5f5; padding: 12px; border-radius: 8px; | |
| border-left: 4px solid #9c27b0; margin: 6px 0; } | |
| """ | |
| # βββ APP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks(title="CryptoBox Pro", css=css) as demo: | |
| gr.Markdown(f""" | |
| # π CryptoBox Pro v{VERSION} | |
| Caja de herramientas criptogrΓ‘fica. Todo se procesa en la instancia local donde corre esta app. | |
| `secrets` (CSPRNG) Β· `cryptography` Β· `PyNaCl / libsodium` | |
| """) | |
| # ββ TAB 1 β ContraseΓ±as ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("ContraseΓ±as"): | |
| gr.Markdown("### Generador con distribuciΓ³n uniforme rigurosa") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| pwd_length = gr.Slider(8, 64, value=16, step=1, label="Longitud") | |
| pwd_upper = gr.Checkbox(value=True, label="MayΓΊsculas A-Z") | |
| pwd_nums = gr.Checkbox(value=True, label="NΓΊmeros 0-9") | |
| pwd_syms = gr.Checkbox(value=True, label=f"SΓmbolos ({SYMBOLS[:12]}β¦)") | |
| with gr.Row(): | |
| btn_pwd = gr.Button("Generar", variant="primary") | |
| btn_pwd_128 = gr.Button("~128 bits", variant="secondary") | |
| btn_pwd_256 = gr.Button("~256 bits", variant="secondary") | |
| with gr.Column(scale=2): | |
| pwd_output = gr.Textbox(label="ContraseΓ±a", interactive=False, elem_classes=["mono"]) | |
| pwd_entropy = gr.Textbox(label="EntropΓa real", interactive=False) | |
| pwd_strength = gr.HTML() | |
| pwd_info = gr.HTML() | |
| def _pwd_render(pwd, upper, nums, syms): | |
| entropy = calculate_entropy(pwd) | |
| strength, color, desc = check_strength(entropy) | |
| charset = get_charset_size(upper, nums, syms) | |
| badge = f'<div class="strength-badge" style="background:{color};">{strength}</div>' | |
| info = f'<div class="info-box">Charset: {charset} chars Β· {math.log2(charset):.2f} bits/char</div>' | |
| return pwd, f"{entropy} bits ({desc})", badge, info | |
| def on_pwd_generate(length, upper, nums, syms): | |
| return _pwd_render(generate_password_rigorous(length, upper, nums, syms), upper, nums, syms) | |
| def on_pwd_128(upper, nums, syms): | |
| pwd, length = generate_password_128_bits(upper, nums, syms) | |
| return *_pwd_render(pwd, upper, nums, syms), length | |
| def on_pwd_256(upper, nums, syms): | |
| pwd, length = generate_password_256_bits(upper, nums, syms) | |
| return *_pwd_render(pwd, upper, nums, syms), length | |
| btn_pwd.click(on_pwd_generate, [pwd_length, pwd_upper, pwd_nums, pwd_syms], | |
| [pwd_output, pwd_entropy, pwd_strength, pwd_info]) | |
| btn_pwd_128.click(on_pwd_128, [pwd_upper, pwd_nums, pwd_syms], | |
| [pwd_output, pwd_entropy, pwd_strength, pwd_info, pwd_length]) | |
| btn_pwd_256.click(on_pwd_256, [pwd_upper, pwd_nums, pwd_syms], | |
| [pwd_output, pwd_entropy, pwd_strength, pwd_info, pwd_length]) | |
| # ββ TAB 2 β Tokens βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Tokens"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| token_bits = gr.Radio([128, 256, 512], value=256, label="Bits") | |
| token_format = gr.Radio( | |
| ["hex", "base64", "base64url", "binary", "uuid_style"], | |
| value="hex", label="Formato") | |
| btn_token = gr.Button("Generar token", variant="primary") | |
| btn_token_5 = gr.Button("Generar Γ5", variant="secondary") | |
| with gr.Column(): | |
| token_output = gr.Textbox(label="Token", interactive=False, lines=3, elem_classes=["mono"]) | |
| token_multiple = gr.Textbox(label="MΓΊltiples tokens", interactive=False, lines=10, elem_classes=["mono"]) | |
| btn_token.click(generate_token, | |
| [token_bits, token_format], [token_output]) | |
| btn_token_5.click(lambda b, f: "\n".join(generate_token(b, f) for _ in range(5)), | |
| [token_bits, token_format], [token_multiple]) | |
| # ββ TAB 3 β Claves βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Claves"): | |
| with gr.Row(): | |
| btn_aes = gr.Button("AES-256 Key", variant="primary") | |
| btn_hmac = gr.Button("HMAC-256 Key", variant="secondary") | |
| key_output = gr.Textbox(label="Clave generada", interactive=False, lines=6, elem_classes=["mono"]) | |
| def on_aes(): | |
| k = generate_aes_key() | |
| return (f"Hex: {k['hex']}\n" | |
| f"Base64: {k['base64']}\n" | |
| f"Base64URL: {k['base64url']}\n" | |
| f"TamaΓ±o: {k['bytes']} bytes = {k['bits']} bits\n" | |
| f"Uso: AES-256-GCM, AES-256-CBC") | |
| def on_hmac(): | |
| k = generate_hmac_key(256) | |
| return (f"Hex: {k['hex']}\n" | |
| f"Base64: {k['base64']}\n" | |
| f"TamaΓ±o: {k['bits']} bits\n" | |
| f"Uso: HMAC-SHA256, JWT HS256") | |
| btn_aes.click(on_aes, [], [key_output]) | |
| btn_hmac.click(on_hmac, [], [key_output]) | |
| # ββ TAB 4 β Cifrado AES-256-GCM ββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Cifrado AES-GCM"): | |
| gr.Markdown("### Cifrado autenticado AES-256-GCM (AEAD)") | |
| with gr.Row(): | |
| with gr.Column(): | |
| enc_msg = gr.Textbox(label="Mensaje a cifrar", lines=3) | |
| enc_pwd = gr.Textbox(label="ContraseΓ±a opcional (si vacΓo β clave aleatoria)", type="password") | |
| btn_encrypt = gr.Button("Cifrar", variant="primary") | |
| with gr.Column(): | |
| enc_output = gr.Textbox(label="Resultado", interactive=False, lines=9, elem_classes=["mono"]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| dec_ciphertext = gr.Textbox(label="Ciphertext (Base64)", lines=2) | |
| dec_key = gr.Textbox(label="Clave (Hex)") | |
| btn_decrypt = gr.Button("Descifrar", variant="primary") | |
| with gr.Column(): | |
| dec_output = gr.Textbox(label="Mensaje descifrado", interactive=False, elem_classes=["mono"]) | |
| def on_encrypt(msg, pwd): | |
| if not msg: | |
| return "β οΈ Introduce un mensaje" | |
| r = aes_gcm_encrypt(msg, pwd) | |
| if "error" in r: | |
| return f"β {r['error']}" | |
| return (f"Algoritmo: {r['algorithm']} β {r['mode']}\n\n" | |
| f"Ciphertext (Base64):\n{r['ciphertext']}\n\n" | |
| f"Clave (Hex):\n{r['key_hex']}\n\n" | |
| f"Clave (Base64):\n{r['key_b64']}\n\n" | |
| f"Nonce: {r['nonce']}\n{r['key_source']}") | |
| def on_decrypt(ct, key): | |
| if not ct or not key: | |
| return "β οΈ Introduce ciphertext y clave" | |
| r = aes_gcm_decrypt(ct, key) | |
| if r.get("success"): | |
| return f"β {r['plaintext']}" | |
| return f"β {r.get('error', 'Error desconocido')}" | |
| btn_encrypt.click(on_encrypt, [enc_msg, enc_pwd], [enc_output]) | |
| btn_decrypt.click(on_decrypt, [dec_ciphertext, dec_key], [dec_output]) | |
| # ββ TAB 5 β Argon2id βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Argon2id"): | |
| gr.Markdown("### Hash de contraseΓ±as y derivaciΓ³n de claves") | |
| with gr.Row(): | |
| with gr.Column(): | |
| argon_pwd = gr.Textbox(label="ContraseΓ±a", type="password") | |
| with gr.Row(): | |
| btn_argon_hash = gr.Button("Hash Argon2id", variant="primary") | |
| btn_argon_derive = gr.Button("Derivar clave", variant="secondary") | |
| gr.Markdown("**Verificar hash**") | |
| argon_verify_pwd = gr.Textbox(label="ContraseΓ±a a verificar", type="password") | |
| argon_verify_hash = gr.Textbox(label="Hash Argon2id") | |
| btn_argon_verify = gr.Button("Verificar", variant="secondary") | |
| with gr.Column(): | |
| argon_output = gr.Textbox(label="Resultado", interactive=False, lines=10, elem_classes=["mono"]) | |
| def on_argon_hash(pwd): | |
| if not pwd: return "β οΈ Introduce una contraseΓ±a" | |
| r = argon2id_hash(pwd) | |
| if "error" in r: return f"β {r['error']}" | |
| return f"Algoritmo: {r['algorithm']}\n{r['params']}\n{r['note']}\n\n{r['hash']}" | |
| def on_argon_derive(pwd): | |
| if not pwd: return "β οΈ Introduce una contraseΓ±a" | |
| r = derive_key_argon2(pwd) | |
| if "error" in r: return f"β {r['error']}" | |
| return (f"Algoritmo: {r['algorithm']}\n" | |
| f"Clave Hex: {r['key_hex']}\n" | |
| f"Clave B64: {r['key_b64']}\n" | |
| f"Salt: {r['salt']}\n" | |
| f"Longitud: {r['length']} bytes") | |
| def on_argon_verify(pwd, hash_str): | |
| if not pwd or not hash_str: return "β οΈ Ambos campos requeridos" | |
| r = argon2id_verify(pwd, hash_str) | |
| return "β ContraseΓ±a CORRECTA" if r["valid"] else f"β ContraseΓ±a INCORRECTA β {r.get('error','')}" | |
| btn_argon_hash.click(on_argon_hash, [argon_pwd], [argon_output]) | |
| btn_argon_derive.click(on_argon_derive, [argon_pwd], [argon_output]) | |
| btn_argon_verify.click(on_argon_verify, [argon_verify_pwd, argon_verify_hash], [argon_output]) | |
| # ββ TAB 6 β Diceware βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Diceware"): | |
| gr.Markdown("### Passphrase memorable de alta entropΓa") | |
| with gr.Row(): | |
| with gr.Column(): | |
| dice_words = gr.Slider(4, 12, value=6, step=1, label="Palabras") | |
| dice_sep = gr.Radio(["-", " ", "_", "."], value="-", label="Separador") | |
| btn_dice = gr.Button("Generar passphrase", variant="primary") | |
| with gr.Column(): | |
| dice_output = gr.Textbox(label="Passphrase", interactive=False, elem_classes=["mono"]) | |
| dice_info = gr.HTML() | |
| def on_dice(words, sep): | |
| r = generate_diceware(words, sep) | |
| info = (f'<div class="info-box">' | |
| f'<b>EntropΓa:</b> {r["entropy"]} bits Β· ' | |
| f'<b>Bits/palabra:</b> {r["bits_per_word"]} Β· ' | |
| f'<b>Lista:</b> {r["wordlist_size"]} palabras (espaΓ±ol)</div>') | |
| return r["passphrase"], info | |
| btn_dice.click(on_dice, [dice_words, dice_sep], [dice_output, dice_info]) | |
| # ββ TAB 7 β Ed25519 ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Ed25519"): | |
| gr.Markdown("### Firmas digitales") | |
| with gr.Row(): | |
| with gr.Column(): | |
| btn_gen_ed = gr.Button("Generar par de claves", variant="primary") | |
| ed_keys = gr.Textbox(label="Claves", interactive=False, lines=12, elem_classes=["mono"]) | |
| with gr.Column(): | |
| ed_msg = gr.Textbox(label="Mensaje a firmar") | |
| ed_priv = gr.Textbox(label="Clave privada (Hex)") | |
| btn_sign = gr.Button("Firmar", variant="primary") | |
| ed_sig = gr.Textbox(label="Firma", interactive=False, elem_classes=["mono"]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| ed_verify_msg = gr.Textbox(label="Mensaje") | |
| ed_verify_sig = gr.Textbox(label="Firma (Hex)") | |
| ed_verify_pub = gr.Textbox(label="Clave pΓΊblica (Hex)") | |
| btn_verify = gr.Button("Verificar firma", variant="primary") | |
| with gr.Column(): | |
| ed_verify_result = gr.HTML() | |
| def on_gen_ed(): | |
| k = generate_ed25519_keys() | |
| if "error" in k: return f"β {k['error']}" | |
| return (f"Algoritmo: {k['algorithm']} ({k['key_size']})\n\n" | |
| f"Privada (Hex):\n{k['private_hex']}\n\n" | |
| f"PΓΊblica (Hex):\n{k['public_hex']}\n\n" | |
| f"β PEM β\n{k['private_pem']}\n{k['public_pem']}") | |
| def on_sign(msg, priv): | |
| if not msg or not priv: return "β οΈ Mensaje y clave privada requeridos" | |
| r = ed25519_sign(msg, priv) | |
| if "error" in r: return f"β {r['error']}" | |
| return f"Hex: {r['signature']}\nBase64: {r['signature_b64']}" | |
| def on_verify(msg, sig, pub): | |
| if not all([msg, sig, pub]): | |
| return '<div class="info-box">β οΈ Todos los campos son requeridos</div>' | |
| r = ed25519_verify(msg, sig, pub) | |
| color = "#4caf50" if r["valid"] else "#f44336" | |
| return f'<div class="strength-badge" style="background:{color};">{r["message"]}</div>' | |
| btn_gen_ed.click(on_gen_ed, [], [ed_keys]) | |
| btn_sign.click(on_sign, [ed_msg, ed_priv], [ed_sig]) | |
| btn_verify.click(on_verify, [ed_verify_msg, ed_verify_sig, ed_verify_pub], [ed_verify_result]) | |
| # ββ TAB 8 β X25519 βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("X25519"): | |
| gr.Markdown("### Intercambio de claves ECDH") | |
| with gr.Row(): | |
| with gr.Column(): | |
| btn_gen_x1 = gr.Button("Generar par A", variant="primary") | |
| x1_keys = gr.Textbox(label="Par A", interactive=False, lines=4, elem_classes=["mono"]) | |
| with gr.Column(): | |
| btn_gen_x2 = gr.Button("Generar par B", variant="primary") | |
| x2_keys = gr.Textbox(label="Par B", interactive=False, lines=4, elem_classes=["mono"]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| x_priv = gr.Textbox(label="Tu clave privada (Hex)") | |
| x_pub_other = gr.Textbox(label="Clave pΓΊblica del otro (Hex)") | |
| btn_shared = gr.Button("Calcular secreto compartido", variant="primary") | |
| with gr.Column(): | |
| x_shared = gr.Textbox(label="Secreto compartido", interactive=False, lines=5, elem_classes=["mono"]) | |
| def on_gen_x(): | |
| k = generate_x25519_keys() | |
| if "error" in k: return f"β {k['error']}" | |
| return f"Privada: {k['private_hex']}\nPΓΊblica: {k['public_hex']}\n{k['algorithm']} Β· {k['key_size']}" | |
| def on_shared(priv, pub): | |
| if not priv or not pub: return "β οΈ Ambas claves requeridas" | |
| r = x25519_shared_secret(priv, pub) | |
| if "error" in r: return f"β {r['error']}" | |
| return (f"Hex: {r['shared_secret_hex']}\n" | |
| f"Base64: {r['shared_secret_b64']}\n" | |
| f"TamaΓ±o: {r['length']} bytes = {r['bits']} bits\n\n" | |
| f"Usa este valor directamente como clave AES-256.") | |
| btn_gen_x1.click(on_gen_x, [], [x1_keys]) | |
| btn_gen_x2.click(on_gen_x, [], [x2_keys]) | |
| btn_shared.click(on_shared, [x_priv, x_pub_other], [x_shared]) | |
| # ββ TAB 9 β API Secrets βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("API Secrets"): | |
| with gr.Row(): | |
| btn_jwt = gr.Button("JWT Secret", variant="primary") | |
| btn_api = gr.Button("API Key", variant="primary") | |
| btn_session = gr.Button("Session Token", variant="secondary") | |
| btn_oauth = gr.Button("OAuth Secret", variant="secondary") | |
| api_output = gr.Textbox(label="Secreto generado", interactive=False, lines=5, elem_classes=["mono"]) | |
| btn_jwt.click(lambda: (lambda r: f"{r['type']}\n{r['secret']}\n\n{r['length_chars']} chars Β· ~{r['estimated_bits']} bits")(generate_jwt_secret()), | |
| [], [api_output]) | |
| btn_api.click(lambda: (lambda r: f"{r['type']}\n{r['api_key']}")(generate_api_key()), | |
| [], [api_output]) | |
| btn_session.click(lambda: (lambda r: f"{r['type']}\n{r['token']}\n\n{r['length']} chars Β· ~{r['estimated_bits']} bits")(generate_session_token()), | |
| [], [api_output]) | |
| btn_oauth.click(lambda: (lambda r: f"{r['type']}\n{r['secret']}\n\n{r['length']} chars Β· ~{r['estimated_bits']} bits")(generate_oauth_secret()), | |
| [], [api_output]) | |
| # ββ TAB 10 β EsteganografΓa βββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("EsteganografΓa"): | |
| gr.Markdown(""" | |
| ### AES-256-GCM + LSB Steganography | |
| Cifra el mensaje con AES-256-GCM y esconde el ciphertext en el bit menos significativo | |
| de cada canal RGB de la imagen PNG. Sin la clave, la imagen parece normal. | |
| """) | |
| with gr.Row(): | |
| # ββ OCULTAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Column(): | |
| gr.Markdown("#### π Ocultar mensaje") | |
| steg_image_in = gr.Image(label="Imagen portadora (PNG recomendado)", | |
| type="filepath", sources=["upload"]) | |
| steg_msg_in = gr.Textbox(label="Mensaje a ocultar", lines=4) | |
| steg_pwd_in = gr.Textbox(label="ContraseΓ±a (opcional; si vacΓo β clave aleatoria)", | |
| type="password") | |
| btn_steg_hide = gr.Button("Cifrar + Ocultar", variant="primary") | |
| steg_key_out = gr.Textbox(label="π Clave AES-256 (HEX) β guΓ‘rdala", | |
| interactive=False, elem_classes=["mono"]) | |
| steg_info_out = gr.HTML() | |
| steg_image_out = gr.Image(label="Imagen con mensaje oculto", | |
| type="filepath", interactive=False) | |
| # ββ EXTRAER ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Column(): | |
| gr.Markdown("#### π Extraer mensaje") | |
| steg_image_ext = gr.Image(label="Imagen con datos ocultos", | |
| type="filepath", sources=["upload"]) | |
| steg_key_in = gr.Textbox(label="Clave AES-256 (HEX)") | |
| btn_steg_ext = gr.Button("Extraer + Descifrar", variant="primary") | |
| steg_result_out = gr.Textbox(label="Mensaje recuperado", | |
| interactive=False, lines=6, | |
| elem_classes=["mono"]) | |
| # ββ Handlers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def on_steg_hide(image_path, message, password): | |
| if not image_path: | |
| return "", '<div class="info-box">β οΈ Sube una imagen</div>', None | |
| if not message: | |
| return "", '<div class="info-box">β οΈ Escribe un mensaje</div>', None | |
| with open(image_path, "rb") as f: | |
| image_bytes = f.read() | |
| png_bytes, meta = steg_hide(image_bytes, message, password) | |
| if png_bytes is None: | |
| return "", f'<div class="info-box">β {meta["error"]}</div>', None | |
| # Guardar imagen resultante en temp β compatible Linux/Windows/macOS | |
| out_path = os.path.join(tempfile.gettempdir(), "steg_output.png") | |
| with open(out_path, "wb") as f: | |
| f.write(png_bytes) | |
| info_html = ( | |
| f'<div class="ok-box">' | |
| f'<b>Algoritmo:</b> {meta["algorithm"]}<br>' | |
| f'<b>Fuente clave:</b> {meta["key_source"]}<br>' | |
| f'<b>Nonce:</b> {meta["nonce"]}<br>' | |
| f'<b>Bits usados:</b> {meta["bits_used"]} / {meta["image_capacity"]}<br>' | |
| f'<b>OcupaciΓ³n:</b> {meta["bits_used"]/meta["image_capacity"]*100:.2f}%' | |
| f'</div>' | |
| ) | |
| return meta["key_hex"], info_html, out_path | |
| def on_steg_extract(image_path, key_hex): | |
| if not image_path: | |
| return "β οΈ Sube una imagen" | |
| if not key_hex: | |
| return "β οΈ Introduce la clave AES-256 en hex" | |
| with open(image_path, "rb") as f: | |
| image_bytes = f.read() | |
| result = steg_extract(image_bytes, key_hex) | |
| if not result.get("success"): | |
| return f"β {result.get('error', 'Error desconocido')}" | |
| return result["plaintext"] | |
| btn_steg_hide.click( | |
| on_steg_hide, | |
| [steg_image_in, steg_msg_in, steg_pwd_in], | |
| [steg_key_out, steg_info_out, steg_image_out], | |
| ) | |
| btn_steg_ext.click( | |
| on_steg_extract, | |
| [steg_image_ext, steg_key_in], | |
| [steg_result_out], | |
| ) | |
| # ββ TAB 11 β AuditorΓa βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("AuditorΓa"): | |
| gr.Markdown("### InformaciΓ³n del sistema y suite de tests criptogrΓ‘ficos") | |
| with gr.Row(): | |
| btn_info = gr.Button("Info del sistema", variant="secondary") | |
| btn_tests = gr.Button("Ejecutar tests", variant="primary") | |
| audit_output = gr.Textbox(label="Resultado", interactive=False, lines=25, elem_classes=["mono"]) | |
| btn_info.click(get_system_info, [], [audit_output]) | |
| btn_tests.click(run_crypto_tests, [], [audit_output]) | |
| gr.Markdown(f"---\nCryptoBox Pro v{VERSION} Β· Python {PYTHON_VERSION} Β· cryptography Β· PyNaCl") | |
| if __name__ == "__main__": | |
| demo.launch() |