Fundamentos de la Esteganografía LSB
La esteganografía es la técnica de ocultar información sensible dentro de un portador digital de manera que su existencia sea imperceptible. El método LSB (Least Significant Bit o Bit Menos Significativo) es uno de los enfoqeus más utilizados en el procesamiento de imágenes digitales debido a su simplicidad y efectividad.
En un modelo de color RGB, cada píxel se compone de tres canales: rojo, verde y azul. Cada canal suele representarse con 8 bits (un byte), permitiendo valores de 0 a 255. El bit situado más a la derecha es el que manos peso tiene en el valor total del color. Por ejemplo, si tenemos un valor de canal 150 (10010110 en binario) y cambiamos su último bit a 1, el valor resultante es 151 (10010111). Esta alteración de ±1 es virtualmente invisible para el ojo humano, lo que permite utilizar este bit para almacenar datos externos.
La capacidad teórica de almacenamiento de una imagen se calcula mediante la siguiente relación:
Capacidad_bytes = (Ancho * Alto * Canales) / 8
Preparación del Entorno y Herramientas
Para manipular imágenes a nivel de bits en Python, la librería estándar de facto es Pillow (PIL). También utilizaremos NumPy para mejorar la eficiencia en el manejo de grandes volúmenes de datos de píxeles.
pip install Pillow numpy
Es crucial trabajar con formatos de imagen sin pérdida de datos, como PNG o BMP. Formatos como JPEG utilizan algoritmos de compresión que alteran los valores de los píxeles, destruyendo la información oculta en los bits menos significativos.
Transformación de Datos a Flujo Binario
Antes de insertar la información en la imagen, el mensaje debe convertirse en una secuencia de bits. Es recomendable incluir un marcador de finalización (null terminator) para que el proceso de extracción sepa cuándo detenerse.
def texto_a_bits(mensaje, codificacion='utf-8'):
"""Convierte un string en una cadena de bits."""
datos_binarios = []
# Añadimos un carácter nulo como delimitador
for caracter in mensaje + '\0':
byte = bin(ord(caracter)).replace('0b', '').zfill(8)
datos_binarios.append(byte)
return ''.join(datos_binarios)
def bits_a_texto(cadena_bits):
"""Convierte una cadena de bits de vuelta a texto."""
caracteres = []
for i in range(0, len(cadena_bits), 8):
byte = cadena_bits[i:i+8]
if len(byte) < 8:
break
punto_codigo = int(byte, 2)
if punto_codigo == 0: # Fin de mensaje
break
caracteres.append(chr(punto_codigo))
return "".join(caracteres)
Algoritmo de Inserción de Datos
El proceso de codificación recorre los píxeles de la imagen y modifica el LSB de cada canal necesario hasta agotar los bits del mensaje secreto.
from PIL import Image
def ocultar_datos(ruta_origen, mensaje, ruta_salida):
img = Image.open(ruta_origen).convert('RGB')
pixeles = img.load()
bits_mensaje = texto_a_bits(mensaje)
indice_bit = 0
total_bits = len(bits_mensaje)
ancho, alto = img.size
for y in range(alto):
for x in range(ancho):
if indice_bit < total_bits:
r, g, b = pixeles[x, y]
# Modificación de canales individuales
canales = list((r, g, b))
for i in range(3):
if indice_bit < total_bits:
# Limpiar el último bit y aplicar el bit del mensaje
canales[i] = (canales[i] & ~1) | int(bits_mensaje[indice_bit])
indice_bit += 1
pixeles[x, y] = tuple(canales)
else:
img.save(ruta_salida)
return True
img.save(ruta_salida)
return True
Algoritmo de Extracción
La extracción consiste en leer el bit menos significativo de cada canal de color de manera secuencial hasta reconstruir el mensaje original basándose en el delimitador nulo.
def extraer_datos(ruta_imagen):
img = Image.open(ruta_imagen)
pixeles = img.load()
bits_acumulados = ""
ancho, alto = img.size
for y in range(alto):
for x in range(ancho):
r, g, b = pixeles[x, y]
for canal in (r, g, b):
# Extraer el LSB mediante una operación AND
bits_acumulados += str(canal & 1)
# Verificar cada 8 bits si hemos llegado al carácter nulo
if len(bits_acumulados) % 8 == 0:
ultimo_byte = bits_acumulados[-8:]
if ultimo_byte == '00000000':
return bits_a_texto(bits_acumulados)
return bits_a_texto(bits_acumulados)
Consideraciones Técnicas y Seguridad
Aunque el método LSB es eficaz para ocultar la presencia de datos, no proporciona seguridad por sí mismo. Si un analista sospecha de la imagen, puede extraer los bits fácilmente. Para una implementación robusta en entornos de producción, se recomienda:
- Cifrado previo: Encriptar el mensaje (por ejemplo, con AES) antes de conevrtirlo a binario.
- Distribución pseudoaleatoria: En lugar de llenar los píxeles de forma secuencial, utilizar una semilla (seed) para saltar entre píxeles, haciendo que el patrón de modificación sea más difícil de detectar mediante estegoanálisis estadístico.
- Control de integridad: Incluir un hash (como SHA-256) para verificar que el mensaje no ha sido alterado.