Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import secrets
|
| 3 |
import string
|
|
@@ -6,7 +8,10 @@ import math
|
|
| 6 |
import json
|
| 7 |
import os
|
| 8 |
import sys
|
|
|
|
| 9 |
from datetime import datetime
|
|
|
|
|
|
|
| 10 |
|
| 11 |
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
| 12 |
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
|
@@ -181,6 +186,103 @@ def aes_gcm_decrypt(ciphertext_b64, key_hex):
|
|
| 181 |
return {"error": str(e), "success": False}
|
| 182 |
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
# βββ ARGON2id ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 185 |
|
| 186 |
def argon2id_hash(password):
|
|
@@ -834,7 +936,99 @@ with gr.Blocks(title="CryptoBox Pro", css=css) as demo:
|
|
| 834 |
btn_oauth.click(lambda: (lambda r: f"{r['type']}\n{r['secret']}\n\n{r['length']} chars Β· ~{r['estimated_bits']} bits")(generate_oauth_secret()),
|
| 835 |
[], [api_output])
|
| 836 |
|
| 837 |
-
# ββ TAB 10 β
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
with gr.Tab("AuditorΓa"):
|
| 839 |
gr.Markdown("### InformaciΓ³n del sistema y suite de tests criptogrΓ‘ficos")
|
| 840 |
with gr.Row():
|
|
|
|
| 1 |
+
from __future__ import annotations # tuple[...] compatible con Python 3.8+
|
| 2 |
+
|
| 3 |
import gradio as gr
|
| 4 |
import secrets
|
| 5 |
import string
|
|
|
|
| 8 |
import json
|
| 9 |
import os
|
| 10 |
import sys
|
| 11 |
+
import tempfile
|
| 12 |
from datetime import datetime
|
| 13 |
+
from PIL import Image
|
| 14 |
+
import io
|
| 15 |
|
| 16 |
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
| 17 |
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
|
|
|
| 186 |
return {"error": str(e), "success": False}
|
| 187 |
|
| 188 |
|
| 189 |
+
# βββ ESTEGANOGRAFΓA AES-256-GCM + LSB ββββββββββββββββββββββββββββββββββββββββ
|
| 190 |
+
|
| 191 |
+
END_MARKER = "1111111111111110" # 0xFFFE en binario β marcador de fin
|
| 192 |
+
|
| 193 |
+
def steg_hide(image_bytes: bytes, message: str, password: str = "") -> "tuple[bytes, dict]":
|
| 194 |
+
"""
|
| 195 |
+
1. Cifra el mensaje con AES-256-GCM (clave aleatoria o derivada de password)
|
| 196 |
+
2. Convierte el ciphertext Base64 a bits
|
| 197 |
+
3. Incrusta los bits en el LSB de los canales RGB del PNG
|
| 198 |
+
Retorna (png_bytes, metadata_dict)
|
| 199 |
+
"""
|
| 200 |
+
# Cifrar primero
|
| 201 |
+
enc = aes_gcm_encrypt(message, password)
|
| 202 |
+
if "error" in enc:
|
| 203 |
+
return None, {"error": enc["error"]}
|
| 204 |
+
|
| 205 |
+
payload = enc["ciphertext"] # string Base64
|
| 206 |
+
key_hex = enc["key_hex"]
|
| 207 |
+
|
| 208 |
+
binary = "".join(format(b, "08b") for b in payload.encode("utf-8"))
|
| 209 |
+
binary += END_MARKER
|
| 210 |
+
|
| 211 |
+
img = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 212 |
+
pixels = list(img.getdata())
|
| 213 |
+
|
| 214 |
+
# Capacidad: 3 bits por pixel (R, G, B)
|
| 215 |
+
capacity_bits = len(pixels) * 3
|
| 216 |
+
if len(binary) > capacity_bits:
|
| 217 |
+
return None, {"error": f"Imagen demasiado pequeΓ±a. Necesitas {len(binary)} bits, capacidad: {capacity_bits}"}
|
| 218 |
+
|
| 219 |
+
new_pixels = []
|
| 220 |
+
bit_idx = 0
|
| 221 |
+
|
| 222 |
+
for pixel in pixels:
|
| 223 |
+
r, g, b = pixel
|
| 224 |
+
if bit_idx < len(binary):
|
| 225 |
+
r = (r & 0xFE) | int(binary[bit_idx]); bit_idx += 1
|
| 226 |
+
if bit_idx < len(binary):
|
| 227 |
+
g = (g & 0xFE) | int(binary[bit_idx]); bit_idx += 1
|
| 228 |
+
if bit_idx < len(binary):
|
| 229 |
+
b = (b & 0xFE) | int(binary[bit_idx]); bit_idx += 1
|
| 230 |
+
new_pixels.append((r, g, b))
|
| 231 |
+
|
| 232 |
+
img.putdata(new_pixels)
|
| 233 |
+
buf = io.BytesIO()
|
| 234 |
+
img.save(buf, format="PNG")
|
| 235 |
+
|
| 236 |
+
return buf.getvalue(), {
|
| 237 |
+
"key_hex": key_hex,
|
| 238 |
+
"key_b64": enc["key_b64"],
|
| 239 |
+
"nonce": enc["nonce"],
|
| 240 |
+
"payload_bytes": len(payload.encode()),
|
| 241 |
+
"bits_used": len(binary),
|
| 242 |
+
"image_capacity": capacity_bits,
|
| 243 |
+
"algorithm": "AES-256-GCM + LSB steganography",
|
| 244 |
+
"key_source": enc["key_source"],
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
def steg_extract(image_bytes: bytes, key_hex: str) -> dict:
|
| 248 |
+
"""
|
| 249 |
+
1. Extrae los bits LSB de los canales RGB
|
| 250 |
+
2. Reconstruye el payload Base64 hasta el END_MARKER
|
| 251 |
+
3. Descifra con AES-256-GCM usando key_hex
|
| 252 |
+
"""
|
| 253 |
+
img = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 254 |
+
pixels = list(img.getdata())
|
| 255 |
+
|
| 256 |
+
bits = ""
|
| 257 |
+
for pixel in pixels:
|
| 258 |
+
for channel in pixel[:3]:
|
| 259 |
+
bits += str(channel & 1)
|
| 260 |
+
|
| 261 |
+
# Buscar END_MARKER reconstruyendo byte a byte
|
| 262 |
+
chars = []
|
| 263 |
+
for i in range(0, len(bits) - 7, 8):
|
| 264 |
+
byte = bits[i:i+8]
|
| 265 |
+
chars.append(chr(int(byte, 2)))
|
| 266 |
+
# Comprobar si los ΓΊltimos 16 bits de chars forman el END_MARKER
|
| 267 |
+
if len(chars) >= 2:
|
| 268 |
+
last_two = "".join(format(ord(c), "08b") for c in chars[-2:])
|
| 269 |
+
if last_two == END_MARKER:
|
| 270 |
+
chars = chars[:-2] # quitar los 2 chars del marcador
|
| 271 |
+
break
|
| 272 |
+
|
| 273 |
+
if not chars:
|
| 274 |
+
return {"error": "No se encontrΓ³ marcador de fin. ΒΏImagen sin datos ocultos?"}
|
| 275 |
+
|
| 276 |
+
payload_b64 = "".join(chars)
|
| 277 |
+
|
| 278 |
+
# Descifrar
|
| 279 |
+
dec = aes_gcm_decrypt(payload_b64, key_hex)
|
| 280 |
+
if not dec.get("success"):
|
| 281 |
+
return {"error": f"Descifrado fallido: {dec.get('error', 'clave incorrecta')}"}
|
| 282 |
+
|
| 283 |
+
return {"plaintext": dec["plaintext"], "success": True}
|
| 284 |
+
|
| 285 |
+
|
| 286 |
# βββ ARGON2id ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 287 |
|
| 288 |
def argon2id_hash(password):
|
|
|
|
| 936 |
btn_oauth.click(lambda: (lambda r: f"{r['type']}\n{r['secret']}\n\n{r['length']} chars Β· ~{r['estimated_bits']} bits")(generate_oauth_secret()),
|
| 937 |
[], [api_output])
|
| 938 |
|
| 939 |
+
# ββ TAB 10 β EsteganografΓa βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 940 |
+
with gr.Tab("EsteganografΓa"):
|
| 941 |
+
gr.Markdown("""
|
| 942 |
+
### AES-256-GCM + LSB Steganography
|
| 943 |
+
Cifra el mensaje con AES-256-GCM y esconde el ciphertext en el bit menos significativo
|
| 944 |
+
de cada canal RGB de la imagen PNG. Sin la clave, la imagen parece normal.
|
| 945 |
+
""")
|
| 946 |
+
|
| 947 |
+
with gr.Row():
|
| 948 |
+
# ββ OCULTAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 949 |
+
with gr.Column():
|
| 950 |
+
gr.Markdown("#### π Ocultar mensaje")
|
| 951 |
+
steg_image_in = gr.Image(label="Imagen portadora (PNG recomendado)",
|
| 952 |
+
type="filepath", sources=["upload"])
|
| 953 |
+
steg_msg_in = gr.Textbox(label="Mensaje a ocultar", lines=4)
|
| 954 |
+
steg_pwd_in = gr.Textbox(label="ContraseΓ±a (opcional; si vacΓo β clave aleatoria)",
|
| 955 |
+
type="password")
|
| 956 |
+
btn_steg_hide = gr.Button("Cifrar + Ocultar", variant="primary")
|
| 957 |
+
steg_key_out = gr.Textbox(label="π Clave AES-256 (HEX) β guΓ‘rdala",
|
| 958 |
+
interactive=False, elem_classes=["mono"])
|
| 959 |
+
steg_info_out = gr.HTML()
|
| 960 |
+
steg_image_out = gr.Image(label="Imagen con mensaje oculto",
|
| 961 |
+
type="filepath", interactive=False)
|
| 962 |
+
|
| 963 |
+
# ββ EXTRAER ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 964 |
+
with gr.Column():
|
| 965 |
+
gr.Markdown("#### π Extraer mensaje")
|
| 966 |
+
steg_image_ext = gr.Image(label="Imagen con datos ocultos",
|
| 967 |
+
type="filepath", sources=["upload"])
|
| 968 |
+
steg_key_in = gr.Textbox(label="Clave AES-256 (HEX)")
|
| 969 |
+
btn_steg_ext = gr.Button("Extraer + Descifrar", variant="primary")
|
| 970 |
+
steg_result_out = gr.Textbox(label="Mensaje recuperado",
|
| 971 |
+
interactive=False, lines=6,
|
| 972 |
+
elem_classes=["mono"])
|
| 973 |
+
|
| 974 |
+
# ββ Handlers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 975 |
+
def on_steg_hide(image_path, message, password):
|
| 976 |
+
if not image_path:
|
| 977 |
+
return "", '<div class="info-box">β οΈ Sube una imagen</div>', None
|
| 978 |
+
if not message:
|
| 979 |
+
return "", '<div class="info-box">β οΈ Escribe un mensaje</div>', None
|
| 980 |
+
|
| 981 |
+
with open(image_path, "rb") as f:
|
| 982 |
+
image_bytes = f.read()
|
| 983 |
+
|
| 984 |
+
png_bytes, meta = steg_hide(image_bytes, message, password)
|
| 985 |
+
|
| 986 |
+
if png_bytes is None:
|
| 987 |
+
return "", f'<div class="info-box">β {meta["error"]}</div>', None
|
| 988 |
+
|
| 989 |
+
# Guardar imagen resultante en temp β compatible Linux/Windows/macOS
|
| 990 |
+
out_path = os.path.join(tempfile.gettempdir(), "steg_output.png")
|
| 991 |
+
with open(out_path, "wb") as f:
|
| 992 |
+
f.write(png_bytes)
|
| 993 |
+
|
| 994 |
+
info_html = (
|
| 995 |
+
f'<div class="ok-box">'
|
| 996 |
+
f'<b>Algoritmo:</b> {meta["algorithm"]}<br>'
|
| 997 |
+
f'<b>Fuente clave:</b> {meta["key_source"]}<br>'
|
| 998 |
+
f'<b>Nonce:</b> {meta["nonce"]}<br>'
|
| 999 |
+
f'<b>Bits usados:</b> {meta["bits_used"]} / {meta["image_capacity"]}<br>'
|
| 1000 |
+
f'<b>OcupaciΓ³n:</b> {meta["bits_used"]/meta["image_capacity"]*100:.2f}%'
|
| 1001 |
+
f'</div>'
|
| 1002 |
+
)
|
| 1003 |
+
return meta["key_hex"], info_html, out_path
|
| 1004 |
+
|
| 1005 |
+
def on_steg_extract(image_path, key_hex):
|
| 1006 |
+
if not image_path:
|
| 1007 |
+
return "β οΈ Sube una imagen"
|
| 1008 |
+
if not key_hex:
|
| 1009 |
+
return "β οΈ Introduce la clave AES-256 en hex"
|
| 1010 |
+
|
| 1011 |
+
with open(image_path, "rb") as f:
|
| 1012 |
+
image_bytes = f.read()
|
| 1013 |
+
|
| 1014 |
+
result = steg_extract(image_bytes, key_hex)
|
| 1015 |
+
|
| 1016 |
+
if not result.get("success"):
|
| 1017 |
+
return f"β {result.get('error', 'Error desconocido')}"
|
| 1018 |
+
return result["plaintext"]
|
| 1019 |
+
|
| 1020 |
+
btn_steg_hide.click(
|
| 1021 |
+
on_steg_hide,
|
| 1022 |
+
[steg_image_in, steg_msg_in, steg_pwd_in],
|
| 1023 |
+
[steg_key_out, steg_info_out, steg_image_out],
|
| 1024 |
+
)
|
| 1025 |
+
btn_steg_ext.click(
|
| 1026 |
+
on_steg_extract,
|
| 1027 |
+
[steg_image_ext, steg_key_in],
|
| 1028 |
+
[steg_result_out],
|
| 1029 |
+
)
|
| 1030 |
+
|
| 1031 |
+
# ββ TAB 11 β AuditorΓa βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1032 |
with gr.Tab("AuditorΓa"):
|
| 1033 |
gr.Markdown("### InformaciΓ³n del sistema y suite de tests criptogrΓ‘ficos")
|
| 1034 |
with gr.Row():
|