martes, 27 de abril de 2010

[How to] OpenSSL en C/C++

Pues como parece que la documentacion de OpenSSL no es precisamente la mejor he pensado en hacer una guia rapida, aqui la teneis.

Pero, antes de empezar, que es OpenSSL ?


OpenSSL es una implementacion libre (bajo una licencia apache-like) de los protocolos SSL y TLS. Implementa las funciones basicas de criptografia y provee varias funciones utiles.

Instalacion:
Esto no tiene gran misterio, instala libssl-dev desde tu repositorio de paquetes y ya esta.


Empezamos... (Ir directamente a:)


Hola mundo:

Las librerias que se van a utilizar son:
  • openssl/bio.h
  • openssl/ssl.h
  • openssl/err.h

El hola mundo seria algo asi:


#include <stdio.h>
// Cabeceras OpenSSL

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

int main(int argc,char ** argv){

    // Iniciando OpenSSL
    SSL_load_error_strings();
    ERR_load_BIO_strings();
    OpenSSL_add_all_algorithms();

    printf("Hola, mundo de OpenSSL\n");
    return 0;
}

Hay que linkarlo con libssl (por ejemplo, compilando con el comando
gcc helloworld.c -lssl -o hello_world ).
El resultado es bastante obvio:
Hola, mundo de OpenSSL

No, no hace nada util, pero si compilo bien, se puede seguir tranquilamente... sino,(logicamente) hay un problema.

Seguimos...

Conexiones inseguras (es bastante parecido a unos sockets normales, pero sirve para famializarse con los conceptos):

El equivalente a el int <socket>; es BIO * <bio>;, esto almacenara los datos de la conexion.

La funcion para crear una nueva conexion es
<bio> = BIO_new_connect("hostname:port");
Como se puede ver, la sintaxis es bastante sencilla y no requiere montar estructuras para establecer conexiones.
Si la variable devuelta es NULL es que hubo un error creando el objeto BIO.
Para comprobar que la conexion se ha establecido se utiliza la funcion BIO_do_connect(<bio>);, si el valor devuelto es 0 o menor, no se ha podido conectar al host.

Enviar y recibir datos se hace exactamente igual que con los sockets de BSD:
-Para recibir:
BIO_read(<bio>, <buffer>, <longitud del buffer>);
(Para quien lo dude, el buffer es donde se leera la informacion, y debe ser un puntero (o un array), las otras variables son obvias ;)
El valor devuelto es el numero de bytes que se han leido, es posible que se necesite meter esta funcion en un bucle para asegurarse de que se leen todos los datos... aunque no suele haber problemas para buffer's de menos de 1Kb

-Para enviar es lo mismo:
BIO_write(<bio>, <buffer>, <longitud del buffer>);
El valor devuelto es (de nuevo) el numero de bytes enviados, sin problemas para menos de 1Kb, aun asi mejor con un bucle... ya cojeis la idea, ¿no?

