Eliminar metadatos de fotos en Ubuntu

Si hablábamos el otro día de metadatos en OpenOffice/LibreOffice hoy vamos a ver cómo borrar los metadatos de las fotos, en este caso sólo del formato jpg. Muchas veces subimos fotografías desde el móvil, lo cual lleva un montón de metadatos inscrustados, los llamados EXIF.

Y es que si una cámara ya va proporcionar la fecha y la hora en la que fue tomado y el modelo (y en caso, por ejemplo, de mi cárama Olympus un montón de datos técnicos, como el modo de flash, el perfil de color…) un móvil puede traer hasta las coordenadas geográficas del lugar donde se ha tomado la foto.

Ya sea por respeto a nuestra intimidad, paranoia o simplemente que hayamos puesto una excusa y esa foto nos la pueda joder («-Cariño estaba trabajando» «-Pues según esta foto a esa hora estabas en el bar») en Ubuntu/Mint/Debian tenemos una herramienta que permite que nuestros jpg se queden limpitos de ellos: jhead.

Se trata de una herramienta de línea de comandos, por lo que lo primero es instalarlo:

sudo apt-get install jhead

Luego la orden para eliminar los datos tampoco tiene mucha ciencia:

jhead -purejpg /ruta/imagen.jpg

Basta con pasarle la ruta de la imagen que queremos dejar sin metadatos y punto. Pero lo verdaderamente útil, como en todo el software de consola, viene cuando queremos trabajar con muchos archivos:

