Publi

2 Maneras de generar identificadores universalmente únicos (UUID) en C

Una buena forma de poner nombre a nuestros recursos con más o menos certeza de que ese nombre es único, es utilizar UUID‘s. Es cierto, que si en máquinas diferentes se generan UUIDs al mismo tiempo, puede ser que haya cierta coincidencia en los valores generados (si nos ponemos cabezones, y si en algún sitio deben convivir varios UUIDs, podemos comprobar que no estén repetidos. Incluso, dependiendo del algoritmo utilizado para generarlos, puede que en la misma máquina haya valores repetidos.

En C, usando Linux, podemos utilizar la biblioteca uuid para generar dichos identificadores, para obtener la cadena del identificador podemos hacer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <uuid/uuid.h>

int main()
{
  uuid_t out;
  char uuid[37];
  uuid_generate(out);
  uuid_unparse(out, uuid);

  printf ("UUID: %s\n", uuid);

  return 0;
}

Para compilar tenemos que enlazar con la biblioteca uuid:

$ gcc -o uuid uuid.c -luuid

Hemos visto cómo creamos una estructura uuid_t, esa estructura es un conjunto de 16 bytes de datos (los 16 bytes que componen el identificador), ya que la forma escrita legible es más grande (ocupa 36bytes + terminador, más del doble). Esta variable nos puede servir para almacenar el identificador en disco o en memoria (si visualizamos el valor de sizeof(uuid_t) nos dará 16 independientemente de la plataforma en la que estemos).

Tenemos varias funciones de libuuid:

  • uuid_generate_random: generará UUID v4 con una fuente puramente aleatoria (/dev/urandom), y si éste no está accesible utiliará otra fuente.
  • uuid_generate_time: utiliza un algoritmo que combina la fecha y hora actuales y la dirección MAC del dispositivo de red, aunque puede comprometer la privacidad del dónde se generó esta clave, no es mala fuente de unicidad. Genera UUID v1
  • uuid_generate: intenta hacer un uuid_generate_random() y si no es posible, uuid_generate_time()

Si no tenemos por ahí la biblioteca uuid, o no queremos depender de ella. Podemos utilizar otro método, accediendo directamente al dispositivo /dev/random o /dev/urandom para generar valores aleatorios (podíamos utilizar rand() y srand():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <uuid/uuid.h>
#include <fcntl.h>
#include <linux/random.h>
#include <sys/ioctl.h>

#define RANDOM_DEVICE "/dev/urandom"

int my_uuid_gen(char uuid[57])
{
    int rfd = open(RANDOM_DEVICE, O_RDONLY);
    unsigned char buffer[16];
    int generated = 0;
    int entropy;
    int total;
    if (ioctl(rfd, RNDGETENTCNT, &entropy) == -1)
        return 0;

    while ( (generated<16) && ((total=read(rfd, (buffer+generated), 16-generated)) > 0) )
        {
            generated+=total;
        }

    if (total == -1)
        return 0;

    /* Generate byte 9 */
    while ( (buffer[8]<80) && (buffer[8]>191) && ((total=read(rfd, &buffer[8], 1))>0) );
    if (total == -1)
        return 0;

    sprintf(uuid, "%02x%02x%02x%02x-%02x%02x-4%x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
           buffer[0],  buffer[1],  buffer[2],  buffer[3],  buffer[4],  buffer[5],
            (buffer[6]%16),  buffer[7],  buffer[8],  buffer[9],  buffer[10], buffer[11],
           buffer[12], buffer[13], buffer[14], buffer[15]);

    return 1;
}

int main()
{
  char uuid[50];

  if (my_uuid_gen(uuid))
      printf ("UUID: %s\n", uuid);
  else
      printf ("Fallo al generar\n");

  return 0;
}

Como UUID v4 que estamos generando, la primera cifra del tercer grupo es un 4, y la primera del cuarto grupo está entre 8 y b. Aquí además, podemos elegir dispositivo de números aleatorios, en principio tenemos /dev/random y /dev/urandom.

El primero es mejor fuente de aleatoriedad que el segundo, aunque el primero también puede llegar a ser más lento. Con la llamada ioctl(rfd, RNDGETENTCNT, &entropy) podemos obtener la entropía de la fuente y podemos ver cuántos bytes (dividiendo entre 8, porque esa entropía viene en bits) encontramos en el dispositivo ahora mismo. Si no hay suficientes bytes, la llamada a read bloqueará el proceso hasta que tengamos suficientes información (podemos mover el ratón o hacer otras cosas en el sistema para generar números aleatorios con cierta calidad).

En cambio /dev/urandom es una fuente inagotable de números aleatorios, aunque de menor calidad (para generar UUID nos vale, pero para generar claves no es tan buena).

Foto: NASA en Flickr Creative Commons

También podría interesarte...

Leave a Reply