[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 está. * Hola mundo ***

Las librerias que se van a utilizar son:

* openssl/bio.h

* openssl/ssl.h
*
* openssl/err.h
*

El hola mundo sería algo asi:

include

// Cabeceras OpenSSL

include

include

include

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 familiarizarse con los conceptos):

El equivalente al int ; es BIO * ;, esto almacenara los datos de la conexion.

La funcion para crear una nueva conexion es esta: = 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 esta función, si el valor devuelto es 0 o menor, no se ha podido conectar al host. BIO_do_connect();

Enviar y recibir datos se hace exactamente igual que con los sockets de BSD: -Para recibir: BIO_read(, , );

(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(, , );

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();

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();

Y para liberar la memoria: BIO_free_all();

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

include

// Cabeceras de OpenSSL

include

include

include

// 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(, , )

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

include

// Cabeceras de OpenSSL

include

include

include

// 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

include

include

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>,,);

Despues, solo hay que pasar los datos por RC4(<&key>,,,);

Por ejemplo:

include

include

include

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(&amp;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

untagged

Esteganografia en python: modulo de BMP acabado » « Bgame (un minijuego de 510 bytes)