Esteganografía de imágenes en Python: Implementación práctica del método LSB

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.

Etiquetas: Python Esteganografía Pillow ciberseguridad procesamiento de imágenes

Publicado el 8-26 03:12