jhead -purejpg /ruta/*.jpg

Podemos usar comodines para afectar a todos los archivos de una carpeta concreta, ahorrándonos el trabajo de ir uno a uno.

Como ya dije arriba, este software sólo trabaja con jpg y sus derivaciones: jpeg, JPEG y JPG. En cualquier caso es el formato más habitual en cámaras y móviles, pero de todas formas en el futuro hablaremos de más opciones para trabajar con imágenes.

Borrar metadatos personales de OpenOffice y LibreOffice

Son famosas las cagadas con los metadatos de políticos, valga este mismo como ejemplo y Chema Alonso tiene muchos más en su blog, cuando intentan falsear o filtrar una información «comprometedora». Para ciertas cosas es útil estar gobernado por inútiles, no lo niego (es que si además de desalmados llegan a ser listos íbamos a estar bien jodidos).

Existen muchas herramientas para borrar los metadatos de OpenOffice y Libre Office, pero si no buscamos una seguridad paranoica, sino simplemente evitar que vayan datos junto al archivo como nombre, dirección… hay formas sencillas de hacerlo.

Si estás elaborando un documento nuevo, lo mejor es guardarlo ya sin los metadatos. Es fácil configurar el OpenOffice para que lo haga:

  • Te vas al menú Herramientas y allí seleccionas Opciones.
  • En opciones, en el árbol de la izquierda eliges Seguridad. En la derecha verás un apartado Opciones de Seguridad y alertas. Allí pulsas el botón Opciones.
  • Dentro del menú que se te abrirá marcas Elimina la información personal al guardar.

libre office

Con esto ya logras que el archivo se guarde sin tus datos personales. ¿Y con los archivos ya guardados?. Bueno, los archivos nativos de OpenOffice y LibreOffice realmente son un archivo comprimido (en tar.gz creo) que podéis abrir con cualquier compresor/empaquetador (7zip, FileRoller, WinZip…) y donde podéis ver varios xml que forman el documento. Los metadatos están guardados en meta.xml así que, si borráis ese archivo (o lo modificáis para eliminar lo que no queréis que salga) está arreglado, muerto el perro se acabó la rabia. También tenéis otros xml por ahí como el settings, donde se guardan datos sobre impresoras, servidores… que pueden ser sensibles. Incluso se podría hacer un script que borre estos metadatos, si buscáis un poco seguro que hay más de uno colgado (y si no, pedidlo y me curro uno en algún artículo).

En fin, otra ayudita para vuestro anonimato y que no os pase como a muchos politicastros. Si queréis algo más profundo y currado, a Chema Alonso me remito again.

Collusion: Comprueba a dónde envían tus datos las webs que visitas

Con el asunto de PRISM, el espionaje de la NSA, los diversos proyectos europeos (en España tenemos SITEL espiándonos desde 2009, por cierto que es curioso como ciertos medios atacaron a Rubalcaba por implantarlo pero no han comentado nada de que el gobierno de Rajoy lo siga utilizando), las filtraciones de Snowden… en fin, todo esto ha puesto la seguirdad y la paranoia en primera línea de información.

Ahora la gente empieza a preguntarse «¿Qué hacen las grandes compañías con mis datos?», empiezan a ser conscientes de que los millones de Facebook y Google tienen que salir e algún sitio y de que «si no pagas tú eres el producto». Por suerte muchos ya llevaban tiempo pensando esto, y más en el campo del desarrollo y de la seguridad web.

Collusion es un término que hace referencia a cuando dos personas u organizaciones se asocian para perjudicar a una tercera. También es un proyecto de Mozilla que vio la luz en 2012 y que nos permite saber, mediante un complemento del navegador, a quién envían la información las páginas que visitamos. Veremos burbujas de tres colores: azul para los sitios que visitamos, rojas para los anunciantes y blancas para webs que reciben alguna información. Luego el software generará un gráfico tipo telaraña uniéndolas para monstrarnos las relaciones. En algunos casos son cosas esperables (Google Analytics u otros servicios de estadísticas, Gravatar…) y en otros en cambio nos encontramos cosas sorprendentes (y desagradables).

gráfico collusion
Gráfico de collusion generado en sólo un día de uso.

Collusion es una buena herramienta para conocer quién conoce qué sobre nosostros, que podemos integrar como complemento en nuestro navegador Mozilla. Eso sí, sólo nos vale como herramienta de análisis, no nos protegerá del seguimiento. Tranquilos, hay formas de hacerlo, y mañana os hablaré de un complemento que os ayudará con eso.

Encriptación AES en MySQL (y MaríaDB)

El uso de la encriptación AES en MySQL (la más segura de la que dispone el famoso SGBD) es sencillo, y se realiza mediante dos funciones: AES_ENCRYPT y AES_DECRYPT. Permite usar AES_128 o AES_256 dependiendo de la fuente que usemos.

Para insertar o modificar los datos de una columna basta con hacer uso de AES_ENCRYPT, que recibe dos parámetros: el dato a insertar (string) y la clave (string también). A diferencia de SQL-Server, en este caso el dato a encriptar siempre es una cadena. Si quieres conocer de antemano la longitud de la cadena resultado (para ajustar el tamaño del campo en la tabla) lo puedes hacer recurriendo a la fórmula 16 * (trunc(logintud_de_la_cadena / 16) + 1). Si recibe un argumento null el resultado será null también.

Para el caso de AES_DECRYPT los parámetros serán la cadena encriptada a descifrar y la contraseña. Si hay algún error en la encriptación (sea por una modificación maliciosa o por datos de entrada incorrectos) esta función devolverá NULL, aunque es posible que en algún caso pueda dar un valor no nulo erróneo si la cadena encriptada o la contraseña son erróneas.

Una inserción de un registro encriptado se ralizaría de la siguiente forma:

INSERT INTO tb_Ejemplo VALUES(AES_ENCRYPT('valor a encriptar como cadena', 'contraseña'));

En este caso si realizamos una select normal nos devolvería los datos encriptados, para poder verlos tendríamos que hacer

SELECT AES_DECRYPT(COLUMNA_ENCRIPTADA, 'contraseña') FROM tb_Ejemplo;

Finalmente, por seguridad podemos guardar la contraseña en una variable del servidor para no enviarla en cada comunicación con la base de datos, tal que así:

SELECT @pss:='password';
INSERT INTO tb_Ejemplo VALUES (AES_ENCRYPT('texto',@pss));

Sobre cifrado tanto MySQL como MariaDB ofrecen varias opciones aparte de AES, que en este caso es la comentada por ser la más segura y robusta. Pero también existe la posibilidad de usar DES (DES_ENCRYPT y DES_DECRYPT), las funciones DECODE y ENCODE (que no se qué algoritmo usan, la verdad, pero que nos obligan a almacenar los datos en una columna tipo BLOB) y para hashes las funciones MD5, SHA1 y ENCRYPT (que llama a la función crypt() de UNIX, por lo que no puede usarse en Windows). También existe la función PASSWORD, pero es la que usa el sistema de autenticación de MySQL, por lo que se recomienda no usarla en aplicaciones propias.

Consejos de seguridad para usar Facebook de forma responsable.

Si en su momento ya hice una entrada de consejos de seguridad generales en la red para proteger la privacidad de tus cuentas, ahora toca uno específico para Facebook.

  • No añadas a lo loco a gente que no conoces, hay muchas cuentas que pretenden hacerse pasar por una persona que quiere amistad o sexo para luego estafarnos o extorsionarnos.
  • Si tienes que aceptar a alguien por «compromiso» puedes dejar a esa persona con acceso limitado para que solo vea lo que pones como público. Y recuerda que el que compartas algo solo para tus contactos no garantiza al 100% que esto no pueda acabar siendo visto por alguien que no querías que lo viera, así que si dudas mejor no compartas.
  • Usa siempre una contraseña fuerte. Lo mejor es que uses un gestor de contraseñas. Si no quieres usarlo entonces recuerda: nunca menos de 11 dígitos y con mayúsculas, minísculas, números y símbolos. Aunque no hayas detectado ningún ataque, cámbiala al menos cada cuatro meses. Y si sospechas que se ha visto comprometida, no pierdas ni un minuto en hacerlo. No la compartas con otras personas y no utilices para FB la misma que para el correo electrónico que tengas asociado.
  • Si usas un equipo en una ubicación pública (cibercafé, centro social, instituto, universidad, biblioteca), o en el ordenador de otra persona, cierra siempre la sesión. No basta con cerrar el navegador, la sesión persiste, y aunque confiéis en la buena fe de la otra persona, ello no implica que la seguridad de su equipo no esté expuesta. Por eso, antes de cerrar el navegador, id a Salir de vuestra cuenta (en el menú de vuestra cuenta).
  • Ojito con el phishing. Comprobad en la barra de direcciones que la dirección es correcta, y que no sea http://www.facbook.com o http://www.bacefook.com o similar. Hay páginas con direcciones que suenan parecidas a la de Facebook creadas expresamente para robar tus datos, fíjate bien en la dirección.
  • Cuidado con los permisos que te piden la aplicaciones. Todo el caso de Cambridge Analítica vino de ahí, de aplicaciones que accedian a datos de terceros por culpa de usuarios que no tenían cuidado a la hora de unirse a estos juegos o tests.
  • Usa la navegación segura. Cuando la utilizas toda la transmisión de información se cifra, haciendo más complejos ciertos ataques. Para activarla debes hacerlo desde el apartado de Seguridad de la Configuración de Cuenta, donde basta marcar la casilla de Usar Facebook a través de una conexión segura siempre que sea posible y luego guardar los cambios. Si está activada verás como en la dirección el procolo cambia de http a https.
  • Si monitorizas la actividad de tu cuenta puedes saber si se ha accedido desde un dispositivo que no es el tuyo. En la sección de Seguridad de tu cuenta podéis ir a Notificaciones de inicio de sesión para que, si se accede desde un dispositivo que no haya sido usado antes para entrar a tu FB, te lo notifique al correo electrónico. También puedes comprobar ahí qué sesiones están activas, y saber si alguien más está conectado a tu cuenta.
  • Y otro punto que repito del anterior: Cuidado con la geolocalización, se pueden cruzar tus datos con los de otras redes y aplicaciones para saber dónde vives, por dónde te mueves…

Y recuerda que Facebook genera millones pero para el usuario es gratis. Piensa ¿De dónde sale la pasta? La minería de datos es uno de los grandes negocios de esta década, así que supón que lo más probable es que comercien con ellos, y ya los tenga alguna empresa china o estadounidense.

Usando SHA-1 en C y C++

Estaba trasteando en C estos días como ejercicio y se me ha ocurrido utilizar una función de resumen (hash) SHA-1. Como el tema es profundamente complejo, lo que he hecho ha sido buscar una ya creada, dado que al tratarde de un algoritmo creado en 1995 existen múltiples. Voy a compartir el que he encontrado por ahí que funciona bien:

/* sha1.c : Implementation of the Secure Hash Algorithm */

