El cifrado XOR

Gabriel Martí
9 min readJun 2, 2022

--

Entendiendo las operaciones con bits

Photo by Alexander Sinn on Unsplash

Introducción

Para poder entender el cifrado XOR veremos primero como funcionan algunas operaciones a nivel de bits. Se debe de entender esto como un pequeño inciso. Las operaciones de bits no se limitan exclusivamente a estas operaciones que se mencionan y se puede profundizar más.

Hay que tener en cuenta, también, que el cifrado XOR es un sistema muy simple y sencillo y que hay otros sistemas de cifrado más complejos, a la vez que seguros. Esto es solo una guía didáctica para entender un concepto y una serie de procesos usados en la programación en lenguaje C.

Operaciones lógicas: AND, OR y XOR

Veamos a continuación las operaciones lógicas que, probablemente, los que estén leyendo este artículo ya conozcan:

Partimos de que un bit tiene dos valores: 1 (cierto) y 0 (falso). Le asignamos valores cierto y falso para que en la lógica de la mente de una persona asimile de manera más natural el resultado. Puede darse el hecho, en algunos sistemas, que se invierta el resultado, es decir 1 falso, 0 cierto, aunque por norma se asume la premisa inicial.

Las operaciones lógicas que usaremos necesitan siempre dos operandos. En la tabla los denominaremos A y B. Con estos operandos se realiza una de las operaciones lógicas y se obtiene un resultado. Las operaciones lógicas son AND, OR y XOR.

La operación AND dará como resultado 1 (cierto), siempre que los dos operandos sean 1 (cierto), en cualquier otro caso el resultado será 0 (falso). La operación OR dará como resultado 1 (cierto), siempre que uno de los dos operandos sea cierto, y solo será 0 (falso) cuando los dos operandos sean 0 (falso). Y finalmente, la operación XOR solamente será 1 (cierto), cuando exclusivamente uno de los dos operandos sea 1 (cierto), pero no los dos. Es decir, si los dos son 1 (cierto) o los dos son 0 (falso) el resultado será siempre 0 (falso).

La siguiente tabla resume dichas operaciones:

Tabla de operaciones lógicas AND, OR y XOR

Cifrando un BIT

Antes de pasar al cifrado de todo un fichero, o incluso una cadena de texto, vamos a empezar por el nivel más bajo. El cifrado de un BIT, y para ello nos vamos a centrar en la operación XOR, conocida también como “OR exclusivo”.

¿Se puede cifrar un bit?

Es muy probable que al leer el título de este apartado nos hagamos esta pregunta. Podríamos pensar que es un poco atrevido hablar del cifrado de un bit, o que es una afirmación absurda. De hecho, el cifrado se verá mejor tratando los bits a gran escala, es decir, con un gran volumen de estos. Pero para entender esta operación prefiero hacer una visión microscópica y centrarme en 1 solo bit, para posteriormente, ampliar el campo de visión donde con una perspectiva mayor se verá el resultado del cifrado.

Para ello, imaginemos lo siguiente: tenemos unos bits de mensaje, que los identificaremos con la letra “m”, y a continuación tendremos otros bits que serán nuestra clave de cifrado e identificaremos con la letra “k”.

Si hacemos una operación XOR de m con k obtenemos el resultado de la columna C, el cual podríamos considerar el bit cifrado. Recordemos, k es la clave.

Al aplicar de nuevo una operación XOR de los datos cifrados (columna C) con la clave (columna k), obtenemos el resultado de la columna D, que es el mensaje descifrado.

Tabla que muestra el cifrado y descifrado de un BIT usando operaciones XOR

Cifrando un BYTE

Ahora que hemos visto la operación con un bit, observemos como se haría con un BYTE. Para el que no lo sepa, un BYTE es un conjunto de 8 bits. Y estos, tradicionalmente, se interpretan también como 1 carácter si nos atenemos a la codificación estándar ASCII.

Supongamos, pues, que el mensaje es la letra “A” mayúscula, cuyo código ASCII en hexadecimal es el 41 y en binario corresponde a la secuencia de 8 bits 01000001.

Queremos cifrar este mensaje, de una letra, con una clave, que también será una letra. En este caso, nuestra clave es la letra “t” minúscula, cuyo código ASCII en hexadecimal es el 74 y en binario corresponde a la secuencia de 8 bits 01110100. He elegido esta letra para que el resultado cifrado nos dé un valor que corresponda a un carácter imprimible. Podría ser cualquier otro y funcionaría exactamente de la misma manera.

Si aplicamos la operación XOR por cada uno de los bits, el resultado es la secuencia 00110101 que corresponde al carácter “5” según la tabla ASCII. Este será nuestro mensaje cifrado.

Resultado ejecución programa xorbyte.c

Si a este mensaje cifrado, le volvemos a aplicar la operación XOR usando la misma clave, podremos observar que obtenemos de nuevo el mensaje original.

Operaciones de cifrado y descifrado de un carácter (byte)

El programa de ejemplo para realizar esto es el siguiente:

Cifrado simétrico

El cifrado que hemos visto hasta ahora se considera simétrico por el hecho de que se utiliza la misma clave para cifrar que para descifrar. Por supuesto, no es el sistema de cifrado más seguro, todo lo contrario, pues se podría llegar a obtener la clave analizando diferentes mensajes cifrados.

Tampoco se le ocurriría a nadie cifrar un contenido importante empleando como clave un solo carácter (o byte), por lo que lo habitual es emplear una palabra, de la cual se va cogiendo cada uno de los caracteres de manera rotativa para ir cifrando los diferentes caracteres del mensaje.

El siguiente ejemplo muestra cómo se cifraría la cadena de texto “ESTAMOS FRENTE A UN GRAVE PROBLEMA” empleando la clave “barcelona”.