Para determinar si se puede leer/escribir (enviar/recibir) en una conexion, la funcion es:
BIO_should_retry(<bio>);
Si no se puede, el valor devuelto es false ,de todas formas, en las pruebas, esta funcion causo algunos problemas (¿quiza al tratar con sockets de lectura bloqueantes?), si quieres mas informacion [ http://www.openssl.org/docs/crypto/BIO_should_retry.html ]

Un bucle simple (como este), solucionaria los posibles problemas:

int sendloop(BIO * bio,char *buf,int buflen){

    int pos=0,aux;
    while (((aux=BIO_write(bio,buf+pos,buflen-pos))<1)&&(pos>0)){

        pos+=aux;
        if (!BIO_should_retry(bio)){
            return 0;

        }
    }
    return 1;
}

Para cerrar la conexion, simplemente hacemos:
BIO_reset(<bio>);

Y para liberar la memoria:
BIO_free_all(<bio>);

Esto seria un ejemplo de cliente HTTP, con OpenSSL (se muestran las cabeceras y la pagina en si, esto se puede cambiar, pero la idea era mostrar como funcionan las conexiones):
#include <stdio.h>
#include <string.h>

// Cabeceras de OpenSSL
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

// Bucle para enviar datos
int sendloop(BIO * bio,char *buf,int buflen){

    int pos=0,aux;
    while (((aux=BIO_write(bio,buf+pos,buflen-pos))<1)&&(pos>0)){

        pos+=aux;
        if (!BIO_should_retry(bio)){
            return 0;

        }
    }
    return 1;
}

int main(int argc,char ** argv){

    // Comprueba que los parametros son los correctos (sin interes)
    if (argc!=3){
        printf("Uso: ./conexion <hostname> <port>\n");

        exit(1);
    }
    char host_port[256];

    sprintf(host_port,"%s:%s",argv[1],argv[2]);

    BIO * bio;
    // Se crea una nueva conexion
    bio = BIO_new_connect(host_port);

    if(bio == NULL)
    {
        printf("Error en BIO_new_connect\n");

    }
    // Se comprueba que la conexion se establecio correctamente
    if(BIO_do_connect(bio) <= 0)

    {
        printf("Error al establecer conexion\n");
    }

    int i;

    char buf[512];

    // Se introduce las cabeceras HTTP en un buffer
    sprintf(buf,"GET / HTTP/1.0\r\nHOST: %s\r\n\r\n",argv[1]);

    // Y se envian
    if ((i=sendloop(bio,buf,strlen(buf)))==0){

        printf("Error al enviar cabeceras\n");
        exit(0);
    }

    char ch_buf[2];

    // Para todos los bytes que se reciben
    while ((i=BIO_read(bio,&ch_buf,1))!=0){

        if (i>0){
            // Se muestran por pantalla
            putchar(ch_buf[0]);

        }
    }

    // La conexion ha finalizado
    printf("\nConexion finalizada\n");

    // Se cierra la conexion
    BIO_reset(bio);

    // Y se libera el espacio
    BIO_free_all(bio);

}

Conexiones seguras:
Las conexiones seguras funcionan igual que las otras, la unica diferencia es en el momento de establecer la conexion...
Ademas de BIO * bio;, se utilizan los siguientes objetos:
SSL_CTX * ctx;
SSL * ssl;

Despues, hay que cargar las librerias, esto se hace con
    SSL_library_init ();
    ERR_load_BIO_strings();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();

(Esto solo hay que hacerlo una vez en todo el programa)
El siguente paso, es crear un entorno SSL (SSL_CTX), que asignaremos a la variable ctx, esto se hace con las funciones SSL_CTX_new()  y SSLv*_method(), pasando como parametro de la primera, la salida de la segunda.
Pongo SSLv*_method, por que hay varias opciones, segun el protocolo que se utilizara, ademas cada opcion se puede utilizar para clientes (SSLv*_client_method), para servidores(SSLv*_server_method), o para los dos (SSLv*_method), para abreviar, se hablara solo de los que funcionan para ambas cosas, si prefieres una en concreto solo tienes que cambiar el nombre de la funcion(añadiendo _client o _server)...
  • SSLv2_method(): Para utilizar unicamente SSLv2 en todo el proceso
  • SSLv3_method(): Para utilizar unicamente SSLv3, esto puede producir problemas porque en las versiones que soportan varios protocolos, la conexion se suele iniciar con SSLv2
  • TLSv1_method(): Para utilizar solo TLSv1, con los mismos problemas de incompatibilidad que SSLv3_method()
  • SSLv23_method(): Para utilizar SSLv2, SSLv3 o TLS1, segun lo que soporte el otro extremos de la conexion, la conexion se iniciara como una de SSLv2 (esta es obviamente la opcion que se deberia usar a menos que haya razones para lo contrario)
Entonces, para iniciar el entorno, utilizamos:
    ctx=SSL_CTX_new(SSLv23_client_method());

Despues hay que cargar la lista de certificados fiables (al final dejo un archivo de prueba), esto se puede hacer desde un archivo o desde una carpeta, con:
SSL_CTX_load_verify_locations(<entorno>, <archivo de certificados>, <carpeta de certificados>)
Si el valor devuelto es false, es que algo fue mal.

Logicamente, no es necesario hacerlo de las dos formas, el valor que no se utilice se reemplaza por NULL, esto es lo que utilice para cargar los certificados desde un archivo:
    if(! SSL_CTX_load_verify_locations(ctx, trust_store_file, NULL))

    {
        printf("Error cargando certificados fiables\n");
        SSL_CTX_free(ctx);

        exit(0);
    }

Otra cosa, si vas a importar los certificados desde una carpeta, primero hay que prepararla para este proposito, esto se puede hacer simplemente con:
c_rehash /ruta/a/la/carpeta

El proximo paso es preparar los BIO y SSL:
    bio = BIO_new_ssl_connect(ctx);
    BIO_get_ssl(bio, & ssl);
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

Una vez hecho esto, hay que establecer la conexion, como se hace con las conexiones normales:
    BIO_set_conn_hostname(bio, host_port);

De nuevo, igual que en las conexiones normales, se comprueba que la conexion fue bien:
    if(BIO_do_connect(bio) <= 0){
        printf("Error al establecer la conexion\n");
    }

Por ultimo, solo queda comprobar que el certificado es correcto, en caso de que no lo sea, queda en manos del programador cerrar la conexion o continuarla:

    if(SSL_get_verify_result(ssl) != X509_V_OK)
    {
        // No, no es valido :(
        // Pero, se puede continuar con la conexion, preguntemos al usuario
        char op;
        printf("El certificado no es valido, quieres continuar con la conexion(S/n)");
        op=getchar();
        if (op=='n'){
            SSL_CTX_free(ctx);
            exit(1);
        }
    }

El resto de la conexion se utiliza como una normal.

Al final, cuando se acabe de utilizar ese entorno ssl, hacemos:
SSL_CTX_free(ctx);

Este seria el ejemplo anterior del cliente HTTP, pero funcionando sobre SSL (recuerda que el puerto de HTTPS es 443):
#include <stdio.h>
#include <string.h>

// Cabeceras de OpenSSL
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

// Constantes
#define trust_store_path "TrustStore/"
#define trust_store_file "TrustStore.pem"

// Bucle para enviar datos
int sendloop(BIO * bio,char *buf,int buflen){

    int pos=0,aux;
    while  (((aux=BIO_write(bio,buf+pos,buflen-pos))<1)&&(pos>0)){

        pos+=aux;
        if (!BIO_should_retry(bio)){
            return 0;

        }
    }
    return 1;
}

int main(int argc,char ** argv){

    // Comprueba que los parametros son los correctos (sin interes)
    if (argc!=3){
        printf("Uso: ./conexion <hostname> <port>\n");

        exit(1);
    }
    char host_port[256];

    sprintf(host_port,"%s:%s",argv[1],argv[2]);

    // Se preparan los objetos
    SSL_CTX * ctx;
    SSL * ssl;

    BIO * bio;

    // Y se levanta la libreria
    SSL_library_init ();

    ERR_load_BIO_strings();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();

    ctx=SSL_CTX_new(SSLv23_method());

    // Se carga la lista de certificados fiables desde un archivo
    if(! SSL_CTX_load_verify_locations(ctx, trust_store_file, NULL))

    {
        printf("Error cargando certificados fiables\n");
        SSL_CTX_free(ctx);

        exit(0);
    }

    /*
    // Se carga la lista de certificados fiables desde una carpeta

    // Antes hay que usar este comando:
    // c_rehash /ruta/a/la/carpeta
    //
    if(! SSL_CTX_load_verify_locations(ctx, NULL, trust_store_path))
    {
        printf("Error al cargar la lista de certificados fiables\n");

        exit(1);
    }
    */

    // Configuramos el BIO y el SSL
    bio = BIO_new_ssl_connect(ctx);

    BIO_get_ssl(bio, & ssl);
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

    // Se establece la conexion
    BIO_set_conn_hostname(bio, host_port);

    // Se comprueba la conexion y se realiza el "apreton de manos"

    if(BIO_do_connect(bio) <= 0){
        printf("Error al establecer la conexion\n");

        SSL_CTX_free(ctx);
        exit(1);
    }

    // Se comprueba que el certificado es valido

    if(SSL_get_verify_result(ssl) != X509_V_OK)
    {
        // No, no es valido :(

        // Pero, se puede continuar con la conexion, preguntemos al usuario
        char op;
        printf("El certificado no es valido, quieres continuar con la conexion(S/n)");
        op=getchar();

        if (op=='n'){
            SSL_CTX_free(ctx);
            exit(1);

        }
    }

    int i;
    char buf[512];
  
    // Se introduce las cabeceras HTTP en un buffer
    sprintf(buf,"GET / HTTP/1.0\r\nHOST: %s\r\n\r\n",argv[1]);

    // Y se envian
    if ((i=sendloop(bio,buf,strlen(buf)))==0){

        printf("Error al enviar cabeceras\n");
        SSL_CTX_free(ctx);
        exit(0);

    }

    char ch_buf[2];

    // Para todos los bytes que se reciben
    while ((i=BIO_read(bio,&ch_buf,1))!=0){

        if (i>0){
            // Se muestran por pantalla
            putchar(ch_buf[0]);

        }
    }

    // La conexion ha finalizado
    printf("\nConexion finalizada\n");

    // Se cierra la conexion
    BIO_reset(bio);

    // Y se libera el espacio
    BIO_free_all(bio);

    // Limpiamos los datos del SSL
    SSL_CTX_free(ctx);

}


Funciones de criptografia:
La idea no es mostrarlas todas, sino mostrar un par de ejemplos, el resto lo podeis buscar a traves de man o en http://www.openssl.org/docs/crypto/crypto.html

SHA-1 (funcion hash)

Para hacer el hash SHA-1 de un string, la libreria utilizada es:
openssl/sha.h

El uso es bastante sencillo,se utilizan 3 variables:
  • El array que se hasheara
  • La longitud del array
  • El buffer donde se guardara la salida
Ahi va el codigo:
#include <stdio.h>

#include <string.h>
#include <openssl/sha.h>

int main(int argc,char **argv){

    if (argc<2){
        printf("Uso: ./sha1 <palabra> [<palabra>] [<palabra>]\n");

    }
    int i;
    for (i=1;i<argc;i++){

        // Hasta aqui, nada interesante, viene ahora
        int digest[5];
        // Obtenemos la salida y la mostramos
        SHA1(argv[i],strlen(argv[i]),(char *)digest);

        printf("[%s] -> ",argv[i]);
        int j;

        for (j=0;j<5;j++){
            printf("%x",digest[j]);

        }
        putchar('\n');
    }
}





RC4 (cifrado simetrico):
La libreria utilizada es openssl/rc4.h

Para iniciar un cifrado, hay que crear la clave, esto se hace con RC4_set_key(<&key>,<longitud de clave>,<clave>);
Despues, solo hay que pasar los datos por RC4(<&key>,<longitud del buffer>,<buffer de entrada>,<buffer de salida>);

Por ejemplo:
#include <stdio.h>
#include <string.h>

#include <openssl/rc4.h>

#define buff_len 256

void write_all(FILE* f,void *buf,size_t n){

    int pos=0,tmp;
    while (((tmp=fwrite(buf+pos,sizeof(char),n-pos,f))>0)&&(pos<n)){

        pos+=tmp;
    }
}

int main(int argc,char **argv){

    if (argc<4){
        printf("Uso: ./rc4 <contraseña> <archivo de entrada> <archivo de salida>\n");

        exit(1);
    }

    FILE *fin,*fout;

    fin=fopen(argv[2],"r");
    if (fin==NULL){

        printf("El archivo de entrada esta vacio\n");
        exit(1);
    }

    fout=fopen(argv[3],"a");
    if (fout==NULL){

        printf("Error al crear el archivo de salida\n");
        exit(1);
    }

    RC4_KEY key;
    RC4_set_key(&key,strlen(argv[1]),argv[1]);

    int l;
    char buffer[buff_len];
    char buffer_out[buff_len];

    while ((l=fread(buffer,sizeof(char),buff_len,fin))>0){

        RC4(&key,l,buffer,buffer_out);
        write_all(fout,buffer_out,l);

    }

    fclose(fin);
    fclose(fout);
    return 0;

}

Aqui teneis en ZIP con todos los archivos: [openssl_how_to.zip]

[Referencias]
http://www.ibm.com/developerworks/views/linux/libraryview.jsp?search_by=openssl&type_by=Articles
http://www.openssl.org/docs/crypto/crypto.html

lunes, 26 de abril de 2010

Esteganografia en python: modulo de BMP acabado

Pues nada, el modulo de bmp esta acabado, es decir, soporta:
  • BMP de 24 Bits (8R,8G,8B)
  • BMP de 32 Bits (8R,8G,8B,8X), sin compresion
  • BMP de 32 Bits (8R,8G,8B,8X), con compresion (bit field)
No esta previsto añadir cosas, pues eso es lo mas que soporta BMP,  las imagenes con menor profundidad ya ni se considera soportarlas, porque la esteganografia seria muy evidente.

pyStego_bmp01.zip

Lo proximo sera añadir soporte para GIF,JPEG o PNG (y APNG?) y soportar todos estos formatos antes de pasar a otro de datos (audio o video)

Hasta ahora!

sábado, 24 de abril de 2010

Esteganografia en python

Llevo bastante tiempo dandole vueltas a la idea de hacer una libreria de esteganografia en python, para poder usarla como base para una aplicacion completa... pues me he decidido a empezar, y estos son los primeros resultados:

baseops.py

bmp.py

El primero es simplemente un conjunto de funciones que creo que pueden ser de utilidad en los distintos modulos (extraer el LSB de un byte, descomponer un char en bits, leer un numero de un archivo...)

El segundo es lo que va comenzando a ser el modulo de esteganografia con BMP, por ahora esta bastante limitado, solo soporta imagenes de al menos 24bits de profundidad (esto seguramente no cambie, pues aceptar profundidades menores solo hace que la implementacion no sea tan sencilla, añadiendo una utilidad minima).
Ademas, por ahora no soporta ningun tipo de compresion, aunque la idea seria implementar compresion/descompresion usando Bit Field (que se utiliza en imagenes de 32 bits)

La clase que se utilizaria es stego_bmp, inicializandose con el nombre del archivo del bmp base o a analizar, las funciones son:

bmp.addmsg(mensaje[,profundidad_utilizada]) # Añade un string a la imagen
bmp.addfile(archivo[,profundidad_utilizada]) # Añade un archivo a la imagen

bmp.getmsg([profundidad_utilizada]) # Recupera un string a la imagen
bmp.getfile(archivo[,profundidad_utilizada]) # Recupera un archivo de la imagen

bmp.dump(mensaje) # Guarda la imagen

Y por supuesto, todo esta bajo la GPLv3 =)