/* SHA: NIST's Secure Hash Algorithm */

/*	This version written November 2000 by David Ireland of 
	DI Management Services Pty Limited <code@di-mgt.com.au>

	Adapted from code in the Python Cryptography Toolkit, 
	version 1.0.0 by A.M. Kuchling 1995.
*/

/* AM Kuchling's posting:- 
   Based on SHA code originally posted to sci.crypt by Peter Gutmann
   in message <30ajo5$oe8@ccu2.auckland.ac.nz>.
   Modified to test for endianness on creation of SHA objects by AMK.
   Also, the original specification of SHA was found to have a weakness
   by NSA/NIST.  This code implements the fixed version of SHA.
*/

/* Here's the first paragraph of Peter Gutmann's posting:
   
The following is my SHA (FIPS 180) code updated to allow use of the "fixed"
SHA, thanks to Jim Gillogly and an anonymous contributor for the information on
what's changed in the new version.  The fix is a simple change which involves
adding a single rotate in the initial expansion function.  It is unknown
whether this is an optimal solution to the problem which was discovered in the
SHA or whether it's simply a bandaid which fixes the problem with a minimum of
effort (for example the reengineering of a great many Capstone chips).
*/

/* h files included here to make this just one file ... */

/* global.h */

#ifndef _GLOBAL_H_
#define _GLOBAL_H_ 1

/* POINTER defines a generic pointer type */
typedef unsigned char *POINTER;

/* UINT4 defines a four byte word */
typedef unsigned long int UINT4;

/* BYTE defines a unsigned character */
typedef unsigned char BYTE;

#ifndef TRUE
  #define FALSE	0
  #define TRUE	( !FALSE )
#endif /* TRUE */

#endif /* end _GLOBAL_H_ */

/* sha.h */

#ifndef _SHA_H_
#define _SHA_H_ 1

/* #include "global.h" */

/* The structure for storing SHS info */

typedef struct 
{
	UINT4 digest[ 5 ];            /* Message digest */
	UINT4 countLo, countHi;       /* 64-bit bit count */
	UINT4 data[ 16 ];             /* SHS data buffer */
	int Endianness;
} SHA_CTX;

/* Message digest functions */

void SHAInit(SHA_CTX *);
void SHAUpdate(SHA_CTX *, BYTE *buffer, int count);
void SHAFinal(BYTE *output, SHA_CTX *);

#endif /* end _SHA_H_ */

/* endian.h */

#ifndef _ENDIAN_H_
#define _ENDIAN_H_ 1

void endianTest(int *endianness);

#endif /* end _ENDIAN_H_ */

/* sha.c */

#include <stdio.h>
#include <string.h>

static void SHAtoByte(BYTE *output, UINT4 *input, unsigned int len);

/* The SHS block size and message digest sizes, in bytes */

#define SHS_DATASIZE    64
#define SHS_DIGESTSIZE  20


/* The SHS f()-functions.  The f1 and f3 functions can be optimized to
   save one boolean operation each - thanks to Rich Schroeppel,
   rcs@cs.arizona.edu for discovering this */

/*#define f1(x,y,z) ( ( x & y ) | ( ~x & z ) )          // Rounds  0-19 */
#define f1(x,y,z)   ( z ^ ( x & ( y ^ z ) ) )           /* Rounds  0-19 */
#define f2(x,y,z)   ( x ^ y ^ z )                       /* Rounds 20-39 */
/*#define f3(x,y,z) ( ( x & y ) | ( x & z ) | ( y & z ) )   // Rounds 40-59 */
#define f3(x,y,z)   ( ( x & y ) | ( z & ( x | y ) ) )   /* Rounds 40-59 */
#define f4(x,y,z)   ( x ^ y ^ z )                       /* Rounds 60-79 */

/* The SHS Mysterious Constants */

#define K1  0x5A827999L                                 /* Rounds  0-19 */
#define K2  0x6ED9EBA1L                                 /* Rounds 20-39 */
#define K3  0x8F1BBCDCL                                 /* Rounds 40-59 */
#define K4  0xCA62C1D6L                                 /* Rounds 60-79 */

/* SHS initial values */

#define h0init  0x67452301L
#define h1init  0xEFCDAB89L
#define h2init  0x98BADCFEL
#define h3init  0x10325476L
#define h4init  0xC3D2E1F0L

/* Note that it may be necessary to add parentheses to these macros if they
   are to be called with expressions as arguments */
/* 32-bit rotate left - kludged with shifts */

#define ROTL(n,X)  ( ( ( X ) << n ) | ( ( X ) >> ( 32 - n ) ) )

/* The initial expanding function.  The hash function is defined over an
   80-UINT2 expanded input array W, where the first 16 are copies of the input
   data, and the remaining 64 are defined by

        W[ i ] = W[ i - 16 ] ^ W[ i - 14 ] ^ W[ i - 8 ] ^ W[ i - 3 ]

   This implementation generates these values on the fly in a circular
   buffer - thanks to Colin Plumb, colin@nyx10.cs.du.edu for this
   optimization.

   The updated SHS changes the expanding function by adding a rotate of 1
   bit.  Thanks to Jim Gillogly, jim@rand.org, and an anonymous contributor
   for this information */

