Călin-Cristian CRUCERU (25528) - Hardware Random Number Generator

Autorul poate fi contactat la adresa: Login pentru adresa

Introducere

Aruncarea unei monezi cinstite

Proiectul constă în implementarea unui Hardware Random Number Generator.

Cunoscute și sub numele de TRNGs (True Random Number Generators), acestea sunt dispozitive care generează secvențe de numere aleatoare - secvențe care nu pot fi prezise, deci care nu urmează un pattern justificabil - folosind ca sursă a entropiei un fenomen fizic, cum ar fi zgomotul atmosferic, zgomotul termic, descompuneri radioactive, fenomene cuantice, etc. Acestea sunt diferite de numerele pseudoaleatoare generate de PRNGs 1), care sunt secvențe de numere rezultate în urma aplicării unui algoritm pe un set - mic, în general - de numere inițiale, cunoscut și ca seed-ul 2) generatorului.

O analogie intuitivă între cele două tipuri de generatoare de numere aleatoare și aruncarea unei monezi cinstite este următoarea: un PRNG, pentru că folosește formule matematice și/sau liste precalculate, corespunde efectuării unui număr mare de experiemente Bernoulli și memorarea rezultatelor, urmând ca la cererea următorului bit aleator, să se răspundă cu următorul bit memorat. Pe de altă parte, un TRNG ar efectua un nou experiment pentru fiecare cerere nouă (într-o manieră leneșă/reactivă).

PRNG-urile sunt uneori preferate TRNG-urilor acolo unde eficiența este critică, datorită throughput-ului lor mult superior; ineficiența celor din urmă provine din faptul că, de foarte multe ori, dispozitivul trebuie să petreacă un timp nedeterminat pentru a atinge nivelul de entropie dorit. Pe de altă parte, există situații în care nondeterminismul oferit de un TRNG reprezintă o necesitate (e.g. extrageri la loterii, jocuri de noroc, generare chei de criptare).

Descriere generală

La un nivel înalt, proiectul este divizat în două module logice:

Achiziția stream-ului de biți

Având ca și inspirație Entropy Key 3) și Intel DRNG 4), implementarea curentă folosește două generatoare de entropie, bazate pe zgomotul de avalanșă 5) generat de o joncțiune PN polarizată invers, care se află în zona de străpungere 6), al căror output este digitizat. Mai departe, se face suma XOR a celor două stream-uri de biți astfel rezultate, care va fi trecută apoi printr-un algoritm de debiasing 7). În cele din urmă, noul stream va fi folosit ca seed pentru un DRBG, spre exemplu CTR-DRBG, care are scopul de a împrăștia entropia obținută într-un set mai mare de numere aleatoare, asigurând un throughput sporit.

În limita timpului disponibil, proiectul își propune și verificarea automată a bias-ului sistemului, folosind algoritmul lui Uli Maurer, permițând încetarea funcționării și notificarea în cazul detecției unui bias semnificativ.

Accesul la biții generați

În varianta brută, biții generați vor fi afiașați într-o consolă legată prin conexiune serială la microcontroller-ul ATMega324. Pentru ca dispozitivul să fie cu adevărat util, accesul la aceștia trebuie să se facă mai ușor. Pentru asta, proiectul ia în considerare două abordări:

* [varianta lightweight] crearea unui mic Web server (e.g. https://random.cs.pub.ro/bits 8-)), care să genereze un nou șir random de dimensiune fixă la fiecare request, permițând astfel o folosire neinteractivă:

$ curl random.cs.pub.ro/bits
b08abd37753c0a39a3764a4d1bc8b2ce192751de961160140e5c5a66e2d7afb8
$ curl random.cs.pub.ro/bits
1321a368b9677403082f84ca9e402ea2c0fdbd6d2fd828b6b5e9e297dc83dcc5

* [varianta real thing] crearea unui modul de kernel care să permită interfațarea stream-ului de biți prin intermediul /dev/urandom, sau a unui nou char device (e.g. /dev/hwrng)

Hardware Design

Lista de piese:

Nume piesă Cantitate
7805CT 1
CONECT JACK 2089 1
Condensator 10uF 1
Hex Inverter 74HCT04 2
Tranzistor 2N3904 4
Rezistență 820K 4
Rezistență reglabilă 500K 4
Rezistență 15K 2
Rezistență 10K 2
Condensator 100nF 5
PL2303 - Serial to USB Module 1

Primele 3 piese fac parte din lista de piese opționale pusă la dispoziție și sunt necesare pentru alimentarea la 12V (detalii mai jos). Ultima piesă este un modul serial to USB pe care l-am folosit atât pentru debugging, cât și în versiunea finală a proiectului pentru achiziția streamului de biți random. Vrusesem inițial să folosesc V-USB - asemenea bootloadHID - dar nu am găsit o implementare a stdio pentru acesta.

Schema electrică:

Deși am analizat mai multe variante, m-am decis asupra acestei scheme atât datorită simplității precum și a succesului pe care pare să îl fi avut în trecut (din experiența colegilor din anii anteriori). Se folosește ca sursă de zgomot joncțiunea BE a primului tranzistor, polarizată invers. Aceasta este alimentată la 12V, după care zgomotul este amplificat prin intermediul a două inversoare cu rezistențe de dimensiuni foarte mari în reacție. Am folosit o rezistență de 800K și una de 500K reglabilă, pentru a putea face fine-tunning. Ultimul inversor are rolul de a prelua spike-urile din zgomotul amplificat de precedentele și a-l aduce la 5V.

Circuitul integrat (hexinverter-ul) este alimentat la 5V (detaliu care lipsește de pe schema de mai sus). Inițiam am încercat să folosesc tot 5V și pentru polarizarea inversă, însă outputul ajungea la un maxim de 3-3.5V cu o medie de 2.5V și era destul de greu să stabilesc ce reprezintă un spike; se vedeau mai degrabă variațiile de tensiune de pe alimentare decât zgomotul - la atingerea colectorului care se află în aer nu se vedea aproape nicio variație pe output. La 12V se observau pe osciloscop mult mai bine spike-uri la intervale de timp neregulate.

NU am mai folosit 2 surse de randomness așa cum plănuisem inițial pentru că am considerat că este un efort duplicat nenecesar.

Software Design

Initial setup

Pentru programarea plăcuței am folosit booloadHID (din repo-urile ArchLinux) folosind ieșirea USB implicită de pe aceasta. Modulul Serial to USB mi-a creat un char device (/dev/ttyUSB0 - setat la 9600 baud rate, 8 data bits, 1 stop bit, no parity) de pe care citesc outputul programului, după inițializarea stdio prin USART (vezi /hwrng/src/stdio_uart_init.c în repo-ul public al proiectului meu pe GitLab).

Achiziția biților random

Am folosit proprietatea de prescaling a ADC-ului, pe care am setat-o la 4 (division factor) pentru a lua sample-uri. Flow-ul este de forma: programez o nouă conversie, interceptez rezultatul prin intrerupere iar la sfârșitul acesteia programez o nouă conversie.

Plecând de la valorile din intervalul 0-1023 pe care mi le oferă ADC-ul cu rezoluție 10 a μC-ului, trebuie să ofer un stream de octeți cu valori aleatoare. Evident că folosirea directă a valorilor pe care mi le dă ADC-ul nu este o soluție deoarece acestea sunt concentrate în intervalul 400-800, cu spike-uri mai rare în afara acestuia. La fel ca și în cazul schemei electrice, și aici am încercat mai multe variante. Cele mai notabile sunt:

  • compararea cu o valoare hardcodată, de referință; atunci când valoarea sample-ului este mai mare decât această valoare se consideră un bit de 1, iar când valoarea sample-ului este mai mică se consideră un bit de 0; în mod surprinzător poate, aceasta a dat, empiric, cele mai bune rezultate (după o căutare binară de mână a acestei valori de referință), însă are dezavantajul clar că output-ul poate fi ușor influențat prin intervenția asupra circuitului.
  • folosirea parității numărului de sample-uri consecutive care au valoarea peste o valoare de referință; spre exemplu, în urma unei secvențe de forma:
  LOW LOW LOW HIGH HIGH HIGH LOW HIGH HIGH LOW LOW HIGH LOW LOW LOW HIGH
              --------------     ---------         ----             ----
                    1                0               1                1

s-ar genera biții 1011. Această metodă are un throughput ceva mai scăzut ca anterioara (nu semnificativ), însă indiferent de valoarea de referință folosită există un bias semnificativ (10-20%) către una dintre valori.

Odată cu această metodă am încercat să folosesc și algoritmul lui John von Neumann de debiasing, care este foarte ușor de implementat. Rezultatul s-a văzut imediat, în sensul că bias-ul a dispărut complet. Problema fundamentală, însă, cu acest algoritm este că el nu modifică proprietățile de randomness ale streamului inițial, ci doar elimină bias-ul (e.g. șirul infinit 1010101010101010… este unbiased însă cel mai probabil nu este random). O problemă la fel de mare este aceea că inevitabil scade throughputul enorm pentru că se dă drop la fiecare 2 biți consecutivi egali.

  • varianta finală, la care am și rămas, este măsurarea parității numărului de sample-uri dintre 2 secvențe HIGH consecutive. Dacă luăm același exemplu ca mai devreme:
  LOW LOW LOW HIGH HIGH HIGH LOW HIGH HIGH LOW LOW HIGH LOW LOW LOW HIGH
  >           <              >   <         >       <    >           <
  -------------              -----         ---------    -------------
        1                      1               0              1

s-ar genera biții 1101. Experimental, această metodă s-a dovedit destul de bună. Există încă un bias către 0, indiferent de valoarea de referință, însă nu este atât de semnificativ: frecvențele valorile din intervalul 0-255 pe un fișier de 1MB generat variază între 0.2 și 0.8 (vezi fișierele /test/statistics/statistics- pentru o analiză exactă). Throughputul este satisfăcător - am generat fișierul de 1MB în aproximativ 10 minute.

Folosirea entropiei obținute

M-am oprit cu experimentele la range-ul mai sus menționat deoarece, așa cum specificasem inițial, urma să folosesc oricum un DRBG (Deterministic Random Byte Generator) pentru împrăștierea entropiei obținute. Am folosit implementarea din biblioteca mbedtls, care este un Counter Mode DRBG bazat pe AES-256. Pipeline-ul generării finale de numere random este următorul:

                                       64 bytes                   max 384 bytes
  +--------------+ 48 bytes  +---------------------+ 48 bytes   +-----------+
  | "Raw" random | --------> | Entropy Accumulator | ---------> | DRBG Seed |
  | bytes        |           | (based on SHA-512)  |            |           |
  +--------------+           +---------------------+            +-----------+
         ^                          |  ^                            |   |
         |         gather entropy   |  |           reseed           |   |
         +--------------------------+  +----------------------------+   |
                                                                        |
                                                   48 bytes             |
                                         +----------------------------- +
                                         |
                                         V
                                    +---------+
                                    | AES-256 |
                                    +---------+
                                         |
                                         | max 1024 bytes
                                         V
                                 +-----------------+
                                 | Final stream of |
                                 | random bytes    |
                                 +-----------------+

Time-consuming a fost înțelegerea modului de funcționare a acestei componente din mbedtls, însă folosirea ulterioară este foarte straight-forward (vezi fișierele sursă /aws/src/bytes_supply.{h,c} din cadrul webserver-ului).

Web Server

Pentru interfațarea cu stream-ul de octeți rezultați, am implementat un Web Server care permite obținerea unui număr fix de octeți random la fiecare request. Această etapă a fost destul de ușoară pentru că am plecat de la implementarea din cadrul temei 5 de la SO. Ca urmare, aceasta este o implementare care rulează pe Linux și este foarte eficientă, folosind mecanisme avansate de multiplexare a I/O-ului precum și API-ului nonblocant al sockets-ilor. Voiam să folosesc și API-ul de asynchronous IO (KAIO) din Linux, însă acesta nu funcționează pe porturi seriale, așa că singurul element blocant - și cu siguranță bottleneck-ul în cazul unui număr mare de conexiuni - este obținerea octeților random de la plăcuță.

Un posibil output al unui request către acest web server arată în felul următor:

calin:aws/ (master✗) $ curl http://localhost:8888/bytes 

Here are 256 random bytes.  Down this page you can find more about this TRNG.

ed b3 0c 0b 9b 5f 74 81 4b 94 10 0a 74 7c df 8c 82 c4 90 1c fc 53 ce a8 ef 28
1d 98 6b a5 9f b7 5a 34 39 92 c1 56 55 99 67 9f b1 4e c8 92 0d d8 d7 d1 27 ba
35 8b 30 fd a3 f3 bd 93 55 43 f2 a8 27 d6 77 6d 2d b5 5a 06 69 42 d5 15 f4 15
2d 76 77 ba b1 b3 a0 a4 15 f9 ba 03 09 2b c8 67 8a 80 88 39 0c a8 0d 04 70 74
58 2e b1 08 0b 8f a7 d7 c6 dd 54 f2 da 70 eb 4a 14 40 c5 dc a0 06 54 8b ac d7
d6 70 bf c8 94 5a c6 e1 b8 d1 82 52 88 9e 73 57 4f d8 62 af f7 aa 4f 2e 03 15
74 fd 24 7e b3 1d 7e fa 62 3d 3c 30 72 c6 e6 5f f9 84 53 9f cc 39 dd fb d3 e2
85 14 e8 0e dd db 09 9a 39 d6 29 ed e9 be f8 7a f6 b1 66 5d f3 29 2f 35 a8 44
d3 ed 62 03 d5 a5 a5 b9 7a 87 90 2e 70 04 5a 1c 2e a7 dc b6 95 5b c5 8a b1 89
ed e2 70 da 1f 86 b2 29 17 d1 6e 25 4b f1 6c ca 72 bd 97 a6 8a 9f
...

NU am mai implementat algoritmul lui Uli Maurer de verificare automată a bias-ului sistemului; am petrecut ceva timp încercând să înțeleg paper-ul respectiv, însă nu am reușit să înțeleg pe deplin și nici nu am găsit alți oameni care să-l fi implementat. NU am mai luat în considerare varianta interfațării stream-ului de biți prin intermediul unui nou char device în /dev deoarece intrarea pe care o crează modulul Serial-USB este foarte asemnănătoare cu ceea ce voiam să fac, iar efortul necesar pentru a învăța cum să fac un modul de kernel pentru asta mi s-a părut prea mare, raportat la timpul liber.

Rezultate Obţinute

Concluzii

Nu este foarte greu nici din punct de vedere electronic și nici din punct de vedere al software-ului (postprocesare) să faci un Hardware Random Number Generator; există foarte multe surse de randomness care pot fi folosite în acest sens. Partea care necesită mult trial and error este testarea acestuia, asigurarea fiabilității și calibrarea.

Pentru mine a fost o experiență foarte plăcută în special pentru că a fost primul meu proiect care a implicat parte de hardware; în sensul acesta, cred că am făcut o alegere foarte bună, pentru că am avut de a face și cu ceva electronică raw (circuitul de generare de numere random), nu doar punerea cap la cap a unor integrate.

Download

Arhiva conține următoarele:

  • README care reia cele menționate deja pe acest wiki
  • schema (/schema), în format .sch și .png
  • firmware-ul (/code/firmware), care conține Makefile (inclusiv cu regulă flash pentru uploadarea pe μC) și codul pentru generarea octeților random, care urmează să fie trecuți, pe mașina la care se conectează, prin algoritmul de DRBG
  • aws-ul (/code/aws), împreună cu dependințele lui externe (http-parser și mbedtls)
  • teste (/code/test) pe care le-am făcut variantei finale a generatorului de octeți random; în particular, directorul /code/test/statistics conține frecvențele de apariție a fiecărui număr din range-ul 0-255 pe capturi de 100KB, respectiv 1MB

Toate de mai sus se află în versiunea finală. Pentru versiuni intermediare, pentru că nu am păstrat un Changelog, se pot consulta commit-urile din repo-ul de GitLab (folderul project/) unde am ținut toate resursele.

Jurnal

  • [22.04]: Am cercetat subiectul și am creat pagina de wiki.
  • [03.05]: Am adăugat lista de piese adiționale
  • [13.05]: Am adăugat schema electrică
  • [20.05]: Am realizat circuitul descris în secțiunea hardware design. Dintr-un motiv pe care nu îl știu nici eu, am vrut să îl fac cât mai compact așa că a fost un chin să trag firele mai apoi, pe spate.
  • [23.05]: Am făcut primul test al circuitului cu osciloscopul și am ajuns la concluzia că funcționează, în sensul că ambele inversoare amplifică, însă outputul este undeva în range-ul 2.5V - 3.5V cu spike-uri foarte rare. Am decis să alimentez la 12V.
  • [24.05]: Am lipit componentele necesare alimentării la 12V și am testat din nou cu osciloscopul. Se vedeau mult mai clar spike-uri către 5V. Am decis că rămâne așa și am trecut la a scrie cod.
  • [25.05]: Am încercat mai multe variante de a genera biți de 0 și 1 din numerele între 0 și 1023 pe care le luam de la ADC. Variantele încercate le-am descris în secțiunea software design. Această etapă mi-a luat aproape o zi întreagă pentru că mi-a fost foarte greu să rămân asupra unei variante; toate aveau avantaje și dezavantaje (bias vs thoughput, etc). Am rămas totuși asupra celei care folosește paritatea numărului de măsurători între 2 spike-uri consecutive.
  • [26.05]: Am adaptat implementarea de la tema 5 de la SO a web serverului pentru a obține sample-uri de bytes random direct în browser (sau terminal folosind curl). Apoi am petrecut ceva timp să înțeleg cum funcționează componenta ctr_drbg din biblioteca mbedtls. După ce am înțeles, am scris codul care o folosește foarte rapid. O descriere a pipeline-ului prin care trec octeții random se află tot în secțiunea software design. M-am jucat ceva timp cu web serverul să mă asigur că funcționează corect apoi am scris documentația (acest wiki și readme-urile din arhiva de mai sus).

Resurse

1) Pseudorandom (Deterministic) Number Generators
2) sămânța
5) avalanche noise
6) avalanche breakdown
7) John von Neumann's debiasing scheme
pm/prj2016/amusat/hwrng.txt · Last modified: 2021/04/14 17:07 (external edit)
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0