Por ultimo avisar que esto no utiliza ningun tipo de estructura, al recuperar un mensaje o un archivo seguramente obtengais una ristra de bytes extraños al final de el... esto es normal, la idea es hacer que sea versatil, si quieres asegurarte de cuales son los extremos de tu mensaje puedes utilizar algun tipo de caracter especial, se podria utilizar el caracter nulo ('\0') como en C, por ejemplo.

Hasta otra!

Actualizando el Jamendo OGG redirector

Pues resulta que el script de greasemonkey que se encargaba de redirigir las descargas de MP3 a las OGG funcionaba... pero solo si se estaba utilizando noScript, cosa que no tuve en cuenta (perdon) :P

Ya esta el codigo arreglado (solo hubo que añadir un substr() y listo), y funciona usando noScript o no ;)

Podeis descargarlo [aqui]

Y aqui esta el codigo fuente (para quien no le apetezca buscarlo por la pagina)

Y ya esta, perdon por las molestias

jueves, 22 de abril de 2010

Introduccion a la criptografia, con Python: MD5 (III)

Siguendo con el tema, en esta parte veremos como funciona la funcion de hash MD5:

¿Que es una funcion HASH?

En informática, Hash se refiere a una función o método para generar claves o llaves que representen de manera casi unívoca a un documento, registro, archivo, etc., resumir o identificar un dato a través de la probabilidad, utilizando una función hash o algoritmo hash. Un hash es el resultado de dicha función o algoritmo. [Wikipedia]
Es decir, es una funcion de un solo sentido (a partir del resultado no se puede recuperar el original) que identifica un "objeto" con precision, si cambia un byte, cambia todo el resultado.

