================================================================================ ---------------------[ BFi13-dev - file 14 - 20/08/2004 ]----------------------- ================================================================================ -[ DiSCLAiMER ]----------------------------------------------------------------- Tutto il materiale contenuto in BFi ha fini esclusivamente informativi ed educativi. Gli autori di BFi non si riterranno in alcun modo responsabili per danni perpetrati a cose o persone causati dall'uso di codice, programmi, informazioni, tecniche contenuti all'interno della rivista. BFi e' libero e autonomo mezzo di espressione; come noi autori siamo liberi di scrivere BFi, tu sei libero di continuare a leggere oppure di fermarti qui. Pertanto, se ti ritieni offeso dai temi trattati e/o dal modo in cui lo sono, * interrompi immediatamente la lettura e cancella questi file dal tuo computer * . Proseguendo tu, lettore, ti assumi ogni genere di responsabilita` per l'uso che farai delle informazioni contenute in BFi. Si vieta il posting di BFi in newsgroup e la diffusione di *parti* della rivista: distribuite BFi nella sua forma integrale ed originale. -------------------------------------------------------------------------------- -[ HACKiNG ]-------------------------------------------------------------------- ---[ DiSPERSiNG iNF0RMATi0N F0R FUN AND PR0FiT ]-------------------------------- -----[ valvoline http://www.s0ftpj.org ]----------------- Dispersing Information for Fun and Profits valvoline@s0ftpj.org 1. Intro L'informazione digitale e' bella perche' manipolabile. E' possibile duplicare, modificare, leggere ed alterare la sorgente di informazione ottenendo infiniti nuovi ceppi indistinguibili dall'originale perche' essi stessi originali, in quanto frutto di successive - possibilmente ripetute - manipolazioni. Ogni flusso di dati che transita attraverso gli infiniti canali informatici puo' essere catturato, analizzato, elaborato ed alterato da chiunque con un minimo di interesse, strumenti e capacita'. Invero, ritengo che sia questa la vera essenza dell'hacking del digitale, il reale potere che si cela dietro ogni kilobyte di dati che vediamo scorrere davanti ai nostri occhi. Esistono svariati metodi per fronteggiare il dilemma dell'originale digitale ed innumerevoli sono le tecniche di hashing e checksum con cui potere equipaggiare i propri documenti, per evitare o, quantomeno, notificare e rendere evidenti ai possibili utenti le modifiche - autorizzate o meno - intervenute sul file in esame. In quest'ambito, nel contesto dell'informazione digitale e della sua facile fruibilita' ed alterabilita', si inserisce l'argomento di quest'articolo. La connettivita' globale e l'uso onnipresente di internet anche per comprare la pizza, ha reso ogni computer affacciato al mondo un potenziale nodo di una gigantesca matrice difficilmente tracciabile, visualizzabile ed identificabile. Il continuo venir meno dei diritti informatici e di espressione, inoltre, e' un fatto innegabile oltre che determinante al fine di delineare un quadro introduttivo di quello che sara' il nostro campo di azione nelle prossime pagine. Proviamo a concentrarci e pensiamo alla nostra situazione attuale: hai internet ma non puoi utilizzarla troppo, perche' se raccogli roba catalogata come illegale sei suscettibile di sanzione. Ti danno spazio gratuito ma non puoi pubblicarci, perche' devi prima fare notifica all'ente per la tutela e regolamentazione degli spazi internet. E' tutto un continuo mettere alla prova il livello di sopportazione dell'uomo medio: ti do' la donna, ma non puoi toccarla. Ti do' le armi, ma non puoi usarle. Sei libero, ma controllato. Puoi guardare, ma non puoi mangiare. Fortunatamente non e' tutto cosi' nero. La comunita' di cui il villaggio globale e' costituito, ha pensato - e qualcuno li ha pure implementati - metodi e tecniche per fronteggiare questo disastro: sistemi di pubblicazione anonima non deterministici e reti p2p che ricostruiscono in maniera anonima il sistema di instradamento IP. Argomenti che, tuttavia, esulano dallo scopo di quest'articolo. Punto saliente e fulcro, se volete, di quest'articolo e' un altro. Quello su cui concentreremo la nostra attenzione e' il concetto di dispersione dell'informazione. Disperdere l'informazione in piccole porzioni per poi poterla ricostruire quando e come serve. E' come se giocassimo ad un vecchio gioco che si faceva dalle mie parti: si disegnava su un foglio di carta uno schizzo di fantasia e si suddivideva il foglio in tanti pezzettini che venivano dati ai giocatori. Se la suddivisione era stata fatta bene, nessuno conosceva il disegno originale: soltanto il disegnatore ne era a conoscenza. Ognuno provava a trovare una sua soluzione al disegno in base al pezzo fornitogli ed alle indicazioni via via date dal disegnatore. Se non si riusciva a trovare la soluzione, si poteva chiedere ad uno o piu' giocatori di aggregarsi in comunita' e condividere i propri frammenti del disegno originale, cosi' da avere un quadro piu' ampio su cui fare illazioni. Vinceva chi riusciva a trovare la soluzione con il minor numero di raggruppamenti. Forse l'esempio non e' completamente pertinente con gli obiettivi di quest'articolo, ma rende l'idea di quello che faremo nelle pagine a seguire: - Dispersing. Dividere l'informazione originale in frammenti, in maniera che nessuno di essi sia portatore di informazioni sensibili rispetto al documento originale. - Rebuild. Ricostruire l'informazione originale, utilizzando esattamente n frammenti scelti a caso tra gli m generati, con n < m . Successivamente, proveremo a trovare uno spazio applicativo per quanto ideato ed implementato, cercando di trovare possibili soluzioni ai problemi sopra esposti. Da qualche parte dovevo anche mettere i disclaimer e tutta l'altra roba che si mette in ogni articolo di BFi che si rispetti; visto l'evento speciale - e visto che la carta costa, l'entropia terrestre aumenta e la minestra che si scrive e' sempre la stessa - ho pensato di risparmiare il tutto proiettandovi direttamente nel vivo del problema. Buona lettura! 2. State of the Art Chiunque abbia un minimo di dimestichezza con internet e le sue applicazioni si sara' trovato ad utilizzare un sistema di file sharing e/o distribuzione di contenuti. OpenNap, EMule, BitTorrent. Non fa differenza. L'idea centrale e' sempre la stessa: mettere al centro di tutto l'utente che condivide contenuti ed utilizzare questo fattore discriminante come sistema di crediti per agevolare o meno utenti piu' "altruisti". Dal momento che i sistemi di comunicazione piu' comuni sono sistemi asincroni che sfavoriscono l'upstream dell'utente, quasi tutti i sistemi che fanno file-sharing e distribuzione di contenuti mettono a disposizione un sistema per parallelizzare il download di un determinato contenuto. In questo modo e' possibile segmentare un download in piu' sorgenti, velocizzando il tutto. L'approccio utilizzato e' quello di segmentare in offsets assoluti l'intero documento ed instaurare una sessione di download diversa per ogni chunk (pezzetto) partizionato. E' ovvio, comunque, che ogni partecipante deve avere a disposizione l'intero documento per essere in grado di esaudire una richiesta di segmentazione effettuata da un possibile interessato. L'informazione, quindi, e' brutalmente duplicata su n diversi siti. Dal momento che il costo a gigabyte di archiviazione scende di giorno in giorno, non e' questo il reale problema che ci troviamo ad affrontare. Quello che, invero, risulta essere un grave problema e' invece la distribuzione di contenuti soggetti a copyright o comunque con un alto grado di confidenzialita'. Se vogliamo distribuire un documento rippato la notte prima da un grosso server di una grossa multinazionale, dobbiamo metterlo online per intero attraverso un sistema di file-sharing. L'alternativa sarebbe quella di distribuire il contenuto attraverso un sistema di pubblicazione anonima, stile freenet, ma dal momento che l'organizzazione del sistema stesso e' caotica, nessuno ci assicura che il contenuto sara' disponibile sempre e comunque per la comunita'. Il prezzo dell'anonimato e dell'arrangiamento caotico in maniera raw si paga in qualche modo. Un'altra alternativa sarebbe quella di distribuire il contenuto attraverso canali privati sotto forma di documenti crittografati. Anche questo non e' accettabile per una pubblicazione di massa del contenuto. Bisogna supporre, infatti, che si abbia a disposizione un sistema noto di crittografia pubblica. Inoltre i contenuti non sarebbero accessibili in maniera totalmente pubblica, in quanto la distribuzione avverrebbe per vie private e confidenziali. Proviamo a riassumere un quadro di quello che si puo' fare allo stato attuale delle cose: - pubblichi il documento su un sistema di file-sharing ma lo devi mettere completamente a disposizione. Questo si ripercuote su due aspetti dello stesso problema: dispendio di spazio ed esposizione diretta ad eventuali rischi sui contenuti messi a disposizione; - pubblichi il documento su un sistema di pubblicazione anonima quale freenet, ma devi essere conscio che il tuo contenuto non e' soggetto a nessuna gerarchia e regola, quindi non e' assicurata la sua persistenza in istanti diversi sullo stesso segmento di rete. Non sarebbe bello riuscire a combinare le potenzialita' e l'immediatezza di un sistema di modello di file-sharing classico, con la potenza dell'anonimato messa a disposizione da un sistema di pubblicaziona anonima? Modelli di file-sharing anonimo stanno cominciando ad affacciarsi timidamente sul mercato, anche se il loro uso e' ancora confinato ad utenze amatoriali - o psicopatiche -. Mute e' un esempio eclatante di quest'ultimo concetto. Utilizza un sistema di arrangiamento caotico dei nodi ed attraverso un complesso sistema di routing multi-pathing - che ricorda molto la strategia adottata dalle formiche per procurarsi il cibo e tornare alla tana - riesce a distribuire informazioni e messaggi preservando l'anonimato dell'utente. Un sistema di overlay network, insomma, che tenta di risolvere il problema dell'addressing IP pubblico, sostituendogli sopra un sistema di identificativi pseudo-casuali e non riconducibili ad un IP reale. Ma se qualcuno ascolta la vostra connessione? Il contenuto ed il documento continuano a viaggiare in chiaro verso la vostra macchina e le connessioni punto-punto sono comunque ricostruibili da un attento ascoltatore in the middle. E' anche vero che potete metterci sopra un crypto-layer - quale SSL, come d'altronte fa MUTE - che vi ponga al sicuro da orecchie e sguardi indiscreti. Tuttavia, e' leggittimo farsi alcune domande: l'overhead di un'operazione di questo tipo e' davvero proponibile? Facciamo un attimo mente locale: connessioni tramite multi-hopping caotico, un layer che si sostituisce all'IP Addressing per la consegna dei messaggi, arrangiamento non gerarchico e - ciliegina sulla torta - un layer crittografico che mette in sicurezza quello che trasferite. Anche se le risorse macchina sono sempre meno costose, non credo che sia molto corretto farne un abuso! Ricadremmo sulla stessa sponda dei sostenitori dei meta-linguaggi e della programmazione mega-complessa-oob^2! Non vogliatemene, ma non sono un sostenitore di queste tecnologie! ;) Se riuscissimo a spezzare l'informazione che desideriamo pubblicare sul nostro sistema di file sharing, se fossimo in grado di assicurare che NESSUNO in possesso di un frammento sia in grado di fare elucubrazioni sul suo contenuto e se a questo aggiungiamo che sarebbe desiderabile avere un modo per ricostruire l'informazione utilizzando soltanto un subset random dei frammenti generati utilizzando una chiave di ricostruzione unica, saremmo ad un punto di svolta del nostro problema! Basterebbe trovare un modo ottimale per dividere i frammenti tra i partecipanti! Le cose, come al solito, non sono mai cosi' immediate e semplici come sembrano, motivo per cui e' meglio fare un piccolo compendio su quello che sono le tecniche che piu' si avvicinano alle nostre esigenze, per poi far vedere la soluzione che mi sento di proporre per risolvere quest'annoso problema. 2.1 IDA: Information Dispersal Alghorithm All'inizio degli anni '90, Michael Rabin propose IDA (Information Dispersal Algorithm), come una potenziale tecnica per raggiungere gli obiettivi di sicurezza ed efficenza di cui sopra, utilizzando un livello di ridondanza molto piu' piccolo e mantenendo su di uno specifico sito informazioni soltanto parziali. L'idea principale e' di usare una chiave segreta per disperdere l'informazione di un documento F in n pezzi distribuiti in n siti (hard disks e/o macchine remote). Il documento F puo' quindi essere ricostruito attraverso m pezzi generici, con m <= n . Il punto chiave e' che ogni pezzo e' di grandezza |F| / m , dove |F| e' la grandezza in bytes del documento F . Conseguentemente, il numero totale di bytes e' (n/m) * |F|. Dal momento che possiamo scegliere n in maniera che (n/m) ~= 1 , il metodo e', in termini di spazio occupato dalle singole componenti, spazialmente efficiente. In questa sezione, spiegheremo come funziona IDA e come e' stato implementato, focalizzando la nostra attenzione sulle operazioni necessarie per un corretto espletamento dell'algoritmo. Sia F il file originale (informazione), che vogliamo disperdere. Il file originale, F, puo' essere visto come una sequenza (o uno stream) di dati nella forma: F=b_1 b_2 b_3 b_4,... dove ogni b_i in questo stream, puo' essere visto come un intero. In generale, per disperdere questo stream, scegliamo un set di n vettori o chiavi segrete ( v_1, v_2, ..., v_n ) oguna di lunghezza m . Le chiavi devono soddisfare certe condizioni di indipendenza lineare. Sia A_nm l'array dove le righe sono i vettori selezionati. A rappresenta una mappatura da uno spazio m-dimensionale a uno spazio n-dimensionale; in altre parole, da una sequenza di m elementi ad un'altra sequenza di n elementi. Per disperdere il file F, semplicemente mapperemo ogni sequenza di m elementi da F in una nuova sequenza di n elementi usando la trasformazione A. Ogni elemento della sequenza risultante e' inviato ad un differente spazio di salvataggio (le macchine, che nel nostro caso, partecipano al network) e mantenuto li'. Quindi inviamo ogni elemento m di F, ad ognuno degli n siti. In definitiva, per disperdere l'intero file F, necessiteremo di inviare |F| / m elementi ad oguno degli n siti. Adesso supponiamo che vogliamo ricostruire il file originale F dai chunks dispersi come descritto sopra. Quest'operazione e' svolta leggendo ognuno degli m chunks; se sono disponibili meno di m pezzi, l'operazione non e' possibile. Al contrario, se sono disponibili piu' di m pezzi, allora una qualunque combinazione degli m pezzi e' adeguata al nostro scopo. I chunks disponibili siano: s_1, s_2, s_3, ..., s_m. Sia, inoltre, B_mn l'array le cui righe sono ( V_{s_1}, V_{s_2}, ..., V_{s_m} ). B, mappa le sequenze di m elementi di F in sequenze di n elementi, le quali grazie alla dispersione effettuata sopra, sono mantenute in n siti differenti (s_1, s_2, ..., s_n). Per ricostruire i primi elementi di F, abbiamo bisogno di raccogliere il primo elemento da oguno degli m luoghi di salvataggio. Una volta ottenuta quest'informazione, effettueremo la corrispondente operazione di trasformazione inversa: T=B^{-1}. Da notare, che se le chiavi sono state scelte in maniera appropriata, almeno un inverso e' garantito esistere. 2.1.2 Disperdiamo l'informazione La dispersione e' semplicemente una A-Trasformazione (n X m). Assumiamo per semplicita' n = 4 ed m = 2. Nella nostra spiegazione semplificativa, ogni elemento dello stream di F e' un byte (8bit), diviso in due pezzettini (4bit ciascuno). | (V_0)^T | | a_00 a_01 | | (V_1)^T | | a_10 a_11 | A= | (V_2)^T | = | a_20 a_21 | | (V_3)^T | | a_30 a_31 | Dove, V_i e' l'i-esima chiave-vettore ed oguno dei a_{ij} e' un numero esadecimale. Siano (b_0 b_1)^T le due cifre decimali (un totale di 8bit). Per disperdere questo pezzo di informazione. usiamo la trasformazione A, come segue: | c_0 | | a_00 a_01 | | c_1 | | a_10 a_11 | | b_0 | | c_2 | = | a_20 a_21 | X | | | c_3 | | a_30 a_31 | | b_1 | Dove, c_i e' la cifra esadecimale, inviata all'i-esimo luogo di salvataggio. 2.1.3 Ricostruiamo l'informazione Siano i chunks s_1 ed s_2 non alterati. La trasformazione di ricostruzione e' in questo caso, una matrice (2 X 2)^T, del tipo: | (V^T)s_1 | | t_00 t_01 | | | = | | | (V^T)s_2 | | t_10 t_11 | Sia (c_0 c_1)^T la rappresentazione di due cifre esadecimali (un totale di 8bit) estratta dai due chunks di cui sopra. Per ricostruire il byte originale dell'informazione, usiamo una trasformazione, T, come segue: | b_0 | | t_00 t_01 | | c_0 | | | = | | X | | | b_1 | | t_10 t_11 | | c_1 | 3. L'aritmetica "alternativa" - Aritmetica dei Polinomi Irriducibili Tutte le operazioni (formalmente, addizione e moltiplicazione) richieste per IDA devono essere riportate usando l'aritmetica dei polinomi irriducibili - IPA -, dove gli interi sono visti come polinomi in un campo finito Z_p. Ad esempio, usando p = 2 - una scelta ovvia per applicazioni digitali - gli interi sono rappresentati come polinomi con coefficenti binari. Per esempio, le cifre esadecimali da 0 a 15, rappresentate in IPA sono le seguenti: 0000=0 0001=1 0010=x 0011=x+1 0100=x^2 0101=x^2+1 0110=x^2+x 0111=x^2+x+1 1000=x^3 1001=x^3+1 1010=x^3+x 1011=x^3+x+1 1100=x^3+x^2 1101=x^3+x^2+1 1110=x^3+x^2+x 1111=x^3+x^2+x+1 In un IPA con 16 elementi,tutte le operazioni sono eseguite modulo un polinomio irriducibile di 4^o grado che non puo' essere diviso da nessun polinmio di 3^o grado o minore). Per esempio, x^4+x+1 e' irriducibile di 4^o grado. 3.2 IPA - Addizione Per sommare due interi, sommiamo i loro polinomi corrispondenti. Questo e' fatto sommando (modulo 2) i coefficenti delle potenze corrispondenti. La somma seguente e' un esempio di somma fatta in IPA: (5 + 6) = (x^2+1) + (x^2+x) = (x+1) = 3 E' immediato notare che l'addizione e', semplicemente, un OR-esclusivo (XOR) bit-a-bit delle rappresentazioni binarie degli interi; proprio su questa fatto, e dall'osservazione che i calcolatori riescono a gestire molto velocemente le operazioni binarie, si basa la nostra libreria. 3.3 - IPA - Moltiplicazione La moltiplicazione e' leggermente piu' complessa. Per moltiplicare due interi, moltiplichiamo i loro polinomi corrispondenti. Se il polinomio risultante e' di grado minore di quello del polinomio irriducibile (4 nel nostro caso), allora abbiamo trovato la rappresentazione polinomiale del risultato dell'operazione. Altrimenti, dobbiamo prendere il resto. Ancora una volta, tutte le addizioni e moltiplicazioni sono eseguite modulo 2. Il seguente esempio mostra una moltiplicazione fatta usando (x^4+x+1) come polinomio irriducibile: (3 * 10)= =(x+1)(x^3+x) =(x^4+x^3+x^2+x) mod (x^4+x+1) =(x^3+x^2+1) =13 Il metodo piu' semplice, per una implementazione software di quanto spiegato, potrebbe essere una tabella di controllo (lookup) in cui andare a trovare i risultati/valori. Una implementazione piu' efficente, scalabile ed elegante, prediligera', invece, l'uso di shift e xor per fare quanto detto sopra. Cerchiamo di capire meglio quanto spiegato. Sia X = x_3 x_2 x_1 x_0 ed Y = y_3 y_2 y_1 y_0 la rappresentazione binaria di due cifre esadecimali da essere moltiplicate, dove il risultato e' Z=z_3 z_2 z_1 z_0 La moltiplicazione di numeri binari si riduce a shift e somme successive; nel nostro caso di sopra, sono richiesti esattamente 4 passi di shift/somme (uno per ogni bit di Y). In ognuno dei passi, il valore accumulato: W = w_3 w_2 w_1 w_0 inizialmente posto a 0000, e' shiftato a sinistra di un bit e se il valore corrispondente di Y e' 1, X e' sommato al valore accumulato ed il risultato e' passato al prossimo passo. Il numero risultante, non puo' essere usato direttamente dal momento che la rappresentazione polinomiale di questo risultato, potrebbe essere di 4^o grado (o piu' alto); ed il resto di questo risultato dovrebbe essere computato usando x^4+x+1 (siamo nell'esempio di prima). Calcolare questo resto, significa che necessitiamo di eseguire sottrazioni successive. Fortunatamente, la sottrazione (modulo 2) e' uguale alla somma (modulo 2). Queste sottrazioni possono essere svolte, dentro ogni passo di quelli descritti sopra (shift/somma); quindi, non hanno necessita' di essere accumulati fino alla fine della moltiplicazione. Infine, osserviamo che al piu' e' richiesta una sottrazione dentro ogni passo della moltiplicazione, dal momento che in ogni passo il grado del risultato accumulato non puo' essere incrementato piu' di uno, e conseguentemente, sottraendo il polinomio irriducibile x^4+x+1 solo una volta, garantiamo che il risultato accumulato rimarra' di terzo grado (o meno). Uno scheletro dell'algoritmo, da implementare - non preoccupatevi, piu' di tanto, nel codice sorgente e' gia' tutto implementato sotto forma di librerie standard ANSI C! - su questa base, puo' essere riassunto in forma nel modo seguente: w4 = 0; w3 = 0; w2 = 0; w1 = 0; w0 = 0; for(i=3; i >= 0; i--) { w4 = w3; w3 = w2 XOR (x_3 * y_i); w2 = w1 XOR (x_2 * y_i); w1 = w0 XOR (x_1 * y_i); w3 = (x_0 * y_i); w1 = w1 XOR w4; w0 = w0 XOR w4; } Questo algoritmo rappresenta la base di quello implementato nella nostra libreria. In realta' quello che viene fatto non e' un ciclo di 4 passi di shift/add, che abbiamo visto in via semplificativa, ma in maniera piu' generale viene ciclata con un for l'intera struttura di messaggio da splittare, vista come array, eseguento passo per passo le operazioni di XOR e shift con maschere per sezione, usando delle tabelle di lookup per la corrispondenza polinomio-elemento, in maniera da velocizzare le operazioni. 4. Mettiamo In pratica quanto imparato 4.1 La libreria di dispersione Allo stato attuale delle cose abbiamo un po' di conti esoterici che dimostrano come splittare un gruppo di bit ed ottenerne degli altri, in numero maggiore, che contengono come in un subset tutti gli altri. Proviamo a vedere di tirare fuori del codice funzionante, per dare in pasto un file e tirare fuori n-chunks che contengono in maniera dispersa l'informazione originale. /* ida splitting routine. we pass filename to split, num of min packets, num of extra packets, and field for operations... */ unsigned long ida_start(char *filename, int num_msg_pkts, int num_extra_pkts, int field_size) { /* local variables skipped here... see, instead, the source code */ gettimeofday(&tp, &tzp); srand48(tp.tv_usec); fseek(fp, 0, SEEK_END); fsize = ftell(fp); //get the num of segments in which splits the original files num_segments=get_segments(field_size, num_msg_pkts, fsize); //initialize the parameters and store keys into sp_struct retval=init_params(num_msg_pkts,num_extra_pkts,field_size, num_segments, &sp_struct); share_struct.sp = &sp_struct; msize=num_msg_pkts*field_size*num_segments*sizeof(u_int32); share_struct.message = (u_int32 *) malloc(msize); esize=num_extra_pkts*field_size*num_segments*sizeof(u_int32); share_struct.shares = (u_int32 *) malloc(esize); memset(share_struct.shares, 0, esize); memset(share_struct.message, 0, msize); fseek(fp, 0, SEEK_SET); //put the contents into the right place... for(i = 0; i < (fsize); i++) { share_struct.message[i] = getc(fp); } fclose(fp); //let's rock'n'roll!...make the shares and store it! make_shares(&share_struct); for(i = 0,j=0; i < (esize/sizeof(u_int32)); j++,i+=(sp_struct.pkt_len/sizeof(u_int32))) { if((fp=fopen(itoa(j), "wb+"))==NULL) { perror("ida_start"); return 1; } //make an SHA1 of the current chunk md = vlv_digest((char *)&(share_struct.shares[i]), sp_struct.pkt_len/sizeof(u_int32)); //write all and finish! fwrite(VERSION, 4, 1, fp); //4bytes fwrite(md, 32, 1, fp); //32bytes fputc(j, fp); //1byte fputc(sp_struct.mpkt_count, fp); //1byte fputc(sp_struct.rpkt_count, fp); //1byte fputc(field_size, fp); //1byte fwrite(&fsize, sizeof(unsigned long), 1, fp); //4byte*/ for(k=0;k<(sp_struct.pkt_len/sizeof(u_int32));k++) { fputc((share_struct.shares[i+k]),fp); } fclose(fp); } //some clean'ups free(share_struct.message); free(share_struct.shares); return fsize; } Allo stesso modo dovremo ripristinare l'informazione originale dati in input un numero sufficiente di chunks per la ricostruzione. Dovremo quindi creare una funzione che, raggiunto un numero minimo di pezzi diversi sia in grado di inizializzare e procedere alla ricostruzione del file originale. /* try to rebuild a file, using the parameters and hash passed as argument in HROOT,HUROOT. */ int ida_rebuild(char *filename, hashstruct *HROOT, hashstruct *HUROOT) { /* local variables skipped here... see, instead, the source code */ gettimeofday(&tp, &tzp); srand48(tp.tv_usec); if(HROOT == NULL) return -5; t2=HROOT; chunk=t2->hash; if(chunk == NULL) { perror("ida_rebuild2"); return 1; } if((fp=fopen(chunk, "rb"))==NULL) { perror("ida_rebuild2"); return 1; } //skip the sha1 and get the parameters... fseek(fp, 36, SEEK_SET); rand_share = fgetc(fp); num_msg_pkts = fgetc(fp); num_extra_pkts = fgetc(fp); field_size = fgetc(fp); fread(&fsize, sizeof(unsigned long), 1, fp); num_segments = get_segments(field_size,num_msg_pkts, fsize); //initialize the parameters/key required for rebuild retval = init_params(num_msg_pkts,num_extra_pkts, field_size,num_segments, &sp_struct); share_struct.sp = &sp_struct; msize=num_msg_pkts*field_size*num_segments *sizeof(u_int32); esize=num_extra_pkts*field_size*num_segments *sizeof(u_int32); //let's try to allocate enough space, for rebuild share_struct.message = (u_int32 *) malloc(msize); memset(share_struct.message, 0, msize); share_struct.shares = (u_int32 *) malloc(esize); memset(share_struct.shares, 0, esize); memset(&rebuild_struct, 0, sizeof(rebuild_struct)); rebuild_struct.rec_table = (u_int32 *) calloc(num_extra_pkts, sizeof(u_int32)); rebuild_struct.rec_mark = (u8 *) calloc(num_extra_pkts, sizeof(u8)); rebuild_struct.pre_block=(u_int32 *)calloc(esize, 1); rebuild_struct.post_block=(u_int32 *)calloc(msize, 1); rebuild_struct.sp = &sp_struct; /*scroll up the chunks list for rebuild...stop when we get enough chunks...*/ for(t2=HROOT;t2!=NULL && !rebuild_struct.done; t2=t2->next) { chunk=t2->hash; fseek(fp, 36, SEEK_SET); rand_share = fgetc(fp); share_off = rand_share * sp_struct.pkt_len; fseek(fp, 44, SEEK_SET); for(k=0;k<(sp_struct.pkt_len/sizeof(u_int32));k++) { share_struct.shares[(share_off/sizeof(u_int32)) +k]=fgetc(fp); } fclose(fp); /*put chunk into share_struct. share_in stop itself when enough chunks are reached*/ share_in(rand_share, (u_int32 *) &(share_struct.shares[share_off / 4]), sp_struct.pkt_len, &rebuild_struct); } /*if we reach this point, we've reassembled successfully the original file. so, we can write it*/ fp = fopen(filename, "wb+"); for(i = 0; i < fsize; i++) { putc(rebuild_struct.post_block[i], fp); } fclose(fp); return 0; } A questo punto le due funzioni base sono disponibili. In realta', mancano molte funzioni di appoggio che troverete nelle librerie sorgenti -- il link e' disponibile alla fine del documento tra le referenze -- Ho deciso di alleggerire un po' il codice per renderlo piu' scorrevole e di facile comprensione. In fondo, le librerie mancanti sono solo routines preposte a particolari scopi. E' importante, invece, sottolineare che a questo punto abbiamo due funzioni: una per disperdere un file, ed una per ricostruire un file dati un numero sufficiente di chunks per la ricostruzione. Il gioco, a questo punto, e' fatto! Bastera' dividere in maniera random ad n-partecipanti i chunks generati e richiederli quando necessario per la ricostruzione. In fondo, come ci assicura Rabin con la sua dimostrazione formale, i chunks non rilasciano informazioni sensibili circa la natura o il contenuto del file originale. Sara' quindi possibile rilasciarli ad n-partecipanti sconosciuti senza per questo compromettere la sicurezza e l'anonimato del documento da noi distribuito. Inoltre, l'invio dei contenuti potra' essere alleggerito da eventuali layer crittografici, indispensabili altrimenti. Ogni chunk, insomma, e' un'informazione pubblica che puo' essere distribuita senza ulteriori problemi ai partecipanti. 5. ANNET - ANonymous NETwork System (IDA Based) Proviamo a pensare ad una implementazione pratica di un sistema distribuito di condivisione file, basato su IDA, che permette il recupero e la pubblicazione di documenti attraverso l'algoritmo IDA in tempo reale, in un network a grandezza variabile e dimensionalmente scalabile. Per raggiungere il nostro scopo, sara' necessario focalizzare la nostra attenzione su alcuni obiettivi: - Affidabilita': i dati pubblicati, devono poter rimanere attivi, anche sotto particolari forme di attacchi e/o di errori di sistema, assicurando all'utente che li ha pubblicati il corretto recupero dei dati immessi. - Efficienza: e' necessario sviluppare un protocollo, ed un sistema di funzionamento, volto ad offrire efficienza a tutti i partecipanti. Sono da evitare onerosi sistemi di scambio messaggi basati su connessioni TCP/IP, e quant'altro pregiudichi l'efficienza, la velocita' e la portabilita' del lavoro. - Sicurezza: nessun partecipante, che mette a disposizione una porzione delle sue risorse, deve essere in grado di leggere documenti a lui non indirizzati. Non deve, inoltre, essere in grado di capire quali porzioni di quali file mantiente/conserva sul proprio spazio condiviso. In questa maniera, si ottiene un duplice risultato: da un lato il cliente che mette a disposizione delle risorse non sara' in grado di operare una censura sui chunks che conserva, rendendo quindi instabile il sistema; dall'altro lato, sara' molto piu' arduo per un potenziale attaccante capire la qualita' e la quantita' delle informazioni in suo possesso, nell'eventualita' che una macchina venisse violata. - Persistenza: colui che pubblica il documento, determina la vita dello stesso. Su questa base e sulle informazioni in proprio possesso, i server si adoperano per mantenere vivo il piu' possibile il documento. Soltanto quando l'utente che ha pubblicato il documento decide di eliminarlo, il documento dovra' essere eliminato, repentinamente, dal sistema. L'idea e' quella di creare un'architettura che utilizzi un particolare algoritmo/schema di cifratura e manipolazione dei files per l'invio dei dati ai clienti connessi al network; il file viene diviso in piccoli pezzettini (chunks), viene aggiunto ad essi un sistema di controllo di errore ed un sistema di ricostruzione, creando una sorta di file system sicuro ed anonimo in rete. Se alcuni singoli PC all'interno del network vengono spenti, subiscono un attacco esterno o hanno un fault hardware, gli agenti rimanenti possono organizzarsi per ricostruire il documento, utilizzando le informazioni aggiuntive conservate in ogni chunk. Il sistema di correzione degli errori sopracitato e' la chiave di volta di quanto qui illustrato; il file viene diviso in m pezzettini, ma soltanto un numero n minimo di essi, sara' davvero necessario per la ricostruzione dell'informazione originale. Usando un livello di ridondanza molto piccolo e leggero per la correzione degli errori e mantenendo solo informazioni parziali su uno specifico luogo di archiviazione, si hanno gli estremi per concretizzare, almeno in linea teorica, quello che abiamo illustrato sin qui. ANNET e' basato su un network di macchine in cui ogni server conserva pezzettini di informazione (chunks). Per la dinamicita' del servizio proposto, i dati viaggiano e si evolvono frequentemente: puo' capitare, ad esempio, che il netserver decida di ridividere (resplitting) il documento xxx poiche' sono presenti pochi chunks nel sistema e pertanto la vita stessa del documento e' messa in discussione. In questo caso il netserver si preoccupa del corretto recupero del documento, rigenerandolo e ridistribuendolo alle nuove macchine presenti in rete, assicurando quindi una presenza costante dello stesso; ricordiamo che la vita del documento puo' essere decisa esclusivamente dall'autore dello stesso. In nessun altro caso, si deve presentare la possibilita' di distruzione del file; farlo metterebbe in crisi ed in discussione l'esistenza stessa del network. Prima di tutto, definiamo come agenti utili chi pubblica, un lettore ed un dataserver; definiamo, inoltre, come agenti indispensabili i netservers. La comunicazione tra questi ultimi puo' essere implementata attraverso un layer di comunicazione che preveda lo scambio di informazioni utili quali ad esempio il proprio file system corrente, i propri utenti ed il proprio spazio globale attualmente utilizzabile, creando, in pratica, un network di netservers. Gli agenti si appoggiano al nostro livello di comunicazione, che a sua volta si appoggia al layer UDP/IP per la consegna a livello fisico dei pacchetti. Gli agenti comunicano tra loro, attravero indirizzi IP. In linea di principio, tuttavia, e' possibile frappore un ulteriore livello che renda anonimo l'indirizzamento del messaggio, usando un sistema di mixnets e/o remailing e delegando la consegna fisica del pacchetto a particolari servers preposti per la consegna anonima. Gli autori sono agenti che producono documenti e che voglio conservare in maniera sicura nel nostro network. I netservers sono agenti che si preoccupano della corretta divisione dei documenti in chunks e della puntuale consegna ai relativi dataservers. Questi ultimi sono agenti preposti al ricevimento ed alla conservazione di chunks. 6. Note legali ed anonimato Quello dell'anonimato e' un concetto molto vasto che spesso viene utilizzato senza definirne esattamente i contorni. In questo lavoro come identita' si intende quella dichiarata dall'utente (username, password) e non quella locazionale (da dove viene effettuata la richiesta, indirizzo fisico). Ci focalizzeremo nel garantire un livello di anonimato basato su username/password, lasciando l'aspetto locazionale a lavori atti a garantirlo, mixnets e/o reply forwarders. ANNET, nella mia idea attuale, non garantisce l'anonimato del richiedente e/o del pubblicante. Queste richieste, infatti, possono comunque essere tracciate a livello rete, ma garantisce l'anonimato del contenuto: ogni partecipante al network, non puo' conoscere il contenuto ne' tenere traccia dei chunks attualmente presenti nel suo sistema. Questo livello di anonimato assicura che soltanto gli utenti, eventualmente autorizzati, possano riprendere il documento e conoscerne il contenuto. Dall'altro lato, viene garantito che nessun partecipante possa filtrare i propri contenuti rispetto a propri canoni (cancellando chunks non voluti, ad esempio), rendendo cosi' instabile il network. Rimane ancora aperto, anche se in forma molto minore, il problema della localizzazione (sebbene sia possibile localizzare le richieste fisicamente, non e' ora possibile imputare una particolare richiesta ad un particolare utente, dato che il traffico e' sostenuto da un layer crittografico). Per quanto riguarda gli aspetti legali, tanto menzionati in quest'ultimo periodo, bisogna puntualizzare alcune cose. Il protocollo qui descritto, applica trasformazioni ai contenuti dei documenti, dividendoli e modificandoli secondo precisi schemi matematici. Il risultato ottenuto (i chunks), e' molto distante dall'essere una parte integra del documento originale. - I Netservers non contengono informazioni ne' contenuti sensibili a leggi sul diritto d'autore (copyright); essi mantengono solo informazioni continuamente in evoluzione, sullo stato del file system e della rete (senza mai indicare ne' conservare chi e come contiene le informazioni che si stanno cercando). - I Dataservers non contengono informazioni, ma solo trasformazioni inutilizzabili senza la corretta chiave e senza l'ausilio di un numero minimo di chunks. Soltanto l'autore (colui che pubblica) del documento puo' essere imputato riguardo la leggittimita' del contenuto dei documenti pubblicati. 7. Conclusioni Cosa ci faccio con tutto questo? In realta' l'idea fondamentale alla base di quest'articolo e' stata quella di spronare la fantasia del lettore e nel contempo renderlo conscio di alcune tecnologie che, a mio modo di vedere, prenderanno piede nei prossimi anni -- per un motivo o per un altro --. La risposta alla domanda di prima e' abbastanza semplice! Potete farci tutto e niente. E' vero che l'implementazione messa a disposizione con l'articolo tratta solo l'algoritmo base e non tutto il protocollo descritto... ma quello potete implementarlo come credete! :-) Avendo a disposizione l'idea, i concetti ed una libreria funzionante che implementi un sistema di divisione e ricostruzione sicura di un file, e' possibile implemetarvi sopra tutto quello che si desidera! La librearia e' stata scritta general-pourpouses, in maniera da venire incontro a chiunque desideri farne un'implementazione piu' particolareggiata. Il carattere generale della libreria, tuttavia, mette in risalto alcune debolezze -- dal punto di vista di efficienza computazionale -- che, comunque, possono essere risolte. Magari piu' in la', con un po' di tempo, lo faro' io stesso! :-) 8. Ringraziamenti - s0ftpj interamente, per esserci. - freaknet per l'incredibile affetto dimostrato negli anni. - vrl labs per essere stati sempre vicini anche nelle idee piu' strane! - misc. tutti gli altri che dimentico sempre! A. Referenze e links utili - M. Rabin, Efficient dispersal of information for security, load balancing, and fault tolerance, J. ACM 38, 335-348 (1989). - http://www.freenet.org - http://www.freehaven.net - http://www.s0ftpj.org ================================================================================ ------------------------------------[ EOF ]------------------------------------- ================================================================================