#define expand(W,i) ( W[ i & 15 ] = ROTL( 1, ( W[ i & 15 ] ^ W[ (i - 14) & 15 ] ^ \
                                                 W[ (i - 8) & 15 ] ^ W[ (i - 3) & 15 ] ) ) )


/* The prototype SHS sub-round.  The fundamental sub-round is:

        a' = e + ROTL( 5, a ) + f( b, c, d ) + k + data;
        b' = a;
        c' = ROTL( 30, b );
        d' = c;
        e' = d;

   but this is implemented by unrolling the loop 5 times and renaming the
   variables ( e, a, b, c, d ) = ( a', b', c', d', e' ) each iteration.
   This code is then replicated 20 times for each of the 4 functions, using
   the next 20 values from the W[] array each time */

#define subRound(a, b, c, d, e, f, k, data) \
    ( e += ROTL( 5, a ) + f( b, c, d ) + k + data, b = ROTL( 30, b ) )

/* Initialize the SHS values */

void SHAInit(SHA_CTX *shsInfo)
{
    endianTest(&shsInfo->Endianness);
    /* Set the h-vars to their initial values */
    shsInfo->digest[ 0 ] = h0init;
    shsInfo->digest[ 1 ] = h1init;
    shsInfo->digest[ 2 ] = h2init;
    shsInfo->digest[ 3 ] = h3init;
    shsInfo->digest[ 4 ] = h4init;

    /* Initialise bit count */
    shsInfo->countLo = shsInfo->countHi = 0;
}


/* Perform the SHS transformation.  Note that this code, like MD5, seems to
   break some optimizing compilers due to the complexity of the expressions
   and the size of the basic block.  It may be necessary to split it into
   sections, e.g. based on the four subrounds

   Note that this corrupts the shsInfo->data area */

static void SHSTransform( digest, data )
     UINT4 *digest, *data ;
    {
    UINT4 A, B, C, D, E;     /* Local vars */
    UINT4 eData[ 16 ];       /* Expanded data */

    /* Set up first buffer and local data buffer */
    A = digest[ 0 ];
    B = digest[ 1 ];
    C = digest[ 2 ];
    D = digest[ 3 ];
    E = digest[ 4 ];
    memcpy( (POINTER)eData, (POINTER)data, SHS_DATASIZE );

    /* Heavy mangling, in 4 sub-rounds of 20 interations each. */
    subRound( A, B, C, D, E, f1, K1, eData[  0 ] );
    subRound( E, A, B, C, D, f1, K1, eData[  1 ] );
    subRound( D, E, A, B, C, f1, K1, eData[  2 ] );
    subRound( C, D, E, A, B, f1, K1, eData[  3 ] );
    subRound( B, C, D, E, A, f1, K1, eData[  4 ] );
    subRound( A, B, C, D, E, f1, K1, eData[  5 ] );
    subRound( E, A, B, C, D, f1, K1, eData[  6 ] );
    subRound( D, E, A, B, C, f1, K1, eData[  7 ] );
    subRound( C, D, E, A, B, f1, K1, eData[  8 ] );
    subRound( B, C, D, E, A, f1, K1, eData[  9 ] );
    subRound( A, B, C, D, E, f1, K1, eData[ 10 ] );
    subRound( E, A, B, C, D, f1, K1, eData[ 11 ] );
    subRound( D, E, A, B, C, f1, K1, eData[ 12 ] );
    subRound( C, D, E, A, B, f1, K1, eData[ 13 ] );
    subRound( B, C, D, E, A, f1, K1, eData[ 14 ] );
    subRound( A, B, C, D, E, f1, K1, eData[ 15 ] );
    subRound( E, A, B, C, D, f1, K1, expand( eData, 16 ) );
    subRound( D, E, A, B, C, f1, K1, expand( eData, 17 ) );
    subRound( C, D, E, A, B, f1, K1, expand( eData, 18 ) );
    subRound( B, C, D, E, A, f1, K1, expand( eData, 19 ) );

    subRound( A, B, C, D, E, f2, K2, expand( eData, 20 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 21 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 22 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 23 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 24 ) );
    subRound( A, B, C, D, E, f2, K2, expand( eData, 25 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 26 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 27 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 28 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 29 ) );
    subRound( A, B, C, D, E, f2, K2, expand( eData, 30 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 31 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 32 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 33 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 34 ) );
    subRound( A, B, C, D, E, f2, K2, expand( eData, 35 ) );
    subRound( E, A, B, C, D, f2, K2, expand( eData, 36 ) );
    subRound( D, E, A, B, C, f2, K2, expand( eData, 37 ) );
    subRound( C, D, E, A, B, f2, K2, expand( eData, 38 ) );
    subRound( B, C, D, E, A, f2, K2, expand( eData, 39 ) );

    subRound( A, B, C, D, E, f3, K3, expand( eData, 40 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 41 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 42 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 43 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 44 ) );
    subRound( A, B, C, D, E, f3, K3, expand( eData, 45 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 46 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 47 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 48 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 49 ) );
    subRound( A, B, C, D, E, f3, K3, expand( eData, 50 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 51 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 52 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 53 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 54 ) );
    subRound( A, B, C, D, E, f3, K3, expand( eData, 55 ) );
    subRound( E, A, B, C, D, f3, K3, expand( eData, 56 ) );
    subRound( D, E, A, B, C, f3, K3, expand( eData, 57 ) );
    subRound( C, D, E, A, B, f3, K3, expand( eData, 58 ) );
    subRound( B, C, D, E, A, f3, K3, expand( eData, 59 ) );

    subRound( A, B, C, D, E, f4, K4, expand( eData, 60 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 61 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 62 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 63 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 64 ) );
    subRound( A, B, C, D, E, f4, K4, expand( eData, 65 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 66 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 67 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 68 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 69 ) );
    subRound( A, B, C, D, E, f4, K4, expand( eData, 70 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 71 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 72 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 73 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 74 ) );
    subRound( A, B, C, D, E, f4, K4, expand( eData, 75 ) );
    subRound( E, A, B, C, D, f4, K4, expand( eData, 76 ) );
    subRound( D, E, A, B, C, f4, K4, expand( eData, 77 ) );
    subRound( C, D, E, A, B, f4, K4, expand( eData, 78 ) );
    subRound( B, C, D, E, A, f4, K4, expand( eData, 79 ) );

    /* Build message digest */
    digest[ 0 ] += A;
    digest[ 1 ] += B;
    digest[ 2 ] += C;
    digest[ 3 ] += D;
    digest[ 4 ] += E;
    }

