Tutoriales

Firma y valida archivos con OpenSSL

OpenSSL es un conjunto de aplicaciones que permite enviar y recibir información con seguridad a través de conexiones de red haciendo uso de certificados o llaves públicas y privadas. En este tutorial aprenderás a generar un par de llaves privada y pública, generar un hash tipo SHA de cualquier archivo que quieras compartir de forma segura, firmar el SHA (Secure Hash Algorithm) del archivo con la llave privada y por último validar el archivo contra su firma SHA haciendo uso de la llave pública. Se hará uso de la librería OpenSSL en C para firmar y verificar el SHA. Se hará uso del programa openssl en consola para generar las llaves. Todo esto es útil para compartir archivos entre 2 entidades y al firmar y verificar esos archivos se garantiza que el archivo no fue manipulado durante el intercambio del mismo de una entidad a otra.

 

Ítems necesarios

+ Un equipo corriendo Linux

+ La librería libssl-dev instalada (instrucciones más adelante)

 

Fuentes

+ OpenSSL how-to: RSA_verify - Bertrand Mollinier
http://www.bmt-online.org/geekisms/RSA_verify

+ Stack Overflow
http://stackoverflow.com/questions/29550708/openssl-dgst-verify-and-sign-equivalency-with-rsa-verify

 

Nota 1

Se recomienda ya no utilizar este método de firma y validación ya que se ha visto que es vulnerable. Es recomendable utilizar la siguiente guía:

EVP_Sign* and EVP_Verify*

https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying

 

Nota 2 

Existe una compilación de OpenSSL en Windows también pero me es más fácil usar Linux para esto.

 

Generación de las llaves privada y pública

Para poder firmar y verificar cualquier archivo es necesario contar con un par de llaves. La llave privada es la llave que el emisor del archivo debe tener. NO DEBE COMPARTIRLA ya que si la comparte, alguien más podría firmar el archivo y hacerlo pasar como legítimo. La llave pública es una llave que se genera a partir de la llave privada y servirá para validar la firma del archivo.

Para generar la llave privada abre una consola en Linux y teclea el siguiente comando. Hace uso del algoritmo RSA.

openssl genrsa -out private.key 2048

La llave generada se llamará private.key. A partir de tal llave se debe generar la llave pública. Hazlo a través del siguiente comando.

openssl rsa -in private.key -out public.key -outform PEM -pubout

Más detalles de la operación aquí:

https://www.openssl.org/docs/apps/rsa.html

 

Comandos de consola

Si te interesa usar comandos de consola para generar tus archivos encriptados... te sugiero que uses los siguientes:

 * Generate a private key
 * "openssl genrsa -out private.key 2048"
 *
 * Extract the public key (DER certificate form) from the private key (needed by RSA_verify())
 * "openssl req -outform DER -new -x509 -key private.key -out public.key -days 30000"
 *
 * Generate a public key with no certificate info (only needed by "openssl dgst -sha1 -verify ...")
 * "openssl x509 -inform DER -in public.key -pubkey -noout > public_no_cert.key"
 *
 * Sign a file with the private key
 * "openssl dgst -sha1 -sign private.key -out file.sign file"
 * 
 * Verify a file with the public key (no certificate info)
 * "openssl dgst -sha1 -verify public_no_cert.key -signature file.sign file"


Código fuente

El archivo que quieres compartir será convertido en un SHA (Secure Hash Algorithm) y después será firmado con la llave privada. Este archivo y la firma SHA le deberán ser entregados al receptor para que los valide contra la llave pública. La llave privada no debe compartirse.

La firma se lleva a cabo con la función sign_data().

Una vez firmado el archivo, la validación se hará comparando el archivo supuestamente original, la firma y la llave pública.

La llave pública no puede firmar archivos, así que puede ser compartida... públicamente.

La verificación del archivo puede hacerse con verify_data().

A continuación el código fuente del ejemplo.

 

#include <openssl/bio.h>

#include <openssl/err.h>

 

#include <openssl/rsa.h>

#include <openssl/sha.h>

#include <openssl/pem.h>

#include <openssl/x509.h>

#include <malloc.h>

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

 

#define FILE_PATH "/home/user/workspace/src/file"

#define PRIVATE_KEY_PATH "/home/user/workspace/src/private.key"

#define SIGNATURE_PATH "/home/user/workspace/src/permissions.sign"

#define PUBLIC_KEY_PATH "/home/user/workspace/src/public.key"

 

static unsigned char RSA_Read_File(const char* path, unsigned char* buffer, size_t size);

static size_t RSA_Get_File_Size(const char* path);

 

/** ------------------------------------------------------------------------

 * Function definitions

 * ---------------------------------------------------------------------- */

 