Esto se suele utilizar (por ejemplo) para comprobar que las descargas se realizaron correctamente, comparando el hash del archivo original y el del archivo descargado, si son iguales, todo fue bien :)

MD5 es una funcion de hash, pero presenta debilidades, como colisiones, por eso se deberia evitar usar en el futuro, de todas formas, aun se utiliza y servira de introduccion para este tipo de funciones.
Vamos alla...



class md5:
    def __init__(self,s):

        ln=len(s)*8
        self.padd(s)

        self.attach_len(ln)
        self.buff_init()
        self.update(ln)

        self.final()


El primer paso es añadir un bit '1' a lo que se quere hashear, y despues añadir '0's hasta que el tamaño en bits sea igual a 448, siendo modulo 512 (o 56, modulo 64, en bytes):


    def padd(self,s):

        self.stream=s+"\x80"
        while ((len(self.stream)%64)!=56):

            self.stream+="\x00"


Despues hay que añadir a continuacion el tamaño en bits (al principio, antes del paso anterior) del archivo/mensaje/etc..., con un numero de 64 bits(8 bytes):


    def attach_len(self,ln):
        ln%=1<<64
        i=7

        l=[]
        while i>=0:
            k=256**i

            aux=ln/k
            l.append(aux)
            ln-=aux*(k)

            i-=1
        while len(l)>0:
            self.stream+=chr(l.pop(len(l)-1))