/* When run on a little-endian CPU we need to perform byte reversal on an
   array of long words. */

static void longReverse(UINT4 *buffer, int byteCount, int Endianness )
{
    UINT4 value;

    if (Endianness==TRUE) return;
    byteCount /= sizeof( UINT4 );
    while( byteCount-- )
        {
        value = *buffer;
        value = ( ( value & 0xFF00FF00L ) >> 8  ) | \
                ( ( value & 0x00FF00FFL ) << 8 );
        *buffer++ = ( value << 16 ) | ( value >> 16 );
        }
}

/* Update SHS for a block of data */

void SHAUpdate(SHA_CTX *shsInfo, BYTE *buffer, int count)
{
    UINT4 tmp;
    int dataCount;

    /* Update bitcount */
    tmp = shsInfo->countLo;
    if ( ( shsInfo->countLo = tmp + ( ( UINT4 ) count << 3 ) ) < tmp )
        shsInfo->countHi++;             /* Carry from low to high */
    shsInfo->countHi += count >> 29;

    /* Get count of bytes already in data */
    dataCount = ( int ) ( tmp >> 3 ) & 0x3F;

    /* Handle any leading odd-sized chunks */
    if( dataCount )
        {
        BYTE *p = ( BYTE * ) shsInfo->data + dataCount;

        dataCount = SHS_DATASIZE - dataCount;
        if( count < dataCount )
            {
            memcpy( p, buffer, count );
            return;
            }
        memcpy( p, buffer, dataCount );
        longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness);
        SHSTransform( shsInfo->digest, shsInfo->data );
        buffer += dataCount;
        count -= dataCount;
        }

    /* Process data in SHS_DATASIZE chunks */
    while( count >= SHS_DATASIZE )
        {
        memcpy( (POINTER)shsInfo->data, (POINTER)buffer, SHS_DATASIZE );
        longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness );
        SHSTransform( shsInfo->digest, shsInfo->data );
        buffer += SHS_DATASIZE;
        count -= SHS_DATASIZE;
        }

    /* Handle any remaining bytes of data. */
    memcpy( (POINTER)shsInfo->data, (POINTER)buffer, count );
    }

/* Final wrapup - pad to SHS_DATASIZE-byte boundary with the bit pattern
   1 0* (64-bit count of bits processed, MSB-first) */

void SHAFinal(BYTE *output, SHA_CTX *shsInfo)
{
    int count;
    BYTE *dataPtr;

    /* Compute number of bytes mod 64 */
    count = ( int ) shsInfo->countLo;
    count = ( count >> 3 ) & 0x3F;

    /* Set the first char of padding to 0x80.  This is safe since there is
       always at least one byte free */
    dataPtr = ( BYTE * ) shsInfo->data + count;
    *dataPtr++ = 0x80;

    /* Bytes of padding needed to make 64 bytes */
    count = SHS_DATASIZE - 1 - count;

    /* Pad out to 56 mod 64 */
    if( count < 8 )
        {
        /* Two lots of padding:  Pad the first block to 64 bytes */
        memset( dataPtr, 0, count );
        longReverse( shsInfo->data, SHS_DATASIZE, shsInfo->Endianness );
        SHSTransform( shsInfo->digest, shsInfo->data );

        /* Now fill the next block with 56 bytes */
        memset( (POINTER)shsInfo->data, 0, SHS_DATASIZE - 8 );
        }
    else
        /* Pad block to 56 bytes */
        memset( dataPtr, 0, count - 8 );

    /* Append length in bits and transform */
    shsInfo->data[ 14 ] = shsInfo->countHi;
    shsInfo->data[ 15 ] = shsInfo->countLo;

    longReverse( shsInfo->data, SHS_DATASIZE - 8, shsInfo->Endianness );
    SHSTransform( shsInfo->digest, shsInfo->data );

	/* Output to an array of bytes */
	SHAtoByte(output, shsInfo->digest, SHS_DIGESTSIZE);

	/* Zeroise sensitive stuff */
	memset((POINTER)shsInfo, 0, sizeof(shsInfo));
}

static void SHAtoByte(BYTE *output, UINT4 *input, unsigned int len)
{	/* Output SHA digest in byte array */
	unsigned int i, j;

	for(i = 0, j = 0; j < len; i++, j += 4) 
	{
        output[j+3] = (BYTE)( input[i]        & 0xff);
        output[j+2] = (BYTE)((input[i] >> 8 ) & 0xff);
        output[j+1] = (BYTE)((input[i] >> 16) & 0xff);
        output[j  ] = (BYTE)((input[i] >> 24) & 0xff);
	}
}


unsigned char digest[20];
unsigned char message[3] = {'a', 'b', 'c' };
unsigned char *mess56 = 
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";

/* Correct solutions from FIPS PUB 180-1 */
char *dig1 = "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D";
char *dig2 = "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1";
char *dig3 = "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F";

/* Output should look like:-
 a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d
 A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D <= correct
 84983e44 1c3bd26e baae4aa1 f95129e5 e54670f1
 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 <= correct
 34aa973c d4c4daa4 f61eeb2b dbad2731 6534016f
 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F <= correct
*/