/**

 * Read a file from the file system and store it in buffer. Read up to

 * size bytes from the file.

 * @param path - a path for the file

 * @param buffer - an array to write the contents of the file

 * @param size - a maximum number of bytes to read from the file, if available

 * @return 1 if the file was correctly read; 0 if not

 */

static unsigned char RSA_Read_File(const char* path, unsigned char* buffer, size_t size)

{

   unsigned char result = 0;

 

   if((NULL == path) || (NULL == buffer) || (0 == size))

   {

      printf("Invalid parameters for RSA_Read_File\n");

      result = 0;

   }

   else

   {

      FILE *ifp = fopen(path, "r");

 

      if (ifp == NULL)

      {

         printf("Error while opening [%s]\n", path);

         result = 0;

      }

      else

      {

         size_t file_size = RSA_Get_File_Size(path);

         size_t read_bytes = 0;

 

         if(file_size < size)

         {

            size = file_size;

         }

         read_bytes = fread(buffer, 1, size, ifp);

         fclose(ifp);

 

         if(read_bytes != size)

         {

            printf("Read [%u] expected [%u] from [%s]\n", read_bytes, size, path);

            result = 0;

         }

         else

         {

            result = 1;

            printf("Read [%u] bytes from [%s]\n", read_bytes, path);

         }

      }

   }

 

   return result;

}

 

/**

 * Get the size of a file

 * @param path - a path for the file

 * @return the size of the file

 */

static size_t RSA_Get_File_Size(const char* path)

{

   size_t size = 0;

   struct stat filestatus;

   int result = -1;

 

   if(NULL == path)

   {

      printf("path is null\n");

   }

   else

   {

      result = stat(path, &filestatus);

 

      if(0 == result)

      {

         size = filestatus.st_size;

      }

      else

      {

         printf("stat failed [%s]\n", path);

         size = 0;

      }

   }

 

   printf("Size [%u] bytes from [%s]\n", size, path);

 

   return size;

}

 

int sign_data(

      const void *buf, /* input data: byte array */

      size_t buf_len,

      void *pkey, /* input private key: byte array of the PEM representation */

      size_t pkey_len,

      void **out_sig, /* output signature block, allocated in this function */

      size_t *out_sig_len)

{

   int status = EXIT_SUCCESS;

   int rc = 1;

 

   BIO *b = NULL;

   RSA *r = NULL;

 

   unsigned char *sig = NULL;

   unsigned int sig_len = 0;

 

   SHA_CTX sha_ctx = { 0 };

   unsigned char digest[SHA_DIGEST_LENGTH];

 

   rc = SHA1_Init(&sha_ctx);

   if (1 != rc)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   rc = SHA1_Update(&sha_ctx, buf, buf_len);

   if (1 != rc)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   rc = SHA1_Final(digest, &sha_ctx);

   if (1 != rc)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   b = BIO_new_mem_buf(pkey, pkey_len);

   r = PEM_read_bio_RSAPrivateKey(b, NULL, NULL, NULL);

 

   sig = malloc(RSA_size(r));

   if (NULL == sig)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   rc = RSA_sign(NID_sha1, digest, sizeof digest, sig, &sig_len, r);

   if (1 != rc)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   *out_sig = sig;

   *out_sig_len = sig_len;

 

   if (NULL != r)

      RSA_free(r);

   if (NULL != b)

      BIO_free(b);

   if (EXIT_SUCCESS != status)

      free(sig); /* in case of failure: free allocated resource */

   if (1 != rc)

      fprintf(stderr, "OpenSSL error: %s\n", ERR_error_string(ERR_get_error(), NULL));

 

   return status;

}

 

int verify_data(

        const void *buf,    /* input data: byte array */

        size_t buf_len,

        const void *sig,    /* signature block: byte array */

        size_t sig_len,

        const void *cert,   /* input cert: byte array of the DER representation */

        size_t cert_len)

