Scrappy-Doo commited on
Commit
c323fa3
Β·
verified Β·
1 Parent(s): dbebbca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -1
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 β€” AuditorΓ­a ───────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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():