main()
{
	SHA_CTX sha;
	int i;
	BYTE big[1000];

	SHAInit(&sha);
	SHAUpdate(&sha, message, 3);
	SHAFinal(digest, &sha);

	for (i = 0; i < 20; i++)
	{
		if ((i % 4) == 0) printf(" ");
		printf("%02x", digest[i]);
	}
	printf("\n");
	printf(" %s <= correct\n", dig1);

	SHAInit(&sha);
	SHAUpdate(&sha, mess56, 56);
	SHAFinal(digest, &sha);

	for (i = 0; i < 20; i++)
	{
		if ((i % 4) == 0) printf(" ");
		printf("%02x", digest[i]);
	}
	printf("\n");
	printf(" %s <= correct\n", dig2);

	/* Fill up big array */
	for (i = 0; i < 1000; i++)
		big[i] = 'a';

	SHAInit(&sha);
	/* Digest 1 million x 'a' */
	for (i = 0; i < 1000; i++)
		SHAUpdate(&sha, big, 1000);
	SHAFinal(digest, &sha);

	for (i = 0; i < 20; i++)
	{
		if ((i % 4) == 0) printf(" ");
		printf("%02x", digest[i]);
	}
	printf("\n");
	printf(" %s <= correct\n", dig3);

	return 0;
}

/* endian.c */

void endianTest(int *endian_ness)
{
	if((*(unsigned short *) ("#S") >> 8) == '#')
	{
		/* printf("Big endian = no change\n"); */
		*endian_ness = !(0);
	}
	else
	{
		/* printf("Little endian = swap\n"); */
		*endian_ness = 0;
	}
}

He dejado todos los comentarios, aunque se os pueda hacer coñazo, por respeto a los creadores originales. La función original la podéis consultar aquí.

Para C++ por otra parte, podéis disfrutar de la librería libre Crypto++ con múltiples algoritmos, que podéis encontrar en este enlace.

Algoritmos criptográficos más frecuentes

Un algoritmo criptográfico no es más (bueno, lo digo como si esto fuera poco… xD) que una función matemática usada en los procesos de encriptación y desencriptación. El algoritmo se sirve de una clave para encriptar y desencriptar datos.

Tal vez lo principal sea empezar por la diferencia entre sistemas de clave simétrica y asimétrica.  En el caso de la criptografía simétrica la explicación es simple: se usa la misma clave para encriptar y desencriptar, es decir, tanto el emisor como el receptor han de conocer la misma clave. La cuestión de la criptografía asimétrica es más compleja, utilizando dos claves: pública y privada, la privada sólo la tiene el receptor, y la pública todos los emisores. El emisor encripta el mensaje usando la clave pública, pero este sólo puede ser desencriptado con la clave privada (que sólo tiene el receptor y que nadie más ha de saber para que la seguridad sea eficiente).

Ahora, simplemente, incluyo una lista con los más utilizados.

  • DES:  Consistente en una serie de permutaciones y sustituciones, utiliza una clave simétrica de 64 bits, de los cuales 56 se usan para la encriptación y 8 para la paridad. Fue diseñado por IBM y declarado standar en 1977 por la NBS, y en esos tiempos el algoritmo estuvo rodeado de gran polémica: se consideraba que la longitud de clave era insuficiente y existían fuertes rumores de que la NSA había impuesto que este tuviera una puerta trasera. En 1992 se publicó el primer ataque teórico para romperlo, en 1998 una máquina diseñada específicamente para romperlo lo logró en 56 horas, y en 1999 un ordenador normal fue capaz de romperlo en 22 horas. A partir de ese momento se considera DES como inseguro, pero una nueva especificación, TripleDES o 3DES, una evolución de DES con clave de 128 bits y triple cifrado, lo sustituye. Desde 2001 se adopta como estandar AES, mucho más robusto.
  • RC5: Es la evolución de RC4, que consistía en hacer un XOR al mensaje con un vector aleatorio que se desprende de la clave. RC5 en cambio usa otra operación, llamada dependencia de datos. Como peculiaridad tiene un tamaño variable de bloque (16,32, 64 0 128 bits), de clave (de 0 a 2024 bits) y de número de vueltas (de 0 a 255)
  • IDEA: Trabaja con bloques de texto de 64 bits, operando siempre con números de 16 bits y usando operaciones como XOR, y suma y multiplicación de enteros. El algoritmo de desencriptación es muy similar al de encriptación, lo que lo hace muy rápido y fácil de programar. Actualmente se considera que es invulnerable a ataques por fuerza bruta, e incluso frente al criptoanálisis diferencial resiste muy bien, teniendo sólo vulnerabilidades las claves más cortas, pero con una clave fuerte es muy resistente. Además, se trata de un algoritmo de libre difusión, lo que permite que cualquiera pueda utilizarlo.
  • Diffie-Hellman: El protocolo criptográfico de Diffie-Hellman, llamado así por sus creadores, fue el punto de partida de los sistemas asimétricos, basados en clave pública y clave privada. Su validez para el intercambio de claves asimétricas hace que esté implementado en los diferentes sistemas seguros más habituales en Internet, como SSL o VPN. El algoritmo se basa en potencias de números y la función mod (módulo discreto), haciendo uso de las potencias discretas, fáciles de calcular pero siendo muy difícil obtener la función inversa. Este protocolo sí es sensible a ataques man in the middle, en los que alguien pueda interceptar la comunicación.
  • RSA: Aunque fue creado en 1977, RSA sigue siendo el sistema de clave pública más conocido y usado. Se trata de un sistema muy rápido y que se utiliza mucho en los sistemas mixtos (lo explicaré más abajo). Su seguridad radica en la dificultad de factorizar números enteros grandes. Los mensajes enviados se representan mediante números, y el funcionamiento se basa en el producto conocido de dos números primos grandes elegidos al azar. El cálculo de estas claves se realiza en secreto en la máquina en la que se va a guardar la clave privada.

Bueno, como ya comenté arriba, existen sistemas mixtos. Los sistemas de clave asimétrica requieren más coste de proceso que los de clave simétrica, este hecho ha llevado a buscar una solución híbrida que combine la seguridad de la transmisión de claves asimétricas con la mayor ligereza en el procesamiento de los sistemas simétricos. El concepto es sencillo: el mensaje se encripta utilizando una clave simétrica, la clave simétrica a su vez se encripta utilizando una clave pública y se envía todo en el mismo paquete. De esta forma sólo se requiere usar la clave privada para desencriptar la clave simétrica, y una vez se obtiene esta basta con utilizarla para descifrar el resto del mensaje.

