El talón de Aquiles de la criptografía: cómo los PRNG pueden arruinar tu seguridad

El taló d'Aquil·les de la criptografia: com els PRNG poden arruïnar la teva seguretat

Celia Catalán

Accessibility


A02:2021 - Fallades criptogràfiques – Nombres aleatoris pseudoaleatoris (PRNG)

Introducció

La generació de números aleatoris en programació és crucial per a moltes aplicacions, especialment en criptografia. Un número aleatori és aquell que no es pot predir o anticipar, la qual cosa garanteix la seguretat en la generació de claus, tokens, o qualsevol valor sensible que ha de romandre secret.

Hi ha dos tipus principals de generadors de números aleatoris (RNG, per les seves sigles en anglès):

1. Generadors de números aleatoris pseudoaleatoris (PRNG): Utilitzen algoritmes deterministes que, tot i que produeixen seqüències de números que semblen aleatoris, són predecibles si es coneix l'estat inicial o llavor. Això és perillós en contextos criptogràfics, ja que un atacant podria replicar la seqüència i anticipar valors sensibles.

2. Generadors de números aleatoris veritablement aleatoris (TRNG): Basats en fenòmens físics imprevisibles, com la radiació electromagnètica o el soroll tèrmic, aquests generadors són molt més segurs, ja que no són previsibles.

La vulnerabilitat sorgeix quan s'utilitzen PRNG insegurs o mal implementats en sistemes criptogràfics. Si un atacant pot predir els números generats, pot trencar la seguretat dels sistemes, derivar claus criptogràfiques, interceptar comunicacions segures o accedir a recursos protegits.

Aquesta relació entre aleatorietat i seguretat és una de les raons per les quals OWASP inclou els errors en la criptografia, com la generació de números aleatoris insegurs, entre les principals preocupacions de seguretat (A02:2021 Falles Criptogràfiques).

Introducció Tècnica

Un algoritme de Generació de Números Pseudoaleatoris (PRNG) és un sistema determinista que transforma un valor inicial conegut com a llavor mitjançant una funció matemàtica predefinida per produir una seqüència de números que, a primera vista, semblen aleatoris. No obstant això, aquesta aleatorietat és només superficial, ja que la seqüència està completament determinada per la llavor inicial. Això implica que dues instàncies del mateix algoritme, si són inicialitzades amb la mateixa llavor, generaran una seqüència idèntica de números. Encara que aquesta propietat pot ser útil en certs contextos, com en simulacions reproductibles, és altament problemàtica en criptografia, on la predictibilitat socava els principis fonamentals de seguretat.

Des del punt de vista de la seguretat criptogràfica, es depèn en gran mesura de la qualitat i la imprevisibilitat dels números aleatoris utilitzats en processos com la generació de claus, vectors d'inicialització i nonce. Els PRNG tradicionals, com els generadors congruencials lineals (LCG), són vulnerables a atacs a causa de la seva estructura matemàtica senzilla i la poca entropia utilitzada en les llavors. Aquests generadors solen dependre de valors predecibles, com timestamps, que un atacant pot inferir.

A diferència dels PRNG tradicionals, els generadors de números pseudoaleatoris criptogràficament segurs (CSPRNG) estan dissenyats per resistir atacs fins i tot si s'exposa parcialment el seu estat intern o si s'intercepten múltiples sortides de la seqüència. Els CSPRNG utilitzen fonts d'entropia més robustes, pot ser un generador de números pseudoaleatoris en maquinari o fins i tot processos de sistemes imprevisibles, i apliquen algoritmes criptogràfics avançats, com els basats en xifrats en bloc o funcions hash, per garantir la imprevisibilitat de la seqüència.

Vulnerabilitats principals

Predictibilitat en generació de claus criptogràfiques: La predictibilitat dels PRNG insegurs compromet la generació de claus criptogràfiques. Si un atacant aconsegueix inferir la llavor utilitzada S_0, ja sigui mitjançant una anàlisi de les sortides del generador o endevinant una llavor basada en un valor previsible com un timestamp, pot reconstruir la clau privada associada i desxifrar missatges o realitzar signatures fraudulentes. Aquest problema ha afectat històricament algorismes com el RSA i l'ECC, on la qualitat de les claus depèn directament de la qualitat dels nombres aleatoris.

Nonces reutilitzats o predecibles: En sistemes criptogràfics com AES-GCM o ChaCha20-Poly1305, la reutilització d'un mateix nonce (número únic per missatge) compromet la confidencialitat i integritat de les dades. Si un PRNG genera nonces N predecibles o reutilitza valors a causa de col·lisions en un espai reduït, un atacant pot realitzar atacs com la recuperació de text pla a través d'anàlisis de diferències. Un cas notable és la reutilització de nonces en la implementació de WPA2, que va permetre atacs com KRACK.

Sessions JWT vulnerables: Els tokens JSON Web Tokens (JWT) depenen de valors únics i aleatoris per evitar que siguin replicats o endevinats. Si un PRNG insegur genera identificadors de sessió o valors de signatura predecibles, un atacant podria forjar tokens vàlids per accedir a sistemes protegits. Per exemple, una signatura HMAC basada en claus derivades de números pseudoaleatoris predecibles podria permetre un atac de suplantació amb esforç computacional reduït.