Se inicializan cuatro buffers (A,B,C y D) (de 32 bits) que se utilizan en el algoritmo:


    def buff_init(self):
        self.a = 0x67452301
        self.b = 0xefcdab89
        self.c = 0x98badcfe
        self.d = 0x10325476




Se procesa el  objeto/archivo/..., esto es la parte mas "opaca" del algoritmo...
Para comenzar se divide el objeto en partes de 64 bytes, concretamente en arrays de 16 elementos, con 4 bytes cada uno:


    def update(self,ln):
    
        i=0

        while i<(len(self.stream)/64):
            j=0

            x=[]
            while j<16:
                t=((ord(self.stream[(i*64)+(j*4)+0]))|

                 (ord(self.stream[(i*64)+(j*4)+1])<< 8)|

                 (ord(self.stream[(i*64)+(j*4)+2])<< 16)|

                 (ord(self.stream[(i*64)+(j*4)+3])<< 24))

                x.append(t)
                j+=1
            self.transform(x)

            i+=1



Despues, con cada division se obtienen los valores de los buffer's:


    def transform(self,x):

        AA=self.a
        BB=self.b
        CC=self.c

        DD=self.d


Y se le aplican una serie de transformaciones (veremos despues en detalle como funcionan esas transformaciones):


        #Ronda 1
        AA=FF (AA, BB, CC, DD, x[ 0], S11, 0xd76aa478) # 1

        DD=FF (DD, AA, BB, CC, x[ 1], S12, 0xe8c7b756) # 2

        CC=FF (CC, DD, AA, BB, x[ 2], S13, 0x242070db) # 3

        BB=FF (BB, CC, DD, AA, x[ 3], S14, 0xc1bdceee) # 4

        AA=FF (AA, BB, CC, DD, x[ 4], S11, 0xf57c0faf) # 5

        DD=FF (DD, AA, BB, CC, x[ 5], S12, 0x4787c62a) # 6

        CC=FF (CC, DD, AA, BB, x[ 6], S13, 0xa8304613) # 7

        BB=FF (BB, CC, DD, AA, x[ 7], S14, 0xfd469501) # 8

        AA=FF (AA, BB, CC, DD, x[ 8], S11, 0x698098d8) # 9

        DD=FF (DD, AA, BB, CC, x[ 9], S12, 0x8b44f7af) # 10

        CC=FF (CC, DD, AA, BB, x[10], S13, 0xffff5bb1) # 11

        BB=FF (BB, CC, DD, AA, x[11], S14, 0x895cd7be) # 12

        AA=FF (AA, BB, CC, DD, x[12], S11, 0x6b901122) # 13

        DD=FF (DD, AA, BB, CC, x[13], S12, 0xfd987193) # 14

        CC=FF (CC, DD, AA, BB, x[14], S13, 0xa679438e) # 15

        BB=FF (BB, CC, DD, AA, x[15], S14, 0x49b40821) # 16


        # Round 2
        AA=GG (AA, BB, CC, DD, x[ 1], S21, 0xf61e2562) # 17

        DD=GG (DD, AA, BB, CC, x[ 6], S22, 0xc040b340) # 18

        CC=GG (CC, DD, AA, BB, x[11], S23, 0x265e5a51) # 19

        BB=GG (BB, CC, DD, AA, x[ 0], S24, 0xe9b6c7aa) # 20

        AA=GG (AA, BB, CC, DD, x[ 5], S21, 0xd62f105d) # 21

        DD=GG (DD, AA, BB, CC, x[10], S22, 0x2441453) # 22

        CC=GG (CC, DD, AA, BB, x[15], S23, 0xd8a1e681) # 23

        BB=GG (BB, CC, DD, AA, x[ 4], S24, 0xe7d3fbc8) # 24

        AA=GG (AA, BB, CC, DD, x[ 9], S21, 0x21e1cde6) # 25

        DD=GG (DD, AA, BB, CC, x[14], S22, 0xc33707d6) # 26

        CC=GG (CC, DD, AA, BB, x[ 3], S23, 0xf4d50d87) # 27

        BB=GG (BB, CC, DD, AA, x[ 8], S24, 0x455a14ed) # 28

        AA=GG (AA, BB, CC, DD, x[13], S21, 0xa9e3e905) # 29

        DD=GG (DD, AA, BB, CC, x[ 2], S22, 0xfcefa3f8) # 30

        CC=GG (CC, DD, AA, BB, x[ 7], S23, 0x676f02d9) # 31

        BB=GG (BB, CC, DD, AA, x[12], S24, 0x8d2a4c8a) # 32


        # Round 3
        AA=HH (AA, BB, CC, DD, x[ 5], S31, 0xfffa3942) # 33

        DD=HH (DD, AA, BB, CC, x[ 8], S32, 0x8771f681) # 34

        CC=HH (CC, DD, AA, BB, x[11], S33, 0x6d9d6122) # 35

        BB=HH (BB, CC, DD, AA, x[14], S34, 0xfde5380c) # 36

        AA=HH (AA, BB, CC, DD, x[ 1], S31, 0xa4beea44) # 37

        DD=HH (DD, AA, BB, CC, x[ 4], S32, 0x4bdecfa9) # 38

        CC=HH (CC, DD, AA, BB, x[ 7], S33, 0xf6bb4b60) # 39

        BB=HH (BB, CC, DD, AA, x[10], S34, 0xbebfbc70) # 40

        AA=HH (AA, BB, CC, DD, x[13], S31, 0x289b7ec6) # 41

        DD=HH (DD, AA, BB, CC, x[ 0], S32, 0xeaa127fa) # 42

        CC=HH (CC, DD, AA, BB, x[ 3], S33, 0xd4ef3085) # 43

        BB=HH (BB, CC, DD, AA, x[ 6], S34, 0x4881d05) # 44

        AA=HH (AA, BB, CC, DD, x[ 9], S31, 0xd9d4d039) # 45

        DD=HH (DD, AA, BB, CC, x[12], S32, 0xe6db99e5) # 46

        CC=HH (CC, DD, AA, BB, x[15], S33, 0x1fa27cf8) # 47

        BB=HH (BB, CC, DD, AA, x[ 2], S34, 0xc4ac5665) # 48


        # Round 4
        AA=II (AA, BB, CC, DD, x[ 0], S41, 0xf4292244) # 49

        DD=II (DD, AA, BB, CC, x[ 7], S42, 0x432aff97) # 50

        CC=II (CC, DD, AA, BB, x[14], S43, 0xab9423a7) # 51

        BB=II (BB, CC, DD, AA, x[ 5], S44, 0xfc93a039) # 52

        AA=II (AA, BB, CC, DD, x[12], S41, 0x655b59c3) # 53

        DD=II (DD, AA, BB, CC, x[ 3], S42, 0x8f0ccc92) # 54

        CC=II (CC, DD, AA, BB, x[10], S43, 0xffeff47d) # 55

        BB=II (BB, CC, DD, AA, x[ 1], S44, 0x85845dd1) # 56

        AA=II (AA, BB, CC, DD, x[ 8], S41, 0x6fa87e4f) # 57

        DD=II (DD, AA, BB, CC, x[15], S42, 0xfe2ce6e0) # 58

        CC=II (CC, DD, AA, BB, x[ 6], S43, 0xa3014314) # 59

        BB=II (BB, CC, DD, AA, x[13], S44, 0x4e0811a1) # 60

        AA=II (AA, BB, CC, DD, x[ 4], S41, 0xf7537e82) # 61

        DD=II (DD, AA, BB, CC, x[11], S42, 0xbd3af235) # 62

        CC=II (CC, DD, AA, BB, x[ 2], S43, 0x2ad7d2bb) # 63

        BB=II (BB, CC, DD, AA, x[ 9], S44, 0xeb86d391) # 64