Detectar un intruso en tu red wifi

Es más habitual de lo que creéis que algún cabroncete se cuele en vuestra red wifi. A fin de cuentas cualquier hijo de vecino se puede hacer con el Wifiway y un manual, y si su tarjeta  de red se lo permite en cuestión de minutos, u horas, se te cuela. Si notáis que la red empezó a ir más lenta desde hace una temporada, es un buen indicador de que puede que os estén jodiendo la wifi.

Hoy he probado dos aplicaciones: Zamzom Wireless Network Tool y Wireless Network Watcher.

El primero os lo podéis bajar desde su página oficial pero es de pago, por lo que la versión gratuita no tiene todas las funcionalidades. En todo caso lo básico sobre quién está conectado a la red te lo dará (IP y MAC), pero por ejemplo no te deja ver el nombre del equipo. 

En el caso del segundo, que puedes descargar aquí, la interfaz es más fea, si lo quieres en castellano te tienes que bajar un fichero con la traducción aparte y da la impresión de ir un poquitín más lento (realmente no, lo que pasa es que este no tiene análisis rápido como el Zamzom, por defecto hace siempre el análisis en profundidad). Ventajas: este te da toda la información sobre quién está conectado, por lo que es mucho más útil. Para instalar el idioma que quieras basta con descargarse el archivo de traducciones que hay en la misma página de descargas y copiarlo dentro de la carpeta donde tengáis el .exe guardado.

Hay una tercera opción: AirSnare que lleva sin actualizaciones desde 2006 pero que dicen que da buen rendimiento. En este caso sólo está disponible en alemán o en inglés.

Ok, y una vez detectado el intruso ¿qué? ¿Cómo echar a un intruso de la red wifi?. Pues toca cambiar la contraseña del router y, de paso, comprobad que esté usando una encriptación fuerte, porque si usa wep estáis jodidos, con Wifiway es cuestión de minutos entrar. Para cambiar de wep a un tipo de encriptación más fuerte, aquí tienes un tutorial de cómo hacerlo.
En fin, a ver si no tenéis muchos intrusos pateando vuestra red, jeje. Suerte con la caza.

Crear nuestra propia VPN en Ubuntu

Ya hace unos días hablamos de cómo configurar una IP estática en Ubuntu (y MacOS de regalo). Esto, como ya comenté en el artículo en cuestión, es necesario para ciertos servicios, como el que hoy nos atañe: Crear nuestra propia VPN en Ubuntu. Además, el puerto PPTP (1723 TCP) debe estar abierto. Como también necesitamos tener abierto otro puerto que soporte el protocolo GRE, lo mejor es usar la regla para PPTP del router. Manualmente no podemos realizar esta operación, pero las reglas predefinidas sí la realizan.

Una vez con la configuración previa lista, tocará irse al terminal para descargar e instalar el paquete PPTP, con permisos de administrador como para toda instalación. Tal que así:

sudo apt-get install pptpd

Como ya sabéis sudo nos permite realizar una instrucción con permisos de administrador (la primera vez que lo usas en el terminal te pedirá la contraseña). Cuando comience la instalación también os preguntará si queréis instalar el paquete bcrelay, pulsad la S para confirmar y vía.

Cuando termine la instalación, se lanzará de forma automática el servidor PPTP. Ahora toca meter mano en la configuración, con el editor de texto Nano.

sudo nano /etc/pptpd.conf

Al final de todo tenéis que añadir las líneas localip seguida de la ip que queráis tener como servidor local en la VPN (ojo, poned una que esté fuera del rango de IP locales, para evitar conflictos) y remoteip seguido del rango de direcciones que usaremos para asignar a los clientes, podéis poner un rango o varias separadas por comas. Aquí un ejemplo:

localip 10.11.0.1
remoteip 10.11.0.100-150,10.10.10.245

Ya tenéis un ejemplo con ambas opciones. Tras esto tocará añadir usuarios a nuestra VPN, configurando el archivo chap-secrets.

sudo nano /etc/ppp/chap-secrets

El formato es el siguiente, con los elementos separados por tabulaciones: nombre_usuario pptpd password *. El primero es el nombre de usuario (ponéis el que queráis darle a ese usuario en la red), lo siguiente el nombre del servidor (en este caso pptpd), la contraseña (poned algo seguro, no abc123) y la ip (un *, que ya nos ocuparemos de eso después).

En fin, tras esto, y como ya he dicho, toca ocuparse de las ip, concretamente configurando el cortafuegos. Como en todo lo primero es que nuestro amigo «el nano» nos abra el archivo rc.local:

sudo nano rc.local

En este caso vamos a la penúltima línea (justo antes de exit 0) del script y añadimos:

iptables -t nat -A POSTROUTING -s 10.11.0.0/24 -o eth0 -j MASQUERADE

El argumento 10.11.0.0/24 es el rango de direcciones que elegimos cuando estábamos configurando PPTPD, si pusiste otro configúralo en consecuencia. El argumento eth0 es el nombre de la tarjeta de red, asegúrate de poner la tuya. Guarda cambios y sigue porque ahora tocará editar otro archivo, el del ip forwarding:

sudo nano /etc/sysctl.conf

Ahí busca la línea comentada #net.ipv4.ip_forward=1 y quítale el # para descomentarla. Guarda cambios y sal.

Y con todo esto nuestro servidor de VPN en Ubuntu está configurado. Ahora sólo te queda configurar los clientes y ya tienes tu propia VPN creada, para poder comunicarte con seguridad con sus miembros, de forma que vuestros datos no estén expuestos a espionajes varios (eso sí, recuerda que toda seguridad no vale de nada si la contraseña de alguno de los usuarios es revelada, la seguridad perfecta no existe, ni existirá mientras haya un factor humano)

