Copy Link
Add to Bookmark
Report
BFi numero 13 file 14
================================================================================
---------------------[ 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 <valvoline@s0ftpj.org> 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 ]-------------------------------------
================================================================================