Y por ultimo, se devuelven los valores que se tomaron de los buffer a ellos:


        self.a=(self.a+AA)&0xFFFFFFFF
        self.b=(self.b+BB)&0xFFFFFFFF

        self.c=(self.c+CC)&0xFFFFFFFF
        self.d=(self.d+DD)&0xFFFFFFFF



Una cosa... el hacer and con 0xFFFFFFFF a las variables es para evitar que ocupen mas de 32 bytes, otra forma seria hacerlo modulo 0x100000000, pero esta es mas rapida.
Bien, veamos en que consisten las transformaciones... para empezar, S11,S12,S.. son constantes predefinidas, sus valores son estos:


S11=7
S12=12

S13=17
S14=22
S21=5
S22=9
S23=14

S24=20
S31=4
S32=11
S33=16
S34=23

S41=6
S42=10
S43=15
S44=21

Ademas en las transformaciones se utilizan unas funciones basicas:
def F(x,y,z):

    return (x&y)|((~x&0xFFFFFFFF)&z)

def G(x,y,z):
    return (x&z)|(y&(~z&0xFFFFFFFF))

def H(x,y,z):
    return x^y^z

def I(x,y,z):
    return y^(x|(~z&0xFFFFFFFF))

# Rotacion
def rotate_left(x,y):
    return ((x<<y)|(x>>(32-y)))