Ejemplo de cifrado XOR de una frase, usando como clave una palabra

La elección de los caracteres de la clave está hecha aposta en este ejemplo con tal de obtener una cadena cifrada con el máximo de caracteres imprimibles. Cabe recordar que al hacer una operación con bits podríamos obtener resultados con valores inferiores al código 20 hex que, en la tabla ASCII, corresponden en su mayoría a caracteres no imprimibles. O incluso se podría generar algún byte a 00 hex, con lo que el problema estaría en que sería interpretado como fin de cadena.

Cifrando la frase en lenguaje C

Visto el ejemplo anterior, la función para cifrar la cadena de texto es muy sencilla. Se trata de recorrer la frase carácter a carácter y cifrar cada uno de estos caracteres con el carácter correspondiente de la palabra clave. Al agotar los caracteres de la palabra clave se vuelve a empezar por el primero.

Para ello, la solución más práctica es usar el operador de módulo (%) que nos da el resto de una división entera, con lo que podemos obtener directamente la posición del carácter correspondiente de la clave.

Función de cifrado XOR de una cadena de texto usando otra cadena como clave

En la siguiente captura de pantalla podemos ver el resultado del programa aplicado a la frase anterior utilizando dicha clave. Para el caso de caracteres no imprimibles, la función mostrará simplemente un punto.

Resultado de la ejecución del programa de ejemplo cifrando una frase

El programa de ejemplo que muestra el resultado anterior es el que se muestra a continuación:

Cifrando un archivo

Llegados a este punto, poca cosa nueva voy a explicar sobre el cifrado. De hecho, nada. La operativa es exactamente la misma. Simplemente, voy a mostrar los detalles para cifrar el contenido de un archivo sobre otro de una manera muy simple y sin entrar profundas optimizaciones porque se trata solo de un ejemplo orientado al nivel más básico.

Los argumentos

El programa recibirá 3 argumentos: nombre de archivo a cifrar, clave de cifrado y nombre de archivo de salida.

Para leer los argumentos de la línea de comandos, en lenguaje C, tenemos dos variables que se reciben en la función main(). Tradicionalmente, se les llama argc y argv, pero pueden tener cualquier otro nombre.

Argc, es un entero que contiene el número de argumentos recibidos por línea de comandos, incluido el propio nombre del programa. Por lo tanto, argc tendrá el valor 1 si únicamente se llama el programa sin argumentos.

Argv es un array de punteros a caracteres. Si has programado en lenguaje C, sabrás que las cadenas de caracteres como variable no existen como tal. En realidad, dicha variable es un puntero (guarda una dirección de memoria) que apunta al primer carácter de la cadena, y sabe cuándo esta cadena finaliza al encontrar un carácter nulo (‘\0’), es decir, un 0.

Por lo tanto, el primer paso es comprobar que se han pasado el número de parámetros correcto y, si no es así, informar de ello mostrando, por ejemplo, una ayuda de uso del programa.

Ejemplo de comprobación de parámetros recibidos

Seguidamente, ya llamamos a la función que se encargará de abrir el archivo y cifrar su contenido sobre otro archivo.

Como estamos tratando archivos que pueden contener datos binarios (no nos centramos solo en texto), debemos abrir el archivo en modo de lectura binaria.

Ejemplo de apertura de archivo en modo lectura y binario

También debemos abrir el archivo destino, en modo escritura y binario. De hecho, es lo mismo que la apertura del archivo anterior y simplemente cambia las opciones “rb” por “wb”. En los dos casos si no se puede abrir el archivo muestra mensaje y sale de la función devolviendo 0.

Finalmente, únicamente queda hacer el bucle que ira leyendo byte a byte del archivo origen, lo cifra y lo escribe al archivo destino.

Parte del código que va cifrando el contenido del archivo

Cabe remarcar que este es únicamente un ejemplo didáctico y sencillo, pues no está nada optimizado al estar leyendo un carácter del archivo por cada operación de cifrado. Lo ideal sería leer el contenido del archivo por bloques, por ejemplo, de 512 bytes o 1024 bytes (o más en función de necesidades y disponibilidad de memoria), de manera que se minimize el número de lecturas a disco. Y posteriormente cifrar cada bloque por completo y grabarlo por completo en el archivo destino.

Una alternativa al cifrado de cada carácter rotativamente, y aprovechando la lectura por bloques, podría ser cifrar cada bloque con uno de los caracteres de la contraseña. Es decir, cada carácter (byte) cifra todo un bloque, y al cambiar de bloque se cambia de carácter.

El código fuente de ejemplo que cifra todo un archivo se muestra a continuación:

Uso de XOR en Malware

A pesar de su simplicidad, o debido a ello, el cifrado XOR es ampliamente usado en muchos malwares para ocultar (no tanto como cifrar) su código y evadir las medidas de detección de los antivirus y otras herramientas. Y digo ocultar porque la misma contraseña que se usa para descifrar el código está dentro del mismo código.

Muestra de ello es el ejemplo de la siguiente captura de pantalla que corresponde a un fragmento del malware Turla Keylogger el cual ocultaba los nombres de las funciones con un cifrado XOR con el valor 55 hex.

Muestra parcial de Turla Keylogger donde se descifra parte del código con operaciones XOR

A partir de ahora cuando oigas una noticia sobre un malware o un ransomware, no dejes de pensar en el cifrado XOR.

Para facilitar la descarga del código he creado un repositorio en GitHub.

https://github.com/gabimarti/cifradoXOR

--

--

Gabriel Martí

Ex-Docente CFGM, CFGS Ciberseguridad. Actualmente Consultor en Ciberseguridad. Intereses en robótica, ciberseguridad, reversing. Twitter @gmarti @310hkc41b