{

   int status = EXIT_SUCCESS;

   int rc = 1; /* OpenSSL return code */

 

   SHA_CTX sha_ctx = { 0 };

   unsigned char digest[SHA_DIGEST_LENGTH];

 

   BIO *b = NULL;

   X509 *c = NULL;

   EVP_PKEY *k = NULL;

 

   rc = SHA1_Init(&sha_ctx);

   if (1 != rc)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   rc = SHA1_Update(&sha_ctx, buf, buf_len);

   if (1 != rc)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   rc = SHA1_Final(digest, &sha_ctx);

   if (1 != rc)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   b = BIO_new_mem_buf(cert, cert_len);

   if (NULL == b)

   {

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   c = d2i_X509_bio(b, NULL);

   if (NULL == c)

   {

      BIO_free(b);

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   k = X509_get_pubkey(c);

   if (NULL == k)

   {

      BIO_free(b);

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   /* make sure that the public key from the cert is an RSA key */

   if (EVP_PKEY_RSA != EVP_PKEY_type(k->type))

   {

      EVP_PKEY_free(k);

      BIO_free(b);

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   rc = RSA_verify(NID_sha1, digest, sizeof(digest), sig, sig_len, EVP_PKEY_get1_RSA(k));

   printf("rc RSA_Verify %i\n", rc);

   if (1 != rc)

   {

      EVP_PKEY_free(k);

      BIO_free(b);

      status = EXIT_FAILURE;

      return status;

   }

   printf("%u\n", __LINE__);

 

   if (NULL != k)

      EVP_PKEY_free(k);

   if (NULL != b)

      BIO_free(b);

 

   if (1 != rc)

      fprintf(stderr, "OpenSSL error: %s\n", ERR_error_string(ERR_get_error(), NULL));

 

   return status;

}

 

int main(int argc, char** argv)

{

   int result;

 

   /* Sign */

 

   unsigned char* input_file_buffer;

   size_t input_file_size;

 

   unsigned char* private_key_buffer;

   size_t private_key_size;

 

   unsigned char* signature;

   size_t signature_size;

 

   /* Verify */

 

   unsigned char* signature_buffer;

 

   unsigned char* public_key_buffer;

   size_t public_key_size;

 

   /* Read file to sign */

   input_file_size = RSA_Get_File_Size(FILE_PATH);

   input_file_buffer = (unsigned char*)malloc(input_file_size);

   if(NULL == input_file_buffer)

   {

      printf("malloc fail %u\n", __LINE__);

      return EXIT_FAILURE;

   }

   RSA_Read_File(FILE_PATH, input_file_buffer, input_file_size);

 

   /* Read private key */

   private_key_size = RSA_Get_File_Size(PRIVATE_KEY_PATH);

   private_key_buffer = (unsigned char*)malloc(private_key_size);

   if(NULL == private_key_buffer)

   {

      printf("malloc fail %u\n", __LINE__);

      return EXIT_FAILURE;

   }

   RSA_Read_File(PRIVATE_KEY_PATH, private_key_buffer, private_key_size);

 

   /* ----------------------------------------------------------------------------- */

   /* Sign */

   result = sign_data(

         input_file_buffer,

         input_file_size,

         private_key_buffer,

         private_key_size,

         (void**)&signature,

         &signature_size);

 

   printf("SIGN result %d %u\n", result, __LINE__);

 

   if(private_key_buffer)

   {

      free(private_key_buffer);

   }

 

   /* Write the signature into a file */

   if(EXIT_SUCCESS == result)

   {

      FILE *fp;

 

      fp = fopen(SIGNATURE_PATH, "w+");

 

      if(NULL == fp)

      {

         free(signature);

         printf("fopen failed %u\n", __LINE__);

         return -1;

      }

 

      fwrite(signature, 1, signature_size, fp);

 

      fclose(fp);

   }

 

   /* Read the signature */

   signature_size = RSA_Get_File_Size(SIGNATURE_PATH);

   signature_buffer = (unsigned char*)malloc(signature_size);

   if(NULL == signature_buffer)

   {

      printf("malloc fail %u\n", __LINE__);

      return EXIT_FAILURE;

   }

   RSA_Read_File(SIGNATURE_PATH, signature_buffer, signature_size);

 

   /* Read the public key */

   public_key_size = RSA_Get_File_Size(PUBLIC_KEY_PATH);

   public_key_buffer = (unsigned char*)malloc(public_key_size);

   if(NULL == public_key_buffer)

   {

      printf("malloc fail %u\n", __LINE__);

      return EXIT_FAILURE;

   }

   RSA_Read_File(PUBLIC_KEY_PATH, public_key_buffer, public_key_size);

 

   /* ----------------------------------------------------------------------------- */

   /* Verify */

   result = verify_data(

         input_file_buffer,

         input_file_size,

         signature_buffer,

         signature_size,

         public_key_buffer,

         public_key_size);

 

   printf("VERIFY result %d %u\n", result, __LINE__);

 

   if(input_file_buffer)

   {

      free(input_file_buffer);

   }

 

   if(signature_buffer)

   {

      free(signature_buffer);

   }

 

   if(public_key_buffer)

   {

      free(public_key_buffer);

   }

 

   return 0;

 

}

 

Compilación

Para compilar el código necesitas tener instalado el paquete libssl-dev para que se incluyan los headers de compilación en Linux. Ejecuta el siguiente comando:

sudo apt-get install libssl-dev

Una vez instalado, llama a gcc invocando el uso de las librerías dinámicas ssl y crypto.

gcc -Wall -o rsa_sign_test rsa_sign_test.c -lssl -lcrypto

 

Ejecución

Asegúrate de que todos los archivos estén en el path indicado y simplemente ejecuta el programa. Si todo sale bien podrás ver que RSA_verify() devuelve un 1 al confirmar que el archivo es legítimo.