De canales, huellas, máscaras, secretos y grafos

Parte III – Esteganografía de andar por casa.

Quizás no hayas oído nunca esa palabra: Esteganografía.

Según la RAE:

Vamos a ocultar en la imagen la información de una marca de agua, pero no será apreciable a la vista, sin embargo, podrá extraerse para verificar la autenticidad o autor de la fotografía.

Parece complicado, pero verás que no.

Este será el proceso que vamos a hacer:

  • Generaremos un archivo con la información que queremos incluir oculta en la imagen.
  • Firmaremos ese archivo digitalmente con un certificado como el del DNIe o FNMT que al ser emitidos por entidades de confianza, garantizarán la autenticidad de la identidad del firmante.
  • Incluiremos los datos y su firma en un código QR.
  • Y ese QR de un modo que explicaré luego, irá en la imagen sin ser visto.
  • Obviamente, te diré como verificarlo por si utilizan tu imagen.

Vamos a utilizar dos métodos distintos para ocultar nuestra firma (una fácil pero débil y otro fuerte, pero complicado). Para ambos el proceso de generación del QR es idéntico, así que dividiré el proceso.

Generación de la firma (firmada, valga la redundancia).

Lo primero es definir qué queremos incluir en los datos.

Por ejemplo, algo como esto:

AUTOR: @rafasith, PROYECTO: Venetian Dream, DESCRIPCION: Los puentes de Venecia

Generamos un fichero de texto con esa información: mensaje.txt.

El siguiente paso es firmar digitalmente ese texto.

Necesitamos en primer lugar un certificado digital en el formato adecuado.

Todo hijo de vecino con DNI tiene un certificado en el chip. Si dispones de un lector de DNI en casa puedes generarte un certificado de la FNMT que es más sencillo de usar. Sigue las instrucciones que se dan aquí para generarlo

Si todo te funciona correctamente (Reconozco que a veces se sufre en ese proceso) podrás descargarte un archivo con tu certificado de la FNMT en formato P12.

Ahora vas a tener que descargar una utilidad para tratar los certificados, firmar, validar, etc. Openssl es la utilidad de código libre que nos permite trabajar con cualquier tema criptográfico. En Linux viene instalada por defecto, pero en windows puedes usar, por ejemplo, esta distribución.

Una vez instalada utilizaremos su interfaz de comandos para realizar los pasos que nos permitirán incluir la firma en el QR.

El primer paso es extraer la clave privada en formato PEM, Clave_Privada.pem del certificado de la FNMT que nos hemos descargado en formato P12, Mi-Certificado.pfx.

openssl pkcs12 -in Mi-Certificado.pfx -clcerts -nokeys -out Clave_Privada.pem

Obviamente, tendrás que introducir la contraseña que protege el archivo.

Extraeremos también el certificado público X.509 (clave pública firmada por el emisor) para que pueda verificarse por cualquier persona que la firma es correcta.

openssl pkcs12 -in Mi-Certificado.pfx -clcerts -nokeys -out Certificado_Publico.pem

Ya podemos realizar la firma de los datos que hemos incluido en el archivo firma.txt:

openssl dgst -sha256 -sign Clave_Privada.pem -out firma.bin mensaje.txt

El archivo firma.bin contiene la firma de los datos del archivo mensaje.txt. Esa información está en código binario. Antes de pasarla al código QR tenemos que pasarla a un formato Base64 (texto) que pueda interpretarse:

openssl base64 -in firma.bin -out firma.b64

Ya tenemos el mensaje para la máscara y su firma. Unimos la información de esos ficheros en uno solo (Contenido_QR.txt). Añadimos unas etiquetas para visualizar mejor el contenido y nos quedan los siguientes datos:

TEXTO:
AUTOR: @rafasith, PROYECTO: Venetian Dream, DESCRIPCION: Los puentes de Venecia
FIRMA:
G4sgxQLAuReE+hDaA3cbHYzpB4meEB15EG6dJYc6tBp5Q1BGFt9PhzKic+oWW3Q1
L1KignarPL4MwgMTMTeDngcFiBXxCE09crcyYTDHB7dPiyctR1VkyIA7hxIzMdkA
zSo6NMW8HVMTlCacQKpgWk4rw6PKE1iYh5AflldT1C+tDGRD88Yv/E53SnSBCmnn
9bIVasLJ6V0ZmAMsNgtlor7Kn7eSj4Tr1cjlQ1fXT1yb8RWUWOy1+GnqaM9GOj/F
Qa3cyT7DybY+6yR1jfjIEKNLCROg7ygtkOyTIwUrcIjygNA80l/dhgF96b31mmeG
7azFt9HzRBQHWv9XVTDr7w==

Tenemos la información ya preparada para generar un QR.

Hay infinidad de utilidades para generar un código QR.

Los utilizas a diario pero seguramente no sabes en qué consisten. Seguramente solo los has utilizado para leer la carta de un restaurante o para que te dirija a una página web.

Tiene muchas más utilidades porque esa nube de puntos codifica información de modo que es resistente a errores de lectura y de degradación de la imagen,

Nosotros vamos a emplearlo para codificar nuestros datos con su firma.

Emplearé una utilidad de Linux llamada qrencode que si no la tienes la puedes instalar, porque de nuevo, aquí se usan software libre.

qrencode -o QR.png -s 6 -l H < Contenido_QR.txt

Hemos generado el QR con la máxima capacidad de corrección de errores (-l H).

El resultado es el siguiente:

Puedes utilizar tu teléfono móvil para leer este QR y verás que tiene la información del fichero Contenido_QR.txt,

Ya tenemos la información que vamos a esconder en la imagen.

El lado oscuro de la imagen.

Como sabeis soy un gran fan de la saga de Star Wars.

Utilizaré una frase del Maestro Yoda [<(-_-)>] para describir el primer procedimiento de ocultación de información en una imagen.

Luke: Is the dark side stronger?
Yoda: No, no, no… Quicker, easier, more seductive.

Empire Strikes back (Dagobah’s training)

Te preguntarás a qué viene esta cita.

Fácil.

Vamos a esconder la información del QR generado en el apartado anterior en el lado oscuro de la fotografía. Y, como bien dice Yoda, no es el método más fuerte, sino el más rápido y el más fácil. Lo de que sea más seductor no sabría valorarlo…

Antes de empezar hay que entender cómo se almacena una imagen en una fotografía digital (a nivel básico).

Una fotografía no es más que una matriz de puntos de dos dimensiones [ancho, alto] en la que en cada punto (pixel) se asocia un vector de 3 dimensiones [R (rojo), G (verde), B (azul)]. A cada color se asocia un valor de 8 bits. Con 8 bits obtenemos un rango de 28 = 256 tonos en cada canal primario de color.

256 tonos de rojo, 256 de verde y 256 de azúl. Que combinados nos dan 2563 = 16.777.216 colores.

Son muchos colores. Desde luego, yo no soy capaz de distinguirlos.

¿Qué ocurre si quitamos algunos? Que probablemente no nos demos cuenta. ¿Y qué ocurre si quitamos colores? Que dejaremos espacio libre (liberaremos bits) en el fichero para meter nuestra información.

Resumo: Vamos a quitar colores en la fotografía (vamos a borrar bits) y en ese espacio libre incrustaremos la información que queremos ocultar.

Pero… ¿Qué colores quitamos?

La respuesta debería ser obvia: los que menos vayamos a echar en falta.

Nuestro ojo, aun siendo una maravilla evolutiva, no es perfecto.

“Ve” mejor unos colores que otros. Y desde luego, ve mejor los colores más brillantes que los más oscuros.

Todos los gatos son pardos por la noche, se dice, para resaltar que cuando hay bajas condiciones de iluminación distinguimos peor los detalles.

Ahí está la respuesta. Quitemos colores oscuros.

Ya hemos visto antes que cada color se codifica con 8 bits (256 valores). Por ejemplo, un pixel de una fotografía almacenaría estos valores:

Ese color no es otro que un rosa

Si yo borro en el canal rojo el bit que está más a la derecha (el bit menos significativo):

Obtengo otro rosa

Ni siquiera Conchi Lillo puede distinguirlo del anterior (quizás mi mujer y mis hijas, sí; pues tienen un ojo que detectan la más mínima variación o combinación errónea en mi vestimenta).

La información que “escondamos” en el bit menos significativo de cada canal de color en un fichero RGB pasará desapercibido al ver la fotografía. Recalco lo del bit LSB (Less Significant Bit), es decir, el que determina los tonos más oscuros, es decir, el lado oscuro de la fotografía (DISCLAIMER: Esto no es del todo cierto, pero me vais a permitir la licencia de referencia a la saga de Star Wars).

¿Cuánta información nos cabe?

Podemos echar la cuenta.

Imagina una imagen de Full HD (1920*1080 píxeles). Si solo usamos 1 bit de 1 canal de color eso son 1920 * 1080 = 2.054.160 bits = 256.770 bytes ~ 250 Kb.

El QR que hemos generado ocupa 4K

Nos sobra espacio por todos los lados…

Así que vamos a ello. Vamos a insertar el QR en una imagen.

Encubriendo la huella.

Dado que vamos a esconder la información en el lado oscuro, utilizaré como contenedor una fotografía que me hicieron en el Training Day de la Legion 501 que se celebró en Madrid el pasado octubre.

Yo mismo en el Training Day Madrid 2024

Podríamos utilizar software existente, pero aquí se viene a aprender, así que prepara tu bloc de notas. Si utilizas vi es que vas a por nota.

Para hacerlo lo más sencillo y didáctico vamos a pasar esa fotografía a formato ppm con cualquier programa editor de imágenes (Yo recomiendo GIMP, que es Opensource).

PPM es un formato gráfico muy, muy sencillo.

El formato PPM tiene una variedad que almacena en texto plano toda la información de la imagen

La Variedad de texto plano consta de:

  • Primera línea que contiene el valor P3.
  • Segunda línea con las dimensiones de la imagen (píxeles).
  • Tercera línea que define el mayor valor que puede tener un componente de color (generalmente 255).
  • Valores sucesivos que representan los componentes de color de cada canal (R, G, B).

Con el objeto de simplificar este ejemplo, he escalado la fotografía contenedora al mismo tamaño que la del QR. Ambas tienen un tamaño de 702 de alto por 702 de ancho. De este modo el código queda mínimo.

#!/usr/bin/perl
use strict;
use warnings;
# ARCHIVOS
my $entrada = 'entrada.ppm';
my $qrfile  = 'qr.ppm';
my $salida  = 'salida.ppm';
open my $in,  '<', $entrada or die "No se puede abrir $entrada\n";
open my $qr,  '<', $qrfile  or die "No se puede abrir $qrfile\n";
open my $out, '>', $salida  or die "No se puede crear $salida\n";
# LEE CABECERAS
# ESTA VERSION SOLO SIRVE PARA ARCHIVO CONTENEDOR E IMAGEN OCULTA (QR) DE IGUAL TAMAÑO
my @header;
for (1..3) {
    my $line = <$in>;
    <$qr>;
    push @header, $line;
    print $out $line;
}
# LEE TODOS LOS DATOS RGB COMO LISTA DE NUMEROS
my @datos_img = grep { /\d/ } map { split } <$in>;
my @datos_qr  = grep { /\d/ } map { split } <$qr>;
# OCULTA ARCHIVO QR
# BUCLE DE 3 EN 3 PORQUE NO NECESITAMOS LEER LOS TRES CANALES (r,g;b) DEL QR
for (my $i = 0; $i + 2 < @datos_img; $i += 3) {
    # VALORES (r,g,b) DE CADA PIXEL DEL ARCHIVO CONTENEDOR
    my $r = $datos_img[$i];
    my $g = $datos_img[$i+1];
    my $b = $datos_img[$i+2];
    # VALOR DEL CANAL ROJO DEL ARCHIVO A OCULTAR
    # RECUERDA QUE EL QR SOLO TIENE DOS COLORES
    # BLANCO Y NEGRO
    # Y CON LEER UN CANAL (RED) NOS BASTA
    my $qr_r = $datos_qr[$i];
    # BORRA EL BIT MENOS SIGNIFICATIVO DEL CANAL ROJO DEL PIXEL
    $r &= 0b11111110;
    # SI EL PIXEL CORRESPONDIENTE DEL QR ES BLANCO
    # VUELVE A ACTIVAR EL BIT MENOS SIGNIFICATIVO
    # DEL CANAL ROJO DE LA IMAGEN CONTENEDORA
    $r |= 0b00000001 if $qr_r == 255;
    # ESCRIBE EL PIXEL EN EL ARCHIVO DE SALIDA
    print $out "$r $g $b\n";
}
close $in;
close $qr;
close $out;
print "Listo. Imagen guardada en $salida\n";