Consultas parametrizadas con PHP y MySQL

ESTA ENTRADA ESTÁ ANTICUADA. EL USO DE LA LIBRERÍA mysql YA NO SE RECOMIENDA EN PHP POR OBSOLETO. EN ESTE ENLACE TIENES UNA OPCIÓN MEJOR, CON MySQLi. PARA VER COMO HACERLO CON PDO PRUEBA CON ESTE.

Sigo con mi desarrollo freelance en PHP (para matar estos tiempos de desempleo), y hoy me he visto en la situación de tener que usar consultas parametrizadas. ¿Por qué usar consultas parametrizadas? Básicamente por seguridad, dado que las consultas parametrizadas son la mejor opción, a día de hoy, para evitar la inyección SQL (siempre y cuando estén bien implementadas). Lo habitual en las consultas MySQL en PHP es chuzarle tal cual los parámetros, concatenando cadenas de texto, lo que es muy inseguro, como en este ejemplo:

$sql = "SELECT * FROM tabla WHERE condicion1 = ".parametro1." and condicion2 = ".parametro2."";

En Java tenemos la función PreparedStatement como una gran ayuda, pero en PHP en principio no (aunque con PDO creo que hay algo similar, pero no he trabajado con dicho framework), así que lo que haremos será crear nuestro propio método para crear consultas parametrizadas y otro para tratar los parámetros.

Pero lo primero es crear la consulta sql tal que así:

$sql = "SELECT * FROM tabla WHERE condicion1 = ? and condicion2= ?";

Este es el formato de consulta parametrizada típico de Java, utilizando interrogaciones en lugar de los valores.

Bien, una vez tenemos así la idea es generar dos funciones. La primera comprobará el tipo de parámetro, si es cadena, array, número o nulo. Si es de cadena lo devolverá entre comillas y con los caracteres especiales ya escapados. Si es un array lo devolverá como una cadena de valores separados por comas, si es un nulo devolverá la cadena NULL y si no es ninguno de esos tres dará por hecho que se trata de un número y lo devolverá tal cual. El código sería el siguiente:

function prepareParam($parametro)
{
if(is_string($parametro))
{
// Si el parámetro es una cadena retorna cadena
return "'".mysql_scape_string($parametro)."'";
}
else if(is_array($parametro))
{
// Si es un array devolvemos una lista de los parámetros
// separados por comas.
$devolver = '';
foreach($parametro as $par)
{
// Cuando retorno es vacio ('') quiere decir que no
// tenemos que añadir la coma.
if($devolver == '')
{
$devolver .= prepararParametro($par);
}
else
{
$devolver .= ','.prepararParametro($par);
}
}
return $devolver;
}
else if($parametro == NULL)
{
// Si es nulo devolvemos la cadena 'NULL'
return 'NULL';
}
else
{
// Devolvemos el parametro.
return $parametro;
}

En fin, me disculpo por enésima vez por el sangrado. En fin, Pilarín, continuamos. Ya tenemos nuestro tratamiento de parámetros, ahora nos queda hacer la función que inserte los parámetros dentro de la consulta, y esta será la siguiente. Una función que recibe la cadena con la consulta y una lista de parámetros, parte la consulta por las interrogaciones, procesa los parámetros y se los chuza dentro:

function preparedStatement($cons_sql, $param = array())
{
// Partimos la consulta por los símbolos de interrogación
$partes = explode("?", $cons_sql);

$devolver = '';
$num_parametros = count($param);

// Recorremos los parametros
for($i = 0; $i < $num_parametros; $i++)
{
// Juntamos cada parte con el parametro correspondiente preparado
//con la función antes creada.
$devolver .= $partes[$i].prepareParam($param[$i]);
}

$num_partes = count($partes);
// Si hay más partes que parametros quiere decir que hay una parte final que hay que concatenar
if($num_partes > $num_parametros)
{
$devolver.= $partes[$num_partes -1];
}

// Devolvemos la consulta preparada
return $devolver;
}

Con esto ya tenemos nuestro PreparedStatement, casero y mejorable (podríamos, por seguridad, hacer que comprobara si el número de parámetros pasado coincide con el número requerido en la consulta, por ejemplo).

Existe otra forma de parametrizar utilizando una función nativa de PHP, sin tener que recurrir a bibliotecas externas, que era la que nos enseñaban en el San Clemente cuando hice allí el ciclo (idea de Rafa Veiga, que era el profesor que daba Proyecto Integrado): La idea es utilizar la función sprintf, que funciona como un printf (si has programado en C seguro que conoces dicha función) pero no imprime en pantalla, simplemente almacena en la variable la cadena construída de forma parametrizada. Dicho así suena un poco rollo, pero con este ejemplo lo entenderás:

$sql = sprintf("SELECT * FROM pi_usuarios WHERE nick = '%s' and password = '%s'", $_POST['nick'],$_POST['passwd']);

Bueno, esto era un fragmento de código de mi proyecto de curso de PHP. Básicamente la consulta recupera todos los registros de la tabla pi_usuarios cuyos valores nick y password coincidan con los que se han pasado por un método post (si hubiera más de uno, devolvería un error, si hubiera menos también, pero eso es tema para otro día que hablemos de tratamiento de errores en php). En cualquier caso os explico: la función sprintf (al igual que printf) sustituirá los %s por valores de cadena de texto que vendrán especificados tras la coma (en este caso las variables $_POST[‘nick] y $_POST[‘passwd’]). Dichos %s van entre comillas porque van a ser tratados en la consulta sql como cadenas, la idea es que paséis entre comillas todo lo que en la consulta, de forma nativa, iría entre comillas, y sin ellas lo que no las necesite (valores numéricos, mismamente). Lo que no se es si este sistema ayuda a evitar la inyección sql de forma efectiva o no, pero al menos se ve más claro que construyendo la consulta a base de concatenar cadenas.

En fin, espero que os haya sido útil.