Esteganografía con ficheros de audio: Enviar documentos secretos en tus canciones preferidas #Esteganografía
Las técnicas de esteganografía nos permiten ocultar elementos (mensaje, objetos, etc…) dentro de
otros elementos de modo que el primero sea imperceptible a primera vista. Por ejemplo, se puede
ocultar información dentro de una imagen utilizando los bits menos significativos para componer el
mensaje oculto. Pues bien, en la siguiente prueba de concepto, vamos a realizar algo parecido
ocultando archivos “dentro del sonido” de un fichero WAV.
Para entender mejor esta prueba, vamos a explicar un poco algunos conceptos básicos necesarios. Todos nosotros cuando éramos pequeños, seguro que hemos cogido plantillas de dibujo en las cuales habían unos puntos numerados, los cuales teníamos que seguir con el lápiz para formar la silueta del dibujo. Pues bien, con el sonido digital ocurre algo parecido. Cuando estamos grabando una sesión de sonido, nosotros elegimos el samplerate (muestras de valores de sonido tomadas en un segundo) y la resolución de dicha muestra (cantidad de bits con los que se miden dichos valores). De esta manera, cuando el reproductor, procese el audio grabado, lo hará como si escribiera una figura de puntos numerados, creando así las ondas sonoras que se reproducirán en nuestros altavoces.
Vale, pero entonces, ¿cómo podemos ocultar la información “dentro del sonido”? Pues la verdad, es que es bastante sencillo de realizar. Teniendo en cuenta, que la resolución de cada muestra puede ser de por ejemplo 16 bits, vamos a coger los 8 bits menos significativos (valores comprendidos entre 0-255, lo cual hace que la modificación de onda de sonido sea imperceptible) de la muestra para almacenar cada byte del fichero que queremos ocultar, además, éstos se van a intercalar entre el canal derecho e izquierdo (stereo) y en un intervalo de X muestras (para evitar posible deterioro en el audio).
Además, para poder tener una estructura la cual podamos desenglosar y recuperar, añadiremos las siguientes funcionalidades:
Para poder visualizar mejor lo que se pretende hacer, he creado un script en Python que utiliza la librería wave y gnupg para este fin, del cual explicaremos los bloques más importantes (recalcar que el script está diseñado para ser funcional a sabiendas de los errores que puede causar por no estar diseñado para un uso constante). El script y demás material lo podéis descargar del siguiente repositorio de github: Estewav y en este vídeo tenéis un ejemplo de su funcionamiento:
Analizando el script para ocultar documentos en ficheros wav
Sólo vamos a repasar los apartados clave del mismo y para seguirlo es muy recomendable tener el script a mano. Vamos a ver el codificador - encargado de codificar y cifrar el archivo que queremos ocultar en el archivo wav - y luego el decodificador podéis ver en el script como es exactamente la función inversa.
codificador.py
jumpsData = 10
#Espacio de muestras del framerate para la toma de datos válidos, al hacer esto, hacemos que los cambios en el audio se noten menos aun si se puede...
numFraSize = 4
# numero de bytes con el que se compondrá el número decimal final que indica la cantidad de bytes que ocupa el fichero oculto
cipher = CIPHER_ON
# establecemos si se va a cifrar o no
header = ["S", "Y", "L", "M"]
# 1 byte por cada caracter (contra menos caracteres, más posibilidades de error por accidente), pasaremos el valor de cada string a su valor ascii para poder manipularlo como bytes.
…
# Obtenemos los datos del fichero de audio original para que sean iguales en el audio wav destino:
channels = fileWav.getnchannels() # canales de audio (1 mono, 2 stereo)
sampleWidth = fileWav.getsampwidth() # número de bytes para las muestras del samplerate
frameRate = fileWav.getframerate() # obtenemos el número de muestras (frames) por segundo que realiza
…
# Si el cifrado está activo, lo ciframos (nos guardamos también el fichero cifrado original)
if cipher == CIPHER_ON:
gpg_home = "gnupg"
gpg = gnupg.GPG(gnupghome=gpg_home)
encrypted_ascii_data = gpg.encrypt_file(fileToHide, None, passphrase=passphrase_cipher, symmetric=True, output=pathFileToHideCipher)
fileToHide.close()
fileToHideEncripted = open(pathFileToHideCipher, 'rb')
bytesFileToHide = bytearray(fileToHideEncripted.read()) # guardamos todos los bytes del archivo en un array
fileToHideEncripted.close()
else:
bytesFileToHide = bytearray(fileToHide.read()) # guardamos todos los bytes del archivo en un array
fileToHide.close()
…
startIn = 0 # índice del byte del sample de audio donde se empieza a contar
# A tener en cuenta:
#
# bytem = byte menos significativo
# byteM = byte más significativo
#
# si se un sistema estereo el muestreo comienza así -> bytem_ch1 - byteM_ch1 - bytem_ch2 - byteM_ch2 .....
# si se un sistema mono el muestreo comienza así -> bytem_ch1 - byteM_ch1 - bytem_ch1 - byteM_ch1 .....
#
# ----------------
…
# Este será el espaciado (saltos) con el que intercalemos cada uno de los bytes de toda la trama (cabecera, etc...)
jump = (sampleWidth * jumpsData) # salto para grabar en los bytes correctos
…
# Al tratarse de bytes no podemos poner un número entero muy alto en cada valor, para ello separamos el número total de bytes que se tienen que guardar en 4 octetos (32bits), mediante desplazamiento binario:
# grabamos el tamaño bytes que ocupa el fichero oculto en los bytes asignados para ello
# utilizamos desplazamiento de bits para guardarlo en bloques de un octeto (byte) y luego poder recomponerlo
desplazamiento = 0
for i in range(numFraSize):
#print bytes(len(bytesFileToHide)>>24 & 0xFF) , bytes(len(bytesFileToHide)>>16 & 0xFF) , bytes(len(bytesFileToHide)>>8 & 0xFF) , bytes(len(bytesFileToHide)>>0 & 0xFF)
bytesfileWav[indexSampleWav] = len(bytesFileToHide)>>desplazamiento & 0xFF
desplazamiento += 8
indexSampleWav += jump
Tal y como están diseñados los scripts, podéis clonaros el repositorio y ejecutarlo cambiando los valores de los ficheros de origen y destino o simplemente ejecutarlo y ver los resultados. Al ejecutar el script codificador.py veremos lo siguiente en la salida:
Nos proporciona datos sobre el audio de origen como el número de canales, el ancho de muestra en bytes y el número total de frames que tiene el ficheros. Además, nos calcula en base a esos datos y en base a los saltos que tenemos configurados, cuantos bytes podemos ocultar dentro de él. A parte, nos analiza si el tamaño del fichero que queremos ocultar es inferior a la capacidad máxima y de ser así nos lo oculta, tiendo en cuenta, que si hemos activado el cifrado GPG nos lo ocultará cifrado con el passphrase que hayamos configurado en el script.
A continuación escucháis el fichero .wav original (por defecto audioBase3.wav) y a continuación escucharéis el fichero estego.wav el cual lleva oculto el fichero (por defecto oculta el archivo calico.jpg cifrado). Es muy probable que no notéis ninguna diferencia entre los dos archivos de sonido. Para hacernos una idea de la viabilidad de esta técnica de esteganografía, vamos a realizar la visualización gráfica del sonido, y para ello nos ayudaremos de la herramienta audacity. A continuación mostraré una imagen de dos tomas de tiempo de un audio musical (audioBase3.wav) y el mismo aplicándole la esteganografía (estego.wav).
Como podéis ver, ni si quiera aumentando hasta ver los puntos de muestreo podemos detectar una diferencia visual apreciable (menos aún nuestro oído). Para acercarnos más y poder ver algo con más claridad, en las siguientes dos imágenes pondré un captura de audio con una frecuencia constante de 1Hz y otra imagen de audio nulo (plano), para que podamos ver que aunque la diferencia de valores es minúscula, estos, claramente existen.
Aquí ya podemos ver pequeños cambios en el alisado de la onda, pero tenemos que tener en cuenta que la onda de arriba es una onda perfecta generada por código y en la grabación de audio real no es tan perfecto.
En este último ejemplo, al ser un audio totalmente nulo, podemos ver sin problemas la dimensión que tiene un byte de datos dentro del audio, el cual es insignificante, ya que hay que tener en cuenta que todo el audio es nulo excepto el dato oculto. Y esto es todo. Espero que os haya gustado y hasta la próxima.
Saludos!
Autor: Christian Prieto Bustamante
Figura 1: Esteganografía con ficheros de audito |
Para entender mejor esta prueba, vamos a explicar un poco algunos conceptos básicos necesarios. Todos nosotros cuando éramos pequeños, seguro que hemos cogido plantillas de dibujo en las cuales habían unos puntos numerados, los cuales teníamos que seguir con el lápiz para formar la silueta del dibujo. Pues bien, con el sonido digital ocurre algo parecido. Cuando estamos grabando una sesión de sonido, nosotros elegimos el samplerate (muestras de valores de sonido tomadas en un segundo) y la resolución de dicha muestra (cantidad de bits con los que se miden dichos valores). De esta manera, cuando el reproductor, procese el audio grabado, lo hará como si escribiera una figura de puntos numerados, creando así las ondas sonoras que se reproducirán en nuestros altavoces.
Figura 2: Los puntos indican en qué momento del eje X (tiempo) se ha tomado una muestra de sonido, y en eje Y se dibuja la resolución captada en ese momento (negativo o positivo) |
Vale, pero entonces, ¿cómo podemos ocultar la información “dentro del sonido”? Pues la verdad, es que es bastante sencillo de realizar. Teniendo en cuenta, que la resolución de cada muestra puede ser de por ejemplo 16 bits, vamos a coger los 8 bits menos significativos (valores comprendidos entre 0-255, lo cual hace que la modificación de onda de sonido sea imperceptible) de la muestra para almacenar cada byte del fichero que queremos ocultar, además, éstos se van a intercalar entre el canal derecho e izquierdo (stereo) y en un intervalo de X muestras (para evitar posible deterioro en el audio).
Figura 3: Valores de los bits de audio en 16bits |
Figura 4: Valores máximos por profundidad de bits: |
Además, para poder tener una estructura la cual podamos desenglosar y recuperar, añadiremos las siguientes funcionalidades:
- Generaremos una cabecera dentro del audio modificado para poder identificar que ahí se está escondiendo “algo” (con un tamaño que determinaremos nosotros).
- Un byte para indicar si el fichero está cifrado con GPG (Añadir una capa de cifrado al documento oculto nos dará más garantía)
- Una trama de 4 bytes para identificar el tamaño del fichero a extraer (número de 32bits, además, nunca llegará a ser tar alto por las limitaciones de un fichero wav, pero 24 bits podrían quedarse cortos).
- Y finalmente la trama de bytes del fichero oculto.
Figura 5: Estructura de la trama de datos ocultos |
Para poder visualizar mejor lo que se pretende hacer, he creado un script en Python que utiliza la librería wave y gnupg para este fin, del cual explicaremos los bloques más importantes (recalcar que el script está diseñado para ser funcional a sabiendas de los errores que puede causar por no estar diseñado para un uso constante). El script y demás material lo podéis descargar del siguiente repositorio de github: Estewav y en este vídeo tenéis un ejemplo de su funcionamiento:
Figura 6: Funcionamiento de Estewav
Sólo vamos a repasar los apartados clave del mismo y para seguirlo es muy recomendable tener el script a mano. Vamos a ver el codificador - encargado de codificar y cifrar el archivo que queremos ocultar en el archivo wav - y luego el decodificador podéis ver en el script como es exactamente la función inversa.
codificador.py
jumpsData = 10
#Espacio de muestras del framerate para la toma de datos válidos, al hacer esto, hacemos que los cambios en el audio se noten menos aun si se puede...
numFraSize = 4
# numero de bytes con el que se compondrá el número decimal final que indica la cantidad de bytes que ocupa el fichero oculto
cipher = CIPHER_ON
# establecemos si se va a cifrar o no
header = ["S", "Y", "L", "M"]
# 1 byte por cada caracter (contra menos caracteres, más posibilidades de error por accidente), pasaremos el valor de cada string a su valor ascii para poder manipularlo como bytes.
…
# Obtenemos los datos del fichero de audio original para que sean iguales en el audio wav destino:
channels = fileWav.getnchannels() # canales de audio (1 mono, 2 stereo)
sampleWidth = fileWav.getsampwidth() # número de bytes para las muestras del samplerate
frameRate = fileWav.getframerate() # obtenemos el número de muestras (frames) por segundo que realiza
…
# Si el cifrado está activo, lo ciframos (nos guardamos también el fichero cifrado original)
if cipher == CIPHER_ON:
gpg_home = "gnupg"
gpg = gnupg.GPG(gnupghome=gpg_home)
encrypted_ascii_data = gpg.encrypt_file(fileToHide, None, passphrase=passphrase_cipher, symmetric=True, output=pathFileToHideCipher)
fileToHide.close()
fileToHideEncripted = open(pathFileToHideCipher, 'rb')
bytesFileToHide = bytearray(fileToHideEncripted.read()) # guardamos todos los bytes del archivo en un array
fileToHideEncripted.close()
else:
bytesFileToHide = bytearray(fileToHide.read()) # guardamos todos los bytes del archivo en un array
fileToHide.close()
…
startIn = 0 # índice del byte del sample de audio donde se empieza a contar
# A tener en cuenta:
#
# bytem = byte menos significativo
# byteM = byte más significativo
#
# si se un sistema estereo el muestreo comienza así -> bytem_ch1 - byteM_ch1 - bytem_ch2 - byteM_ch2 .....
# si se un sistema mono el muestreo comienza así -> bytem_ch1 - byteM_ch1 - bytem_ch1 - byteM_ch1 .....
#
# ----------------
…
# Este será el espaciado (saltos) con el que intercalemos cada uno de los bytes de toda la trama (cabecera, etc...)
jump = (sampleWidth * jumpsData) # salto para grabar en los bytes correctos
…
# Al tratarse de bytes no podemos poner un número entero muy alto en cada valor, para ello separamos el número total de bytes que se tienen que guardar en 4 octetos (32bits), mediante desplazamiento binario:
# grabamos el tamaño bytes que ocupa el fichero oculto en los bytes asignados para ello
# utilizamos desplazamiento de bits para guardarlo en bloques de un octeto (byte) y luego poder recomponerlo
desplazamiento = 0
for i in range(numFraSize):
#print bytes(len(bytesFileToHide)>>24 & 0xFF) , bytes(len(bytesFileToHide)>>16 & 0xFF) , bytes(len(bytesFileToHide)>>8 & 0xFF) , bytes(len(bytesFileToHide)>>0 & 0xFF)
bytesfileWav[indexSampleWav] = len(bytesFileToHide)>>desplazamiento & 0xFF
desplazamiento += 8
indexSampleWav += jump
Tal y como están diseñados los scripts, podéis clonaros el repositorio y ejecutarlo cambiando los valores de los ficheros de origen y destino o simplemente ejecutarlo y ver los resultados. Al ejecutar el script codificador.py veremos lo siguiente en la salida:
Figura 7: Salida del codificador |
Nos proporciona datos sobre el audio de origen como el número de canales, el ancho de muestra en bytes y el número total de frames que tiene el ficheros. Además, nos calcula en base a esos datos y en base a los saltos que tenemos configurados, cuantos bytes podemos ocultar dentro de él. A parte, nos analiza si el tamaño del fichero que queremos ocultar es inferior a la capacidad máxima y de ser así nos lo oculta, tiendo en cuenta, que si hemos activado el cifrado GPG nos lo ocultará cifrado con el passphrase que hayamos configurado en el script.
A continuación escucháis el fichero .wav original (por defecto audioBase3.wav) y a continuación escucharéis el fichero estego.wav el cual lleva oculto el fichero (por defecto oculta el archivo calico.jpg cifrado). Es muy probable que no notéis ninguna diferencia entre los dos archivos de sonido. Para hacernos una idea de la viabilidad de esta técnica de esteganografía, vamos a realizar la visualización gráfica del sonido, y para ello nos ayudaremos de la herramienta audacity. A continuación mostraré una imagen de dos tomas de tiempo de un audio musical (audioBase3.wav) y el mismo aplicándole la esteganografía (estego.wav).
Figura 8: Audio de fichero original y con esteganografía |
Como podéis ver, ni si quiera aumentando hasta ver los puntos de muestreo podemos detectar una diferencia visual apreciable (menos aún nuestro oído). Para acercarnos más y poder ver algo con más claridad, en las siguientes dos imágenes pondré un captura de audio con una frecuencia constante de 1Hz y otra imagen de audio nulo (plano), para que podamos ver que aunque la diferencia de valores es minúscula, estos, claramente existen.
Figura 9: Imagen de toma con 1Hz con un ciclo completo. |
Aquí ya podemos ver pequeños cambios en el alisado de la onda, pero tenemos que tener en cuenta que la onda de arriba es una onda perfecta generada por código y en la grabación de audio real no es tan perfecto.
Figura 10: Imagen de toma nula (silencio completo) |
En este último ejemplo, al ser un audio totalmente nulo, podemos ver sin problemas la dimensión que tiene un byte de datos dentro del audio, el cual es insignificante, ya que hay que tener en cuenta que todo el audio es nulo excepto el dato oculto. Y esto es todo. Espero que os haya gustado y hasta la próxima.
Saludos!
Autor: Christian Prieto Bustamante
1 comentario:
¿Qué tal funciona esta técnica cuando en lugar de usar un WAV como almacenamiento se intenta usar un fichero con compresión con pérdidas como mp3 o m4a? ¿Altera la compresión la onda de tal modo que se pierde el contenido?
Publicar un comentario