Creo que con los comentarios en el código es bastante autoexplicativo.

Si ejecutas ese código obtienes este archivo de salida

Como puedes ver es prácticamente idéntico al de entrada.

Si no te fías que en esa imagen va el QR puedes extraerlo con este script (previamente tendrás que convertir la imagen anterior que está en formato PNG a PPM que es el formato ASCII con el que trabajamos de modo tan simple).

#!/usr/bin/perl
use strict;
use warnings;
# ARCHIVOS
my $input_file  = 'salida.ppm';         # Imagen con firma oculta
my $output_file = 'qr-extraido.ppm';    # Imagen con el QR oculto
open(my $in, '<', $input_file) or die "No puedo abrir $input_file: $!";
open(my $out, '>', $output_file) or die "No puedo abrir $output_file: $!";
# LEE CABECERA
my $magic = <$in>;
print $out $magic;
my @header;
while (@header < 3) {
    my $line = <$in>;
    next if $line =~ /^\s*#/;
    push @header, split(/\s+/, $line);
}
print $out join(" ", @header) . "\n";
# LEE TODOS LOS DATOS RGB COMO LISTA DE NUMEROS
my @pixels_in;
while (my $line = <$in>) {
    next if $line =~ /^\s*#/;
    push @pixels_in, split(/\s+/, $line);
}
close($in);
# GENERA UNA IMAGEN B/N CON EL BIT MENOS SIGNIFICATIVO DEL CANAL ROJO
my @pixels_out;
for (my $i = 0; $i < @pixels_in; $i += 3) {
    my $r = $pixels_in[$i];
    my $lsb = $r & 0b00000001;
    if ($lsb) {
        push @pixels_out, (255, 255, 255);
    } else {
        push @pixels_out, (0, 0, 0);
    }
}
# ESCRIBE SALIDA
my $count = 0;
for my $value (@pixels_out) {
    print $out "$value ";
    $count++;
    print $out "\n" if $count % 12 == 0;
}
close($out);
print "Imagen extraída correctamente y guardada en $output_file\n";

La salida es, por supuesto, el archivo QR original (con nuestros datos y la firma electrónica).

Ahora es cuando ya te digo que este procedimiento no es útil hoy en día.

Pero era el único que conocía yo en mayo de 2006. Y como prueba te dejo esta imagen que realicé como reto para mis compañeros de trabajo.

No vieron ni la pista que había dejado visible en el fresco de Leonardo.

He de decir que el reto no fue resuelto hasta varios años después por un compañero de mi siguiente puesto de trabajo en la Gerencia de Seguridad de Sistemas.

La última cena de la Gerencia.
Lleva mensaje oculto mediante la técnica LSB

Páginas: 1 2 3 4 5 6



Deja un comentario

About Me

Físico por formación, astrónomo por devoción, ingeniero por alimentación, poeta por necesidad.

Diletante eterno, efímero polímata

Entradas recientes

Agendas Astronomía Astronáutica Calendarios Eclipses Einstein Fotografía Física Historia LHC Moonrises Programación Viajes