Una vez definido eso, las transformaciones no son demasiado complejas:
def FF(a,b,c,d,x,s,ac):

    a=(a+(F(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a

def GG(a,b,c,d,x,s,ac):

    a=(a+(G(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a

def HH(a,b,c,d,x,s,ac):

    a=(a+(H(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a

def II(a,b,c,d,x,s,ac):

    a=(a+(I(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a



Como se puede ver, el patron es bastante obvio:


def XX(a,b,c,d,x,s,ac):

    a=(a+(X(b,c,d)+x+ac))

    a= rotate_left(a,s)
    a=(a+b)

    return a


El resto solo sirve para evitar que los valores se salgan de los 32 bits

Y por ultimo, se presenta como resultado lo que hay en los buffers, comenzando por el byte menos significativo de A y acabando por el mas significativo de D, normalmente se hace directamente en la representacion hexadecimal:


    def final(self):

        self.digest=[]
        for some in [self.a,self.b,self.c,self.d]:

            t=uint4tochar(some)
            while len(t)>0:

                thing=t.pop(len(t)-1)
                self.digest.append(myhex(thing)[2:4])

 
    def hexdigest(self):
        from string import join

        return join(self.digest,'')



Las funciones utilizadas son estas:


#Pasa un entero de 4 bytes a un array de 4 bytes
def uint4tochar(s):
    i=0
    t=[0,0,0,0]

    while i<4:
        aux=(s>>(8*i))&0xFF

        t[3-i]=int(aux)
        i+=1

    return t

#Devuelve un hexadecimal con 4 caracteres o mas
def myhex(a):
    r=hex(a)

    if (len(r)==3):
        r="0x0"+r[2]

    return r


El código completo aqui [md5.py]

Si se ejecuta directamente (no importandolo), hace unas pruebas para comprobar que funciona bien, las comprobaciones se hacen contra hashlib:

d41d8cd98f00b204e9800998ecf8427e = d41d8cd98f00b204e9800998ecf8427e True
81dc9bdb52d04dc20036dbd8313ed055 = 81dc9bdb52d04dc20036dbd8313ed055 True
9e107d9d372bb6826bd81d3542a419d6 = 9e107d9d372bb6826bd81d3542a419d6 True
e4d909c290d0fb1ca068ffaddf22cbd0 = e4d909c290d0fb1ca068ffaddf22cbd0 True
6f728eb0a9ef9387cb17dba7cc2116fb = 6f728eb0a9ef9387cb17dba7cc2116fb True


Hasta la proxima ;)

[Referencia]
RFC 1321 
MD5 - Wikipedia
Otra implementacion