Problemes en protocols de handshake: Protocols com TLS i SSH depenen de valors aleatoris per assegurar que les sessions siguin úniques i no puguin ser reproduïdes per un atacant. Si els números pseudoaleatoris utilitzats en el handshake són predecibles, un atacant podria executar atacs de "replay" o forçar la renegociació de claus dèbils, comprometen la confidencialitat del canal. Això es va evidenciar en versions antigues de SSL/TLS que utilitzaven PRNG deficients.

Comparació de generació segura vs. insegura

La generació es pot dividir en tres passos principals:

1. Obtenció de la llavor inicial:

En la generació insegura, la llavor sol derivar-se de fonts fàcilment predecibles, com un timestamp, l'identificador de procés (PID) o fins i tot un valor constant. Per exemple, un generador congruencial lineal pot utilitzar l'hora actual en segons, la qual cosa redueix dràsticament l'espai de cerca per a un atacant. D'altra banda, en la generació segura, la llavor prové d'una font d'entropia robusta, com estats interns recollits pel sistema operatiu. Això assegura que la llavor no sigui previsible ni reproductible.

2. Càlcul de l'estat intern:

Un PRNG insegur transforma la llavor inicial mitjançant operacions matemàtiques simples, com multiplicacions o sumes modulares, que, tot i que generen números aparentment aleatoris, presenten patrons detectables amb prou anàlisi. En canvi, un generador segur aplica algoritmes criptogràfics com funcions hash o xifrats en bloc, els quals posseeixen una gran resistència a la reversió de l'estat intern.

3. Producció de la seqüència de números:

Els PRNG insegurs generen cada número en la seqüència mitjançant fórmules ràpides però deterministes, la qual cosa permet predir futures sortides si es coneix l'estat actual. Per exemple, en observar algunes sortides consecutives, un atacant podria deduir l'algorisme i predir les següents (en casos extremadament senzills). En comparació, els generadors segurs produeixen números utilitzant transformacions que depenen tant de l'estat intern com de l'entropia addicional recollida durant l'operació, fent que cada sortida sigui pràcticament imprevisible.

Anem al codi...


Terminal

               
#include (stdio .h="")
#include (stdlib .h="")
#inclou (temps .h="")

int generar_número_aleatori() {
    static int inicialitzat = 0;
       si (!inicialitzat) {
        srand((unsigned int)time(NULL)); // (-- Ús del timestamp com a llavor
        inicialitzat = 1;
    }

    retorna rand() % 100;
}

int main() {
    printf(Generant números pseudoaleatoris:
);
    per (int i = 0; i (5,i++) {
        printf(%d\n, generar_número_aleatori());
    }
    retorn 0;
}

      
Inicialment s'utilitza time(NULL) com a llavor, mitjançant la funció srand s'inicialitza el generador de nombres pseudoaleatoris amb el valor actual del timestamp (segons des de l'epoch). Malgrat que de primeres pot semblar raonable, atès que el temps canvia constantment, no deixa de ser previsible. Si un atacant aconsegueix conèixer el moment en què s'executa el programa, pot calcular fàcilment la llavor utilitzada.

srand() inicialitza el generador de números aleatoris creant una llavor, en canvi rand() simplement retorna un número aleatori, aquest últim utilitza un generador congruencial lineal (LCG), sent matemàticament simple i produint seqüències predecibles.

El generador inicialitza la llavor una única vegada, mai s'actualitza amb nova entropia, la qual cosa implica que totes les execucions del programa, en un període curt, generaran les mateixes seqüències de números pseudoaleatoris, resultant en col·lisions previsibles.

A més, l'espai de sortida és limitat. El rang de sortida de rand() és petit, típicament entre 0 i 2^32-1, i en el cas de l'exemple, es redueix amb l'operador mòdul. Facilitant la possibilitat d'atacs de força bruta.

Per això es proposa l'aproximació següent per generar els mateixos valors possibles, d'una manera segura.
  • Ús de syscall com getrandom(), la qual és una font d'entropia criptogràficament segura proporcionada per Linux (requereix glibc versió 2.25 o superior).
  • Si el sistema no suporta getrandom(), es pot utilitzar /dev/urandom per obtenir números aleatoris de qualitat similar. En sistemes Windows la funció equivalent seria BCryptGenRandom.
  • Alternativament, en CPUs amb RDRAND (Intel/AMD), es pot fer ús de _rdrand32_step, permetent obtenir números directament del maquinari.

Terminal

#inclou (fcntl.h)
#inclou (errno.h)
#inclou (cadena.h)
#include (linux/random.h)
#include (sys/syscall.h)

int generar_número_random_segur() {
    uint32_t número_aleatori;

    // Usar syscall getrandom para obtener un número aleatorio seguro
    if (syscall(SYS_getrandom, random_number, sizeof(random_number), 0) == -1) {
        perror("Error en generar un número aleatori segur");
        sortida (EXIT_FAILURE);
    }

    retorna random_number % 100;
}

int main() {
    printf("Generant números aleatoris segurs:
");
    for (int i = 0; i ( 5; i++) {
        printf(%d\n, generar_número_aleatori_segur());
    }
    retorn 0;
}
    
      

Daniel Puente, pentester a Zerolynx
Tornar al bloc

Deixa un comentari

Tingueu en compte que els comentaris s'han d'aprovar abans que es publiquin.