feat: add cryptography fundamentals lab scripts
This commit is contained in:
515
Lab3_ECB_CBC_v2.py
Normal file
515
Lab3_ECB_CBC_v2.py
Normal file
@@ -0,0 +1,515 @@
|
||||
"""
|
||||
Lab 3: Modos de operacion AES - herramienta pedagogica
|
||||
|
||||
Pestana 1 ECB vs CBC ........ patron visual, entropia de Shannon e histograma
|
||||
Pestana 2 Maleabilidad ...... bit-flipping sobre CBC, y por que GCM lo detiene
|
||||
Pestana 3 Reuso de nonce .... dos imagenes con el mismo nonce en CTR
|
||||
|
||||
Ninguna de estas construcciones debe usarse en produccion: claves fijas,
|
||||
IV predecibles y ausencia de autenticacion son parte de la demostracion.
|
||||
"""
|
||||
|
||||
import math
|
||||
import tkinter as tk
|
||||
from collections import Counter
|
||||
from tkinter import filedialog, messagebox, ttk
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Random import get_random_bytes
|
||||
from Crypto.Util.Padding import pad
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
# --- Parametros ajustables -------------------------------------------------
|
||||
IMG_SIZE = 400 # lado de las imagenes de la pestana 1 (px)
|
||||
SMALL_SIZE = 280 # lado de las imagenes de la pestana 3 (px)
|
||||
HIST_H = 55 # alto del histograma (px)
|
||||
BLOCK = AES.block_size # 16 bytes
|
||||
STEP_BYTES = IMG_SIZE * 3 * 2 # avance por frame de animacion (2 filas RGB)
|
||||
FRAME_DELAY = 10 # ms entre frames
|
||||
|
||||
KEY = b'Sixteen byte key' # clave ASCII de baja entropia: SOLO demostracion
|
||||
IV_FIJO = b'Sixteen byte iv.'
|
||||
|
||||
|
||||
# --- Metricas y utilidades -------------------------------------------------
|
||||
def shannon_entropy(data: bytes) -> float:
|
||||
"""Entropia de Shannon en bits/byte. Maximo teorico: 8.0"""
|
||||
if not data:
|
||||
return 0.0
|
||||
n = len(data)
|
||||
return -sum((c / n) * math.log2(c / n) for c in Counter(data).values())
|
||||
|
||||
|
||||
def block_stats(data: bytes):
|
||||
"""Total, unicos y repetidos de bloques de 16 bytes."""
|
||||
usable = len(data) - (len(data) % BLOCK)
|
||||
blocks = [data[i:i + BLOCK] for i in range(0, usable, BLOCK)]
|
||||
total = len(blocks)
|
||||
unique = len(set(blocks))
|
||||
return total, unique, total - unique
|
||||
|
||||
|
||||
def format_metrics(data: bytes) -> str:
|
||||
h = shannon_entropy(data)
|
||||
total, unique, repeated = block_stats(data)
|
||||
pct = (repeated / total * 100) if total else 0.0
|
||||
return (f"Entropia Shannon: {h:.4f} / 8.0 bits/byte\n"
|
||||
f"Bloques 16B: {total:,} | unicos: {unique:,}\n"
|
||||
f"Repetidos: {repeated:,} ({pct:.1f}%)")
|
||||
|
||||
|
||||
def xor_bytes(a: bytes, b: bytes) -> bytes:
|
||||
return bytes(x ^ y for x, y in zip(a, b))
|
||||
|
||||
|
||||
def printable(data: bytes) -> str:
|
||||
"""Bytes no imprimibles se muestran como punto medio."""
|
||||
return ''.join(chr(c) if 32 <= c < 127 else '.' for c in data)
|
||||
|
||||
|
||||
def draw_histogram(canvas, data, color="#2c6fbb"):
|
||||
"""Histograma de los 256 valores de byte, dibujado en un Canvas."""
|
||||
canvas.delete("all")
|
||||
w = int(canvas.cget("width"))
|
||||
h = int(canvas.cget("height"))
|
||||
if not data:
|
||||
return
|
||||
counts = Counter(data)
|
||||
max_c = max(counts.values())
|
||||
bar_w = w / 256.0
|
||||
for b in range(256):
|
||||
c = counts.get(b, 0)
|
||||
if not c:
|
||||
continue
|
||||
bh = (c / max_c) * (h - 2)
|
||||
x0 = b * bar_w
|
||||
canvas.create_rectangle(x0, h - bh, x0 + bar_w, h,
|
||||
fill=color, outline="")
|
||||
canvas.create_line(0, h - 1, w, h - 1, fill="#888")
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
class AESLab:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("Lab 3: Modos de Operacion AES")
|
||||
self.root.geometry("1380x800")
|
||||
|
||||
nb = ttk.Notebook(root)
|
||||
nb.pack(fill="both", expand=True, padx=8, pady=6)
|
||||
|
||||
self.tab_modes = tk.Frame(nb)
|
||||
self.tab_flip = tk.Frame(nb)
|
||||
self.tab_ctr = tk.Frame(nb)
|
||||
nb.add(self.tab_modes, text=" 1. ECB vs CBC ")
|
||||
nb.add(self.tab_flip, text=" 2. Maleabilidad de CBC ")
|
||||
nb.add(self.tab_ctr, text=" 3. Reuso de nonce (CTR) ")
|
||||
|
||||
self.build_tab_modes()
|
||||
self.build_tab_flip()
|
||||
self.build_tab_ctr()
|
||||
|
||||
# =======================================================================
|
||||
# PESTANA 1: ECB vs CBC
|
||||
# =======================================================================
|
||||
def build_tab_modes(self):
|
||||
t = self.tab_modes
|
||||
self.img_bytes_raw = None
|
||||
self.original_img = None
|
||||
self.busy = False
|
||||
self.random_iv = tk.BooleanVar(value=False)
|
||||
|
||||
panels = tk.Frame(t)
|
||||
panels.pack(pady=8)
|
||||
self.p_orig = self.make_panel(panels, "Original (plaintext)", IMG_SIZE)
|
||||
self.p_ecb = self.make_panel(panels, "ECB - INSEGURO", IMG_SIZE)
|
||||
self.p_cbc = self.make_panel(panels, "CBC - sin patron visible", IMG_SIZE)
|
||||
|
||||
ctrl = tk.Frame(t)
|
||||
ctrl.pack(pady=6)
|
||||
self.btn_load = tk.Button(ctrl, text="1. Cargar Imagen",
|
||||
command=self.load_image)
|
||||
self.btn_load.pack(side=tk.LEFT, padx=8)
|
||||
self.btn_ecb = tk.Button(ctrl, text="2. Ejecutar ECB",
|
||||
command=self.run_ecb)
|
||||
self.btn_ecb.pack(side=tk.LEFT, padx=8)
|
||||
self.btn_cbc = tk.Button(ctrl, text="3. Ejecutar CBC",
|
||||
command=self.run_cbc)
|
||||
self.btn_cbc.pack(side=tk.LEFT, padx=8)
|
||||
tk.Checkbutton(ctrl, text="IV aleatorio por ejecucion",
|
||||
variable=self.random_iv).pack(side=tk.LEFT, padx=15)
|
||||
|
||||
info = tk.Frame(t)
|
||||
info.pack(fill="x", padx=20, pady=4)
|
||||
tk.Label(info, font=("Courier", 9), anchor="w",
|
||||
text=f"Llave (Hex): {KEY.hex().upper()} "
|
||||
f"[ASCII de baja entropia: solo demostracion]"
|
||||
).pack(anchor="w")
|
||||
self.lbl_status = tk.Label(info, text="Estado: esperando imagen...",
|
||||
font=("Arial", 10, "bold"), fg="blue",
|
||||
anchor="w")
|
||||
self.lbl_status.pack(anchor="w")
|
||||
tk.Label(info, justify="left", fg="#444", font=("Arial", 9), anchor="w",
|
||||
text=("La entropia y el histograma de ECB y CBC son practicamente "
|
||||
"identicos: NO sirven para detectar el modo inseguro.\n"
|
||||
"El indicador valido es 'Repetidos': ECB conserva los bloques "
|
||||
"duplicados del original, CBC los elimina por encadenamiento.")
|
||||
).pack(anchor="w")
|
||||
|
||||
def make_panel(self, parent, title, size, with_hist=True):
|
||||
f = tk.Frame(parent)
|
||||
f.pack(side=tk.LEFT, padx=10)
|
||||
tk.Label(f, text=title, font=("Arial", 10, "bold")).pack()
|
||||
|
||||
# Contenedor de tamano fijo EN PIXELES: un Label sin imagen mide su
|
||||
# width en caracteres, no en pixeles.
|
||||
holder = tk.Frame(f, width=size, height=size, bg="#ccc")
|
||||
holder.pack()
|
||||
holder.pack_propagate(False)
|
||||
img_lbl = tk.Label(holder, bg="#ccc", borderwidth=0)
|
||||
img_lbl.pack(fill="both", expand=True)
|
||||
|
||||
stats_lbl = tk.Label(f, text="Entropia: -\nBloques: -\nRepetidos: -",
|
||||
font=("Courier", 9), justify="left",
|
||||
anchor="w", width=42)
|
||||
stats_lbl.pack(pady=(4, 0))
|
||||
|
||||
hist = None
|
||||
if with_hist:
|
||||
hist = tk.Canvas(f, width=size, height=HIST_H, bg="#f7f7f7",
|
||||
highlightthickness=1, highlightbackground="#ddd")
|
||||
hist.pack()
|
||||
tk.Label(f, text="histograma de bytes 0 ... 255",
|
||||
font=("Arial", 7), fg="#777").pack()
|
||||
|
||||
return {"img": img_lbl, "stats": stats_lbl, "hist": hist}
|
||||
|
||||
def update_panel_metrics(self, panel, data, color="#2c6fbb"):
|
||||
panel["stats"].config(text=format_metrics(data))
|
||||
if panel["hist"] is not None:
|
||||
draw_histogram(panel["hist"], data, color)
|
||||
|
||||
def clear_panel(self, panel):
|
||||
panel["img"].config(image='', bg="#ccc")
|
||||
panel["img"].image = None
|
||||
panel["stats"].config(text="Entropia: -\nBloques: -\nRepetidos: -")
|
||||
if panel["hist"] is not None:
|
||||
panel["hist"].delete("all")
|
||||
|
||||
def set_busy(self, busy):
|
||||
self.busy = busy
|
||||
state = tk.DISABLED if busy else tk.NORMAL
|
||||
for b in (self.btn_load, self.btn_ecb, self.btn_cbc):
|
||||
b.config(state=state)
|
||||
|
||||
def load_image(self):
|
||||
path = filedialog.askopenfilename(
|
||||
initialdir=".",
|
||||
filetypes=[("Images", ("*.png", "*.jpg", "*.jpeg", "*.bmp")),
|
||||
("All Files", "*.*")])
|
||||
if not path:
|
||||
return
|
||||
try:
|
||||
img = (Image.open(path)
|
||||
.resize((IMG_SIZE, IMG_SIZE), Image.LANCZOS)
|
||||
.convert('RGB'))
|
||||
self.img_bytes_raw = img.tobytes()
|
||||
self.original_img = img
|
||||
|
||||
self.show(self.p_orig, img)
|
||||
self.update_panel_metrics(self.p_orig, self.img_bytes_raw, "#4a7c3f")
|
||||
self.clear_panel(self.p_ecb)
|
||||
self.clear_panel(self.p_cbc)
|
||||
|
||||
total = len(self.img_bytes_raw) // BLOCK
|
||||
self.lbl_status.config(
|
||||
text=f"Imagen cargada. Bytes: {len(self.img_bytes_raw):,} | "
|
||||
f"Bloques de 16B: {total:,}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"No se pudo cargar la imagen: {e}")
|
||||
|
||||
def check_ready(self):
|
||||
if self.busy:
|
||||
return False
|
||||
if self.img_bytes_raw is None:
|
||||
messagebox.showwarning("Aviso", "Primero carga una imagen.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def run_ecb(self):
|
||||
if not self.check_ready():
|
||||
return
|
||||
ct = AES.new(KEY, AES.MODE_ECB).encrypt(pad(self.img_bytes_raw, BLOCK))
|
||||
self.lbl_status.config(text="Iniciando cifrado ECB...")
|
||||
self.set_busy(True)
|
||||
self.animate(self.p_ecb, ct[:len(self.img_bytes_raw)],
|
||||
self.img_bytes_raw, mode_name="ECB", color="#b03030")
|
||||
|
||||
def run_cbc(self):
|
||||
if not self.check_ready():
|
||||
return
|
||||
iv = get_random_bytes(BLOCK) if self.random_iv.get() else IV_FIJO
|
||||
ct = AES.new(KEY, AES.MODE_CBC, iv).encrypt(pad(self.img_bytes_raw, BLOCK))
|
||||
self.lbl_status.config(
|
||||
text=f"Iniciando cifrado CBC... IV = {iv.hex().upper()}")
|
||||
self.set_busy(True)
|
||||
self.animate(self.p_cbc, ct[:len(self.img_bytes_raw)],
|
||||
self.img_bytes_raw, mode_name="CBC", color="#2c6fbb")
|
||||
|
||||
def animate(self, panel, target, original, current_idx=0,
|
||||
mode_name="", color="#2c6fbb"):
|
||||
if current_idx >= len(original):
|
||||
self.show_bytes(panel, target)
|
||||
self.update_panel_metrics(panel, target, color)
|
||||
self.lbl_status.config(text=f"Cifrado {mode_name} completado.")
|
||||
self.set_busy(False)
|
||||
return
|
||||
|
||||
self.show_bytes(panel, target[:current_idx] + original[current_idx:])
|
||||
self.lbl_status.config(
|
||||
text=f"Cifrando {mode_name}... bloque {current_idx // BLOCK:,}"
|
||||
f" / {len(original) // BLOCK:,}")
|
||||
self.root.after(FRAME_DELAY,
|
||||
lambda: self.animate(panel, target, original,
|
||||
current_idx + STEP_BYTES,
|
||||
mode_name, color))
|
||||
|
||||
def show(self, panel, img_obj):
|
||||
tk_img = ImageTk.PhotoImage(img_obj)
|
||||
panel["img"].config(image=tk_img)
|
||||
panel["img"].image = tk_img
|
||||
|
||||
def show_bytes(self, panel, data, size=IMG_SIZE):
|
||||
try:
|
||||
self.show(panel, Image.frombytes('RGB', (size, size), data))
|
||||
except Exception as e:
|
||||
print(f"Error mostrando bytes: {e}")
|
||||
|
||||
# =======================================================================
|
||||
# PESTANA 2: maleabilidad de CBC (bit-flipping)
|
||||
# =======================================================================
|
||||
def build_tab_flip(self):
|
||||
t = self.tab_flip
|
||||
self.flip_iv = None
|
||||
self.flip_ct = None
|
||||
self.flip_blocks = ["usuario=esteban;", "rol=invitado;;;;",
|
||||
"saldo=0000001000"]
|
||||
|
||||
tk.Label(t, font=("Arial", 10), justify="left", anchor="w",
|
||||
text=("CBC entrega confidencialidad, NO integridad. Un atacante que no conoce la clave "
|
||||
"puede alterar el bloque de texto claro N modificando el bloque de cifrado N-1.\n"
|
||||
"El precio es que el bloque N-1 queda destruido; si ese bloque es relleno o poco "
|
||||
"revisado, el ataque pasa desapercibido.")
|
||||
).pack(anchor="w", padx=20, pady=(12, 8))
|
||||
|
||||
pt_frame = tk.LabelFrame(t, text="Mensaje original (3 bloques de 16 bytes, sin relleno)",
|
||||
padx=10, pady=8)
|
||||
pt_frame.pack(fill="x", padx=20)
|
||||
for i, b in enumerate(self.flip_blocks):
|
||||
tk.Label(pt_frame, font=("Courier", 11), anchor="w",
|
||||
text=f" Bloque {i}: |{b}|").pack(anchor="w")
|
||||
|
||||
atk = tk.LabelFrame(t, text="Ataque", padx=10, pady=8)
|
||||
atk.pack(fill="x", padx=20, pady=10)
|
||||
tk.Label(atk, text="El atacante quiere que el bloque 1 diga "
|
||||
"(exactamente 16 caracteres):",
|
||||
font=("Arial", 10)).pack(anchor="w")
|
||||
self.flip_target = tk.Entry(atk, font=("Courier", 11), width=24)
|
||||
self.flip_target.insert(0, "rol=admin;;;;;;;")
|
||||
self.flip_target.pack(anchor="w", pady=4)
|
||||
|
||||
btns = tk.Frame(atk)
|
||||
btns.pack(anchor="w", pady=4)
|
||||
tk.Button(btns, text="1. Cifrar con CBC",
|
||||
command=self.flip_encrypt).pack(side=tk.LEFT, padx=6)
|
||||
tk.Button(btns, text="2. Manipular el ciphertext",
|
||||
command=self.flip_attack).pack(side=tk.LEFT, padx=6)
|
||||
tk.Button(btns, text="3. Repetir el ataque contra AES-GCM",
|
||||
command=self.flip_gcm).pack(side=tk.LEFT, padx=6)
|
||||
|
||||
self.flip_out = tk.Text(t, height=16, font=("Courier", 10),
|
||||
bg="#1e1e1e", fg="#dcdcdc",
|
||||
insertbackground="#dcdcdc", wrap="none")
|
||||
self.flip_out.pack(fill="both", expand=True, padx=20, pady=(0, 14))
|
||||
self.flip_log("Pulsa '1. Cifrar con CBC' para comenzar.")
|
||||
|
||||
def flip_log(self, text, clear=False):
|
||||
if clear:
|
||||
self.flip_out.delete("1.0", tk.END)
|
||||
self.flip_out.insert(tk.END, text + "\n")
|
||||
self.flip_out.see(tk.END)
|
||||
|
||||
def flip_plaintext(self) -> bytes:
|
||||
return "".join(self.flip_blocks).encode()
|
||||
|
||||
def flip_encrypt(self):
|
||||
self.flip_iv = get_random_bytes(BLOCK)
|
||||
self.flip_ct = AES.new(KEY, AES.MODE_CBC,
|
||||
self.flip_iv).encrypt(self.flip_plaintext())
|
||||
self.flip_log("=== CIFRADO LEGITIMO (CBC) ===", clear=True)
|
||||
self.flip_log(f"IV : {self.flip_iv.hex().upper()}")
|
||||
for i in range(3):
|
||||
c = self.flip_ct[i * BLOCK:(i + 1) * BLOCK]
|
||||
self.flip_log(f"C{i} : {c.hex().upper()}")
|
||||
self.flip_log("")
|
||||
self.flip_log("El atacante intercepta el ciphertext. No conoce la clave.")
|
||||
|
||||
def flip_attack(self):
|
||||
if self.flip_ct is None:
|
||||
messagebox.showwarning("Aviso", "Primero cifra el mensaje (paso 1).")
|
||||
return
|
||||
target = self.flip_target.get().encode()
|
||||
if len(target) != BLOCK:
|
||||
messagebox.showerror(
|
||||
"Error",
|
||||
f"El texto objetivo debe medir exactamente {BLOCK} caracteres "
|
||||
f"(actualmente mide {len(target)}).")
|
||||
return
|
||||
|
||||
# P1' = D(C1) xor C0' => C0' = C0 xor P1 xor P1'
|
||||
delta = xor_bytes(self.flip_blocks[1].encode(), target)
|
||||
forged = bytearray(self.flip_ct)
|
||||
for j in range(BLOCK):
|
||||
forged[j] ^= delta[j]
|
||||
forged = bytes(forged)
|
||||
|
||||
recovered = AES.new(KEY, AES.MODE_CBC,
|
||||
self.flip_iv).decrypt(forged)
|
||||
|
||||
self.flip_log("")
|
||||
self.flip_log("=== ATAQUE DE BIT-FLIPPING ===")
|
||||
self.flip_log(f"Delta aplicado a C0 : {delta.hex().upper()}")
|
||||
self.flip_log(f"C0 manipulado : {forged[:BLOCK].hex().upper()}")
|
||||
self.flip_log("")
|
||||
self.flip_log("Descifrado en el servidor (la clave nunca se toco):")
|
||||
for i in range(3):
|
||||
b = recovered[i * BLOCK:(i + 1) * BLOCK]
|
||||
mark = " <-- DESTRUIDO" if i == 0 else (" <-- CONTROLADO POR EL ATACANTE"
|
||||
if i == 1 else "")
|
||||
self.flip_log(f" Bloque {i}: |{printable(b)}|{mark}")
|
||||
self.flip_log("")
|
||||
self.flip_log("El atacante escribio el bloque 1 a voluntad sin conocer la clave.")
|
||||
self.flip_log("CBC sin MAC no detecta nada: descifra y entrega el resultado.")
|
||||
|
||||
def flip_gcm(self):
|
||||
pt = self.flip_plaintext()
|
||||
nonce = get_random_bytes(12)
|
||||
ct, tag = AES.new(KEY, AES.MODE_GCM, nonce=nonce).encrypt_and_digest(pt)
|
||||
|
||||
target = self.flip_target.get().encode()
|
||||
if len(target) != BLOCK:
|
||||
target = b"rol=admin;;;;;;;"
|
||||
# En CTR/GCM el flip es aun mas directo: se altera el bloque objetivo.
|
||||
delta = xor_bytes(self.flip_blocks[1].encode(), target)
|
||||
forged = bytearray(ct)
|
||||
for j in range(BLOCK):
|
||||
forged[BLOCK + j] ^= delta[j]
|
||||
|
||||
self.flip_log("")
|
||||
self.flip_log("=== MISMO ATAQUE CONTRA AES-GCM ===")
|
||||
self.flip_log(f"Nonce: {nonce.hex().upper()}")
|
||||
self.flip_log(f"Tag : {tag.hex().upper()}")
|
||||
try:
|
||||
rec = AES.new(KEY, AES.MODE_GCM,
|
||||
nonce=nonce).decrypt_and_verify(bytes(forged), tag)
|
||||
self.flip_log(f"Descifrado: {printable(rec)}")
|
||||
except ValueError:
|
||||
self.flip_log("")
|
||||
self.flip_log(" MAC INVALIDO -> el mensaje se rechaza por completo.")
|
||||
self.flip_log(" GCM verifica la integridad ANTES de entregar texto claro.")
|
||||
self.flip_log("")
|
||||
self.flip_log("Conclusion: el cifrado autenticado (AEAD) no es un extra,")
|
||||
self.flip_log("es el requisito minimo para cualquier canal real.")
|
||||
|
||||
# =======================================================================
|
||||
# PESTANA 3: reuso de nonce en CTR
|
||||
# =======================================================================
|
||||
def build_tab_ctr(self):
|
||||
t = self.tab_ctr
|
||||
self.ctr_a = None
|
||||
self.ctr_b = None
|
||||
|
||||
tk.Label(t, font=("Arial", 10), justify="left", anchor="w",
|
||||
text=("CTR convierte AES en un cifrado de flujo: C = P xor keystream. "
|
||||
"Si dos mensajes usan el mismo nonce comparten keystream, y entonces\n"
|
||||
" C_A xor C_B = P_A xor P_B ... el keystream se cancela y el "
|
||||
"atacante ve la relacion entre ambos textos claros.")
|
||||
).pack(anchor="w", padx=20, pady=(12, 8))
|
||||
|
||||
panels = tk.Frame(t)
|
||||
panels.pack(pady=4)
|
||||
self.p_a = self.make_panel(panels, "Imagen A", SMALL_SIZE, with_hist=False)
|
||||
self.p_b = self.make_panel(panels, "Imagen B", SMALL_SIZE, with_hist=False)
|
||||
self.p_xor_same = self.make_panel(panels, "C_A xor C_B (MISMO nonce)",
|
||||
SMALL_SIZE, with_hist=False)
|
||||
self.p_xor_diff = self.make_panel(panels, "C_A xor C_B (nonces distintos)",
|
||||
SMALL_SIZE, with_hist=False)
|
||||
|
||||
ctrl = tk.Frame(t)
|
||||
ctrl.pack(pady=8)
|
||||
tk.Button(ctrl, text="Cargar Imagen A",
|
||||
command=lambda: self.ctr_load('a')).pack(side=tk.LEFT, padx=8)
|
||||
tk.Button(ctrl, text="Cargar Imagen B",
|
||||
command=lambda: self.ctr_load('b')).pack(side=tk.LEFT, padx=8)
|
||||
tk.Button(ctrl, text="Cifrar y comparar",
|
||||
command=self.ctr_run).pack(side=tk.LEFT, padx=8)
|
||||
|
||||
self.ctr_status = tk.Label(t, text="Carga dos imagenes distintas.",
|
||||
font=("Arial", 10, "bold"), fg="blue")
|
||||
self.ctr_status.pack(pady=4)
|
||||
|
||||
def ctr_load(self, which):
|
||||
path = filedialog.askopenfilename(
|
||||
initialdir=".",
|
||||
filetypes=[("Images", ("*.png", "*.jpg", "*.jpeg", "*.bmp")),
|
||||
("All Files", "*.*")])
|
||||
if not path:
|
||||
return
|
||||
try:
|
||||
img = (Image.open(path)
|
||||
.resize((SMALL_SIZE, SMALL_SIZE), Image.LANCZOS)
|
||||
.convert('RGB'))
|
||||
data = img.tobytes()
|
||||
if which == 'a':
|
||||
self.ctr_a = data
|
||||
self.show(self.p_a, img)
|
||||
self.p_a["stats"].config(text=format_metrics(data))
|
||||
else:
|
||||
self.ctr_b = data
|
||||
self.show(self.p_b, img)
|
||||
self.p_b["stats"].config(text=format_metrics(data))
|
||||
self.ctr_status.config(text="Imagen cargada.")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"No se pudo cargar la imagen: {e}")
|
||||
|
||||
def ctr_run(self):
|
||||
if self.ctr_a is None or self.ctr_b is None:
|
||||
messagebox.showwarning("Aviso", "Carga las dos imagenes primero.")
|
||||
return
|
||||
|
||||
nonce = get_random_bytes(8)
|
||||
ca = AES.new(KEY, AES.MODE_CTR, nonce=nonce).encrypt(self.ctr_a)
|
||||
cb = AES.new(KEY, AES.MODE_CTR, nonce=nonce).encrypt(self.ctr_b)
|
||||
xor_same = xor_bytes(ca, cb)
|
||||
|
||||
c2a = AES.new(KEY, AES.MODE_CTR,
|
||||
nonce=get_random_bytes(8)).encrypt(self.ctr_a)
|
||||
c2b = AES.new(KEY, AES.MODE_CTR,
|
||||
nonce=get_random_bytes(8)).encrypt(self.ctr_b)
|
||||
xor_diff = xor_bytes(c2a, c2b)
|
||||
|
||||
self.show_bytes(self.p_xor_same, xor_same, SMALL_SIZE)
|
||||
self.p_xor_same["stats"].config(text=format_metrics(xor_same))
|
||||
self.show_bytes(self.p_xor_diff, xor_diff, SMALL_SIZE)
|
||||
self.p_xor_diff["stats"].config(text=format_metrics(xor_diff))
|
||||
|
||||
self.ctr_status.config(
|
||||
text=f"Nonce reutilizado: {nonce.hex().upper()} -> el XOR de los "
|
||||
f"ciphertexts es exactamente P_A xor P_B (ambas siluetas visibles). "
|
||||
f"Con nonces distintos, solo ruido.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = AESLab(root)
|
||||
root.mainloop()
|
||||
Reference in New Issue
Block a user