Copy Link
Add to Bookmark
Report

OndaQuadra 0A

eZine's profile picture
Published in 
OndaQuadra
 · 26 Apr 2019

  

__ ___ __ _ __ ___ __ _
| _/ _ \_ | _ __ __| | __ _| _/ _ \_ |_ _ __ _ __| |_ __ __ _
| | | | | || '_ \ / _` |/ _` | | | | | | | | |/ _` |/ _` | '__/ _` |
| | |_| | || | | | (_| | (_| | | |_| | | |_| | (_| | (_| | | | (_| |
| |\___/| ||_| |_|\__,_|\__,_| |\__\_\ |\__,_|\__,_|\__,_|_| \__,_|
|__| |__| |__| |__|

.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [OQ20031122 0X0A]
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: http://www.ondaquadra.org - articoli@ondaquadra.org ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

"Tutto nel ciberspazio e' scandito dalla squarewave dei micro-processori
Il clock dei micro e' come un battito cardiaco elettronico..."


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [OQ20031122 0X0A]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.
:: 0x00 [L0GiN] LA JiHAD [oq~staff]
:: 0x01 [PAGiNAZER0] CRED0 [Pupi]
:: 0x02 [CRYPT0] RSA REVERSiNG [Evilcray]
:: 0x03 [NETW0RKiNG] NEL R0UTER CiSC0 [Cartesio]
:: 0x04 [C0DiNG] 0PENSSL E RSA C0DiNG [binduck]
:: 0x05 [C0DiNG] DiAL0G0 S0PRA i DUE MASSiMi FiLESYSTEM [eazy]
:: 0x06 [SECURITY] SQL-iNJECTi0N [Master^Shadow]
:: 0x07 [SECURITY] SAM CRACKiNG [h23]
:: 0x08 [APPRENDiSTA STREG0NE] C0DiCE iNVERS0: CRiTT0GRAFiA [Zer0]
:: DiGiTALE AVANZATA PARTE 7
:: 0x09 [ViSi0Ni] ESTASi Di UN BLiTTER iMPAZZiT0 [Arkanoid]
:: 0x0A [SHUTD0WN] EL0Gi0 DELLA P0VERTA' [Tritemius]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: LEGAL DISCLAIMER ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Nessuna persona dello staff di OndaQuadra si assume responsibilita'
per l'uso improprio dell'utilizzo dei testi e dei programmi presenti
nella e-zine, ne' per danni a terzi derivanti da esso.
OndaQuadra non contravviene in alcun modo alle aggiunte/modificazioni
effettuate con la legge 23 dicembre 1993, n.547 ed in particolare
agli artt. 615-quater- e 615-quinques-.
Lo scopo di OndaQuadra e' solo quello di spiegare quali sono e come
avvengono le tecniche di intrusione al fine di far comprendere come
sia possibile difendersi da esse, rendere piu' sicura la propria box e
in generale approfondire le proprie conoscenze in campo informatico.
I programmi allegati sono semplici esempi di programmazione che hanno
il solo scopo di permettere una migliore comprensione di quanto
discusso e spiegato nei testi.
Non e' soggetta peraltro agli obblighi imposti dalla legge 7 marzo 2001,
n. 62 in quanto non diffusa al pubblico con "periodicita' regolare" ex
art. 1 e pertanto non inclusa nella previsione dell'art.5 della legge
8 febbraio 1948, n.47.

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: COSTITUZIONE DELLA REPUBBLICA ITALIANA ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Diritti e doveri dei cittadini: Rapporti civili

Articolo 21
Tutti hanno diritto di manifestare liberamente il proprio pensiero
con la parola, lo scritto e ogni altro mezzo di diffusione. [...]


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x00][L0GiN] LA JiHAD [OQ]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

Sono molte le battaglie che vedono impegnati gli hacker su
tutti i fronti.
Intanto ci sono le minacce provenienti dalle celebri leggi
americane ed europee (DMCA, ECMA) sul copyright digitale;
minacce messe in atto da potenti organizzazioni (vedi la RIAA)
con le loro iniziative aggressive.
Per non dimenticare i problemi europei sul brevetto, le
iniziative della BSA, il caso SCO/Linux.

Lo sviluppo della situazione internazionale, precipitata
drammaticamente l' 11 settembre 2001 ed evoluta verso
guerre di cui vediamo l'esito tutti i giorni, ha fornito
ad alcuni l' alibi per auspicare un giro di vite sul controllo
e la riduzione dei diritti civili e della privacy, tutto
ciò spinto dall' onda emotiva scaturita dalla reale minaccia
terroristica.

E in questo scenario globale si va ad inserire la nostra
situazione, la realtà italiana; una realtà in evoluzione
verso lo sviluppo di una societa' multirazziale, multiculturale
e multireligiosa. Evoluzione (o trasformazione) che inevitabilmente
avviene tra tensioni, polemiche, drammi umanitari.

L' hacking da sempre si pone come la "religione laica" della libertà
dell' informazione, l' apertura mentale in senso generale, l' apertura
verso culture diverse; l' hacking ha anche sempre sostenuto e promosso
l' utilizzo della tecnolgia per migliorare la vita di tutti, senza mai
dimenticare l' importanza della cretività, anzi dandole un ruolo
centrale.

In questo panorama anche Ondaquadra deve evolvere ed adattarsi
a questo nuovo mondo; si trasforma ritornando alle origini,
alla semplicità. Ondaquadra diventa più agile e leggera
apre anche ai grandi temi della nostra epoca.

E allora in segno di apertura verso le culture, ci appropriamo
di un termine così mal compreso in Occidente, spesso erroneamente
tradotto come "guerra santa". Proclamiamo la nostra "Jihad", una
battaglia fatta di idee, sogni e fantasia; una battaglia per la difesa
del nostro modo di vedere la realtà.

Una battaglia condotta in "clandestinita'", condotta lontano dai
riflettori. Esercitiamo la nostra volontà di potenza come scomparsa.
Andiamo nella Foresta e combattiamo come fantasmi, invisibili come
il Vento.

La Jihad della libertà digitale è proclamata.
Jihad che combatteremo fino all' ultimo bit...


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x01][PAGINAZER0]CRED0 [Pupi]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

Credo negli amici, quelli veri, quelli che si caramellano le palle per
sei ore ascoltando tutti i tuoi sfoghi e non se ne vanno perchè sanno
che tu hai bisogno di sfogarti e concludono con un "Non ci pensare, la
vita va avanti"
e tu vorresti ammazzarli e non ti rendi conto che in
fondo hanno ragione...

Credo nell'amore, l'amore cieco, l'amore contro cui vai a sbattere la
testa mille e mille volte e nonostante tutte le sofferenze sei sempre
pronto a ripartire, prendere la rincorsa e tirare ancora un'altra
testata in quel muro che è, l'amore...

Credo nei momenti solitari di cui ogni uomo ha bisogno, durante i quali
non riesci a lasciarti in pace e mille pensieri torturano la tua mente
fino a farti impazzire, ti assale la malinconia e finisci sempre col
ritrovarti in lacrime per qualcosa che non hai ben capito cos'è...

Credo nelle cazzate che si fanno solo quando si è ragazzi, e ti
dici "Tanto non mi possono fare niente, sono minorenne, quando divento
grande poi smetto"
e una volta grande continui a fare cazzate perchè,
nonostante tutto, continui a credere in tutto questo...

Credo che ognuno dovrebbe lottare per ciò in cui crede e credo che per
quanto possa apparire solitaria e senza speranza, ogni battaglia, se
motivata da veri ideali e non da futili affari, merita di essere
combattuta...

Credo che i miliardi di dollari spesi in armamenti per assoggettare il
mondo al proprio volere non valgano neppure mezza di tutte le vite
finite nei grandi mattatoi delle guerre...

Credo che il mondo non abbia colori e che chiunque, in ogni parte del
globo, possa essere considerato un proprio fratello...

Credo che il luogo di nascita sia una mera e stupida casualità, in
quanto credo di essere, prima di tutto, figlio del mondo...

Credo in chi si sforza davvero per rendere migliore questo mondo e
credo che ognuno di noi dovrebbe fare quel poco che nel proprio piccolo
possiamo fare, nella speranza che un giorno qualcuno possa davvero
godere dei nostri sforzi uniti...

Credo nella razza umana, nonostante abbia mostrato negli anni quanto di
marcio nasconda fra le sue piaghe, e credo che valga la pena morire
sapendo che questo mio gesto servirà a far vivere chissà chi, chissà
dove, chissà quando...

Credo nella vita, e anche davanti alla morte pianterò alberi in modo
che la signora delle tenebre non abbia mai la meglio sulla nostra
razza...

Credo nelle canzoni che si trovano proprio su quel cd che non doveva
essere nello stereo in quel momento perchè ti fanno pensare ancora di
più a cose a cui vorresti non pensare e sono proprio quelle canzoni che
un giorno ti strapperanno un sorriso quando le riascolterai e penserai
a ciò che fu...

Credo nella ragazza che hai amato per tutta la vita e con cui sei
stato, ti sei lasciato, sei tornato, ti sei lasciato di nuovo e via
dicendo e adesso è sposata con lo stupido di turno e quando ti vede
sorride con dolore e abbassa gli occhi e ti rendi conto che hai
lasciato un segno indelebile nel cuore di chi non ti ha voluto...

Credo che sia inutile battersi per tenersi stretto qualcuno che non
desidera altro che uscire per sempre dalla tua vita e credo fermamente
nel motto "Chi non mi vuole, non mi merita" che suona tanto come una
cazzata ma che aiuta a buttare giù, talvolta, bocconi altrimenti troppo
amari da digerire...

Credo nelle frasi fatte, perchè se sono fatte e mantengono vivo il loro
significato vuol dire che sotto sotto nascondono un fondo di verità che
sta ad ognuno di noi trovare...

Credo nelle cose dette e non dette, che non significano niente, ma che
lasciano alla tua fantasia l'interpretazione, così che tu possa lasciar
volare i pensieri, anche solo per un attimo, sulle ali dei sogni...

Credo nei sogni, credo in chi ce la mette tutta per ottenere
onestamente ciò che vuole e credo che realizzare i propri sogni valga
molto più di migliaia e migliaia di euro...

Credo in chi non si arrende, in chi ha battuto centinaia di volte il
culo a terra ed ogni volta, impassibile, si è rialzato ed è ripartito,
pronto a combattere ancora...

Credo in quei film poco famosi che solo tu ed altri due o tre vedono e
che sembrano descrivere in tutto e per tutto la storia della tua vita e
allora ti senti per un attimo un pò protagonista di uno strano "Truman
show"
...

Credo che la vita sia una e che non ci sia granché al di fuori di essa
e per questo credo che sia stupido sprecarla per rimpiangere in quei
pochi giorni guadagnati di non averli persi godendo della propria
gioventù...

Credo in cose forse più grandi di me e in cui, qualcuno potrebbe dirmi,
è stupido credere, ma credo che sia proprio credendo in queste cose che
riesco a dare un significato e un motivo alla mia esistenza...

Ma soprattutto credo che sia inutile piangere l'estinzione
degli "Hacker" e starsene lì come scemi a rimproverarsi di non essersi
dati abbastanza da fare, di non essersi mossi in tempo...

Credo che ognuno di noi nasconda in sè una personalità molto vicina a
quella del vero hacker, chi più, chi meno, e credo che sarebbe quasi
arrivata l'ora di darsi una svegliata...

Credo che il futuro sia in mano a gente come noi, a gente normale, che
lavora, si impegna, cerca di migliorarsi, di capire, ride e piange
quando serve, cerca di assaporare la propria vita per quella che è,
senza chiedere di più, ha dei veri sentimenti, non ha lasciato che il
denaro e il frenetico tam-tam dell'era moderna appiattissero il proprio
Io, ma anzi conserva una propria inestimabile, imprevedibile,
inarrestabile ed irriproducibile personalità...

Credo in gente che crede, nonostante tutto, in qualcosa...

Credo in tutti voi, chiunque siate, ovunque siate e per qualsiasi cosa
vi stiate battendo...

Credo...

###PUPI###


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x02][CRYPT0] RSA REVERSiNG [Evilcry]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

-Hiew
-SoftICE o qualunque altro debugger
-RSA Tool 2
-Calcolatrice
-Qualche tool per il decripting (tipo powmod)
URL o FTP del programma

www.google.it
Notizie sul programma

Lockless 3 CM (se volete fare pratica)
Essay

RSA overview

Da qualche tempo a questa parte, ho iniziato ad interessarmi di RSA, sia
perchè è interessante da studiare come sistema di crittografia, sia
perchè mi è capitato in più di una protezione, ed inizialmente non
sapevo nemmeno che cosa fosse!. L' Rsa è un cifrario a chiave pubblica
nato al M.I.T. nel 1978, ad opera di Ron Rivest, Adi Shamir e Les
Andleman (RSA deriva infatti dal cognome di questi 3 ricercatori). L'
Rsa è un crittosistema asimmetrico che si basa su alcune proprietà
particolari che hanno i numeri primi. Questo sistema ha trovato un vasto
campo di utilizzo, algoritmi come El-Gamal (anche El-Gamal è un sistema
asimmetrico e quindi abbastanza sicuro) sono diventati più rari, dato il
loro massiccio utilizzo in passato. Vediamo adesso un' ipotetica
sessione di crypting: nella quasi totalità dei casi il messaggio viene
trasformato in Int cioè in numero intero, e poi si tratta soprattutto di
un discorso matematico. Il primo passo per criptare un messaggio è :

n=p*q

N come si può notare dall' equazione ( meglio relazione matematica), è
il prodotto tra p e q che sono 2 numeri primi. Con la dovuta cautela si
può affermare che più piccoli sono P e Q è minore è la sicurezza, quindi
è preferibile usare un modulo N abbastanza grande. Adesso va calcolato
Ô(n). Per far ciò si usa:

Ô(n)=(p-1)*(q-1)

Questa è un' implementazione o più precisamente estensione del teorema
di Eulero. In pratica Ô(n), è un numero 'RELATIVAMENTE' primo a
(p-1)*(q-1). Ma a questo punto qualcuno potrebbe chiedersi, che
significa "relativamente primo": un numero è relativamente primo ad un
altro quando questi due numeri non hanno fattori in comune, tranne
ovviamente 1. Ad esempio 8 e 21 non sono numeri primi, però 8 e 21
saranno relativamente primi ad un altro numero cioè 1, dato che l' unico
fattore comune a 8 e 21 è 1. Precisazione (i fattori di 8 sono 1,2,4,8,
quelli di 21 sono 1,3,7,21). Spero di aver chiarito un pò questo
concetto. {{{ Tnx fly to CyberLaw}}}.

Un altro passo e siamo apposto. Molto semplicemente 'E' si ricava da:

e =Ô(n)

E rappresenta la chiave (o meglio l' Esponente pubblico) con cui sarà
criptato il nostro messaggio. Perciò E dev' essere un numero primo. La
chiave di decrittazione d si ottiene attraverso:

d = e^(-1) mod ((p-1)*(q-1)) ------->Perciò dobbiamo conoscere 'e', 'p'

e 'q'

Ok, adesso sappiamo più o meno come avviene una sessione Rsa, come
avrete certamente notato durante quest' operazione ci sono dei valori
che conosciamo e altri che vanno scoperti (nel caso di un attacco).
Possiamo crearci quindi una tabella che in seguto ci potrebbe essere d'
aiuto.

P e Q Sono numeri primi Incogniti
N N è il prodotto di p*q Conosciuto
Ô(n) Ô(n)=(p-1)*(q-1) Incognito
e Chiave di cripto Incognita
d Chiave di decripto Conosciuta

A questa tabella si potrebbe aggiungere x (messaggio in chiaro) ed y
(messaggio criptato). Adesso siamo arrivati al cuore dell' algoritmo
cioè alla strafamosa:

C=M^e mod n

Dove M è il messaggio da criptare e C è il messaggio criptato. Cercate
di ricordarla dato che è il nucleo dell' algoritmo. Spero che sappiate
ricavarivi la formula inversa in modo da ottenere il messaggio
originale. La formula è la seguente:

M=C^d mod n

Siamo alla fine della nostra panoramica sul Rsa, come avrete sicuramente
notato si tratta di un discorso principalmente matematico, ho cercato di
sintetizzare al massimo il discorso, vi ho risparmiato tutti i
retroscena (teorema di Eulero, Fermat) o si sistemi di fattorizzazione
tipo il Metodo della Curva Ellittica (buon metodo per numeri piccoli, se
però siamo di fronte a numeri grandi, risulta molto lento), poi ci sono:

Pollard's Monte Carlo Algorithm, Continued Fraction Algorithm, Trial
Division (questo è il più vecchio metodo di fattorizzazione che
conosco). In questo periodo non c'è neanche bisogno di sbattersi molto
con calcoli, dato che ci sono tanti programmi (specialmente dopo MIRACL)

che fanno questo per noi.

Pratical RSA

In questa sezione iniziamo a fare pratica con il programma RsaTool2 (dei
The Egoiste TMG) che ci sarà molto utile per il reversing. Per prima
cosa, dopo aver aperto il programma settate il Numer Base) a 10 (cioè
lavoriamo in base decimale) , e verifichiamo la veridicità della
formula:

n=p*q

Nel riquadro "Modulus N", inserite 47 e premete "Factor N", questo
dovrebbe dirvi che è un numero primo, stessa cosa con 53. Adesso
otteniamo N=2491 da una semplice moltiplicazione (47*53), facendo finta
di non conoscere P e Q, mettiamo 2491 in "Modulus N", e vediamo cosa si
ottiene se lo fattorizziamo. Il programma ritorna esattamente in valori

P e Q. Se andiamo un attimo a vedere la formula:

d = e^(-1) mod ((p-1)*(q-1))

vediamo che possiamo ricavarci anche la chiave di decripto D, avendo
però P e Q, mettiamo i valori P e Q nei loro relativi fields, e premiamo
"Calc D", adesso sappiamo anche la chiave di decriptazione. Avendo il
messaggio criptato, adesso possiamo decrittarlo. Per far questo basta
applicare la formula:

M=(C^d) mod n

Possiamo usare la stessa calcolatrice di windows per decriptare numeri
relativamente piccoli, o meglio costruirci un programma che faccia
questo per noi.

La fattorizzazione (addendum)

Questa piccola sezione, non è di fondamentale importanza per capire l'
algoritmo, ma potrebbe aiutarvi a capire quali sono le problematiche più
importanti di un attacco a quest' ultimo. Come spero abbiate capito, per
riuscire a violare un codice protetto con Rsa dobbiamo FATTORIZZARE il
modulo N. Fin quando si tratta di fattorizzare numeri piccoli non ci
sono problemi, ma i problemi sorgono quando si hanno davanti moduli N
molto grossi, in questo caso vanno usati algoritmi di tipo
probabilistico. Gli algoritmi per la fattorizzazione si dividono in due
categorie:

-Algoritmi deterministici.

-Algoritmi di Montecarlo (si basano su una scelta semi-casuale dei dati
di partenza).

Più precisamente gli algoritmi deterministici sono a loro volta divisi
in tre categorie:

-Algoritmi che restituiscono ad ogni tentativo riuscito un divisore
proprio (sia o non sia un numero primo)

-Algoritmi che per ogni tentativo riuscito restituiscono SEMPRE un
divisore primo.

-Algoritmi che ad ogni tentativo riuscito restituiscono un divisore
comune al numero dato.

L' algoritmo o teorema di Euclide, è quello che viene più usato per gli
attacchi. La formula comunemente utilizzata è:

x=(x.a)(x.b)/(x.a.b) mod N

Sia N=777 se si divide per 3 otteniamo 259 (che è >32), 259 si può
dividere per 7 e si ottiene 37 (che è < 72). I divisori primi di 259
perciò sono: 3, 7, 37. Provate a fattorizzarlo con RsaTool2 ed otterrete
gli stessi fattori. Questo genere di algoritmi deterministici possono
essere buoni (per buoni si intende la rapidità di risoluzione) per
numeri piccoli, per risolvere numeri molto grandi vengono invece usati
gli algoritmi di Montecarlo, cioè di tipo probabilistico.

{{Guardare le referenze a fine art.}}

Reversing corner

Ora che bene o male dovremmo aver acquistato una certa familiarità con
questo algoritmo, iniziamo a vedere qualche esempio pratico. Iniziamo
con un crackme abbastanza semplice ed utile. Io ho scelto il lockless
3CM, reperibile sul sito dei lockless. Per prima cosa mettiamo due bpx a
getdlgitemtexta ed a getwindowtexta. Dopo che sice ha poppato dovremmo
essere qui:

Reference To: USER32.GetDlgItemTextA
004137FE Call dword ptr [00428910]

00413804 jmp 00413819 ;Salta sotto

Referenced by a (U)nconditional or (C)onditional Jump at Address:

00413819 pop ebp

0041381A ret 000C

Dopo la chiamata a getdlgitemtexta e dopo il ret ci troveremo qui:

004029B0 push FFFFFFFF

004029B2 push 00419E18

004029B7 mov eax, dword ptr fs:[00000000]

004029BD push eax

004029BE mov dword ptr fs:[00000000], esp

004029C5 sub esp, 00000650

004029CB push esi

004029CC push edi

Possible StringData Ref from Data Obj ->"9901" ; Questo è un valore
molto importante,

004029CD push 004200DC

004029D2 lea ecx, dword ptr [esp+000000E4]

004029D9 call 00401130 ;In questa call il numero 9901 viene "copiato" in

un' altra locazione

Possible StringData Ref from Data Obj ->"12790891"; Anche questo è
importantissimo

004029DE push 004200D0

004029E3 lea ecx, dword ptr [esp+1C]

004029E7 mov dword ptr [esp+00000664], 00000000

004029F2 call 00401130 ;Chiamata alla solita call, che ora trasferisce
12790891

Possible StringData Ref from Data Obj ->"8483678" ; Altro valore di
grande importanza

004029F7 push 004200C8

004029FC lea ecx, dword ptr [esp+00000274]

00402A03 mov byte ptr [esp+00000664], 01

00402A0B call 00401130 ;Idem come sopra

Possible StringData Ref from Data Obj ->"5666933" ;Ultimo valore ma non

di meno importante

00402A10 push 004200C0

00402A15 lea ecx, dword ptr [esp+000001AC]

00402A1C mov byte ptr [esp+00000664], 02

00402A24 call 00401130 ;viene richiamata la solita call

00402A29 mov edx, dword ptr [esp+00000668] ;Indirizzo relativo al serial

00402A30 or esi, FFFFFFFF ;

00402A33 mov edi, edx ;| Metodo molto comune usato per calcolare la
lunghezza di una stringa

00402A35 mov ecx, esi ;| in questo caso del nostro serial

00402A37 xor eax, eax ;|

00402A39 mov byte ptr [esp+00000660], 03 ;|

00402A41 repnz ;|

00402A42 scasb ;|

00402A43 not ecx ;|

00402A45 dec ecx ;|

00402A46 cmp ecx, 0000000E ;se il serial è diverso da Eh (cioè 14 chars)

00402A49 jne 00402BB2 ;Salta alla beggar off, sennò continua

00402A4F xor ecx, ecx

00402A51 mov al, byte ptr [ecx+edx] ;char relativo al serial

00402A54 cmp al, 30

00402A56 jl 00402BB2 ;Salta alla beggar off, se è più piccolo di 30,
cioè 0

00402A5C cmp al, 39

00402A5E jg 00402BB2 ;Salta alla beggar off, se è più grande di 39
cioè 9

00402A64 inc ecx ;Incrementa il contatore

00402A65 cmp ecx, 0000000E

00402A68 jl 00402A51 ;Se è più piccolo di Eh, vai al prossimo ciclo

Adesso soffermiamoci per a fare qualche considerazione. All' inizio
viene preso il serial, invece che il name, e questo dovrebbe farci un pò
pensare, perchè se quello che viene manipolato è il serial, allora
dobbiamo prepararci all' analisi dell' algoritmo.In questo caso
fortunatamente sappiamo per certo che si tratta di rsa, e la cosa ci
avvantaggia molto. Poi notiamo anche la presenza di quattro numeri
fissi, che ci fa pensare (intuitivamente) a E ed N. Vi ricordo la
formula:

C=M^e mod n

Inoltre a 00402a51 che inizia una routine che controlla se il seral
inserito sia un numero, se non lo è salta ad errore. Da questo ciclo
capiamo anche che bisogna lavorare (operazioni di fattorizzazione e
varie) in base 10. Arrivati a questo punto, è meglio se tralasciamo l'
analisi della routine che segue, poichè è abbastanza lunga ed
ingarbugliata. Cercherò quaindi di riassumervi un pò cosa succede:

-Il serial dev' essere di 14 cifre, e poi durante l' algoritmo questo
numero verrà "spezzato" in due parti uguali composte da 7 cifre. Ad
ognuno di questi "pezzi" di serial saranno applicate una seria di
operazioni, che coinvolgono anche in numeri visti sopra (9901 e
12790891). Alla fine, le due parti di serial vengono riunite e poi
avviene il check finale, che ci dirà se il serial è giusto o sbagliato.

Dalle operazioni che vengono fatte, possiamo capire che 12790891
rappresenta il modulo N e che 9901 E. Adesso ci tocca riuscire a
decriptare le due parti di serial. Seguendo la routine, si può notare
inoltre che i valori: 5666933 e 8483678, sono le due parti di serial
corretto, ma purtroppo sono criptati. Inoltre se avete notato entrambi i
valori sono di 7 cifre ciascuno il che dovrebbe insospettirci. Quindi
arrivati a questo punto, facciamoci uno schema della situazione e
vediamo cosa possiamo fare:

-Conosciamo il seriale corretto ma criptato, cioè conosciamo il
parametro C.

-Conosciamo il modulo N con cui sono stati criptati i due valori.

-Conosciamo E (Con E e con P e Q si può trovare il parametro D).

Abbiamo tutto per un corretto decripting, ma dirvi soltanto quale
bottoni premere non sarebbe utile a nulla. Quindi cerchiamo di arrivare

alla soluzione: per poter decriptare i due spezzoni di serial dobbiamo
avere oltre al messaggio criptato (C) , il modulo N ed D. Se vi
ricordate N è dato da P*Q che sono due numeri primi, quindi
fattorizzandolo dovremmo riuscire a trovare P e Q (2 numeri in questo
caso relativamente piccoli). Aprite RsaTool2, ed impostatelo a base 10,
dato che come abbiamo precendente visto i valori sono tutti in base 10.
Scrivete nell' edit box "Modulus N" e fattorizzatelo "Factor N", il
programma vi dirà che questo numero è primo, questo significa che non
può essere il modulo N (che dovrebbe essere composto dal prodotto di due
numeri primi), però dato che è primo può essere il nostro Esponete
Pubblico E. Ora proviamo a fattorizzare 12790891, e da cui otteniamo P
(1667) e Q( 7673).. A questo punto dato che abbiamo P, Q e l' esponete
pubblico E, possiamo calcolarci la chaive di decripto D, che dipende
dalla seguente formula:

d = e^(-1) mod ((p-1)*(q-1))

RsaTool2 ci permette anche di calcolarci D, ricordatevi di mettere 9901
in Public Exp, e poi premete "Calc D". Il valore di D sarà 10961333,
adesso abbiamo tutti gli elementi per poter applicare la formula:

M=C^d mod n

Non sognatevi nemmeno di usare la calcolatrice di windows, esistono dei
programmi apposta per poter decriptare un numero. Vi consiglio di non
effettuare il decripting su un computer lento, ci vorrrebbe molto tempo.

Questi sono i valori da usare per il decrypt:

M1=(8483678^10961333) mod 12790891

M2=(5666933^10961333) mod 12790891

Da cui si ottiene:

M1=7167622

M2=3196885

Queste sono le due parti di serial decriptatato, se ricordate, l'
algoritmo richiedeva un serial di 14 cifre e successivamente veniva
diviso in due, M1 ed M2 rappresentano i 2 pezzi di serial. Quindi per
ottenere il seriale corretto M1M2, vanno uniti. Il valore così ottenuto

sarà 71676223196885.

Riconoscere l' Rsa.

Riconoscere l' Rsa negli schemi di protezione, non è molto semplice,
poichè può presentarsi sotto numerose forme, o peggio può essere
"riadattato". Ma ho pensato di elencarvi in modo molto generico le
principali caratteristiche che *dovrebbe* avere:

-Viene preso il seriale e viene criptato, solitamente con la formula
C=(M^E) mod N, (M non è altro che il serial).

-Di conseguenza dovremo avere 2 valori fissi, che rappresentano il
modulo N e l' esponente pubblico E.

-Poi dovrebbe avvenire un confronto tra il serial criptato ed il nome.

Adesso voglio riportarvi un spezzone di codice preso da un crackme, che
potrebbe chiarirvi un pò le idee.

0040118D sub_40118D proc near

0040118D mov ebx, eax ;In eax abbiamo il nostro serial, che ora vine
messo in ebx

0040118F mov ecx, dword_4030A8 ;in ecx viene messo un valore fisso

00401195 mov esi, dword_4030A4 ; in esi viene messo un altro valore
fisso

0040119B mov eax, 1 ; mette 1 in eax

004011A0 loc_4011A0:

004011A0 cdq ;ConvertDoubleToQuad, si prepara ad un divisione

04011A1 mul ebx ; Moltiplica ebx per eax

004011A3 div esi ;divide esi per eax (in esi si trova uno dei valori
fissi)

004011A5 mov eax, edx ;Sposta il resto della divisione in edx

004011A7 loop loc_4011A0 ; mette in loop queste operazioni, questo ciclo

sarà ripetuto 1109 volte, dato che ecx contiene proprio 1109!
dopo abbiamo un confronto tra il serial corretto ed il seriale inserito
da noi e criptato.

VALORI:

004030A4= 0EAD2C511h cioè 3939681553

004030A8=455h cioè 1109

La routine è abbastanza semplice, in ebx abbiamo il serial inserito da
noi, il quale viene moltiplicato per eax (che la prima volta contiene 1)
e poi viene diviso per esi (in esi abbiamo il valore 3939681553). Poi
viene preso il resto della divisione (quindi possiamo considerarlo come
un MOD!!!) e spostato in eax, tutte queste operazioni vengono ripetute
1109 volte!. Ormai avreste già dovuto capire di che operazione si
tratta, ma comunque vediamo un pò analiticamete cosa avviene:

eax=eax * ebx

dato che viene iterato per 1109 volte equivale a:

eax=(ebx ^ 1109)

di seguito abbiamo eax = eax / esi , ma poichè viene preso solo il resto
può essere considerato come:

eax MOD esi (da ricordare che esi è 3939681553)

Quindi da tutto questo si ricava che, C=(Seriale ^ 1109) MOD 3939681553

Bene! abbiamo finito, ho cercato di scrivere qualcosa di sintetico e
completo allo stesso tempo, spero di esserci riuscito. Nei vari schemi
di protezione l' Rsa si può presentare in modo molto vario, noi abbiamo
esaminato solo uno dei casi più semplici. La prossima volta (se ho
tempo) scriverò qualcosa su come viene usato l' rsa a 128, 256 e 512
bit.

References:

-Cenni di Aritmetica Superiore per la Crittologia, di Marco Frigerio

-Vari docs letti in giro.

Note finali

In fine vorrei porgere i miei più cari saluti a chi mi ha insegnato
qualcosa e chi mi ha aiutato a capire, o più semplicemente alle persone
con le quali ho passato delle ore piacevoli.

Disclaimer

Noi reversiamo al solo scopo informativo e di miglioramento del
linguaggio Assembly .

Capitoooooooo????? Bhè credo di si ;))))

Autore: Evilcry
E-Mail: evilcry@virgilio.it


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x03][NETW0RKiNG] NEL R0UTER CiSC0 [Cartesio]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

Tutto ciò che è contenuto in questo articolo non è da utilizzare in
maniera lucrosa e/o in maniera lamerosa, ma è semplicemente un testo per
porre un po' di risposte a quei perché di tutti i giorni. Non vuole
essere una guida approfondita..a questo possiamo arrivare semplicemente
se ne viene richiesta la necessità.
Ma sopratutto perché questo carattere del "gedit" è così piccolo.. mi
sto fottendo gli occhi, mi conviene cambiare utility.

Ecco le cose fondamentali da fare prima di leggere questo articolo:
1) Visualizzare un carattere più grande.
2) Avere una bella "Fi*a" di fianco a voi.
3) essere assolutamente rilassati.
5) il punto 4 lo salto.

Cominciamo.("occhio che partiremo da molto lontano")
Quante volte scrivendo c:\>tracert IP_HOST oppure [localhost@root]#
traceroute IP_HOST vi è capitato di vedere una tabella simile: 1
10.1.1.1 0.675 ms 1.829 ms 0.476 ms
2 10.1.7.22 5.351 ms 4.559 ms 1.258 ms
3 10.1.11.1 3.395 ms 10.3.7.1 (10.3.7.1) 6.390 ms 1.985 ms
4 10.1.15.1 1.529 ms 2.399 ms 10.14.1.1) 1.174 ms
5 10.1.13.1 5.272 ms 10.946 ms 5.307 ms
6 14.4.10.1 6.219 ms 5.977 ms 5.700 ms
7 12-1-1-0.mil11.it 6.519 ms 6.651 ms 7.738 ms
8 23.14.1.2.net 102.337 ms 102.567 ms 102.084 ms
9 124.12.1.1 103.003 ms 103.558 ms 103.424 ms
"Che ca**o sono sti "zagareddri" --> "cosi !".. ?
il comando traceroute o tracert è un'applicazione che invia un pacchetto

e TRACCIA i passaggi che effettua tale pacchetto per raggiungere la
destinazione impostata nella richiesta. Alcuni utilizzano questa
applicazione per effettuare un controllo generale del funzionamento
della rete ed individuare eventuali errori o per schematizzare l'intero
percorso della rete.
Tutto è nato da qua, mentre eseguivo il traceroute, avevo deciso di
localizzare ogni ip, e costruire a mano la mappa della Rete, disegnando
e disegnando mi son trovato dinnanzi ad un IP del genere
"
gw-3-0-0.ip.net", un GATEWAY (=ponte), cosa per me indigena, alquanto
aliena, era proprio come un UFO per i miei occhi, la sera discutendo
amichevolmente, scoprii che quel GATEWAY era un "
ROUTER", cosa ancora
più ignota. E' nato da li un fervido interesse verso tali dispositivi
che stanno alla base delle LAN e WAN (guardare l'articolo di Spy e di
l1l0, che hanno fornito le necessarie nozioni).
Per continuare l'articolo dovrei fare una piccola introduzione allo
stack TCP/IP.
Lo stack "
TCP/IP" prende il nome dai due protocolli più importanti e
vale a dire il Transfer Control Protocol (TCP) e IP (Internet
Protocol)ma lo stack TCP/IP comprende altri sottoprotocolli...un terzo
protocollo è l'UDP (User Datagram Protocol) non sto a narrararvi tutta
la vicenda, ne conseguirebbe un ulteriore articolo, di conseguenza
torniamo al riassuntino:
Il modello TCP/IP si è formato basandosi sullo standard ISO/OSI
(International Standards Organization/Open Systems Interconnection) uno
standard formato parecchi anni fa (io non ero nato) per far comunicare
computer in reti di grandi dimensioni. La prima Rete ARPANET mamma di
tutte le reti, un po' come i LED ZEPPELIN per il rock, i Metallica per
il Metal...La FI*A per le femmine !... dai un po' di pazienza
allegeriamo questo articolo.
Tornando al modello "
OSI" esso è costituito da 7 Livelli, viene
rappresentato come una pila.. (chissà VoLTa se conosceva lo standard..
BATTUTONA, spero che qualcuno l'abbia capita)

Livello 7 APPLICAZIONE = che equivale alle applicazioni che si eseguono
sul computer per poi fornire dei servizi alle reti... un modo per
ricordare questo livello è pensare al "
ftp, telnet, e-mail, web
browser... etc."

Livello 6 PRESENTAZIONE = è il livello che si occupa della presentazione
dei dati per il livello superiore.. cioè se io ti invio un file di Testo
.Doc che abbia una certa topoligia di intestazione.. e poi ti viene
rappresentato come file AUDIO .. vuol dire che qualcosa non è stata
eseguita per il verso giusto.

Livello 5 SESSIONE = si occupa ti aprire mantenere e chiudere una
sessione.. che viene utilizzata dai livelli superiori

Livello 4 TRASPORTO = Provvede all'affidabilita', al trasporto
effettivo dei dati da una macchina sorgente a quella di destinazione. I

Livello 3 RETE = rete è un livello fondamentale la comunicazione in
rete.. in questo Livello vengono collocati gli indirizzi IP. Il Livello
Rete consente di mettere in comunicazione due sistemi di elaborazione
che possono essere localizzati anche su reti geograficamente diverse.
(cioè se io sto in cucina.. e mia nonna sta in bagno, posso comunicarle
che è finita la cartaigenica grazie al livello rete)

Livello 2 DATA LINK(lo lascio in inglese che fa più colpo) = si occupa
del collegamento e del flusso dati.

Livello 1 FISICO = lo dice la parola stessa.. "
se non hai il PC
accesso.. non comunichi ".
N.B: Ogni Livello comunica con l'entità di pari livello (peer) tramite
il PDU protocol data unit.
Dopo questa BREVISSIMA descrizione del modello OSI, proprio breve..
magari qualcuno mi può prendere superficialmente e considerarmi un
ignorante.. ! passiamo al..
########################################################################

Il Router:
E' un dispositivo di rete che si colloca al livello 3 del modello OSI
(quindi.. vedi su ^ .. livello rete). Il suo compito è di potere
decisionale basandosi sul protocollo di terzo livello, il protocollo IP,

indi per cui decide quale strade far prendere ad un pacchetto per
arrivare a tale destinazione..
Esempio pratico: se io voglio comunicare con un biglietto scritto a mia

  

nonna che è in bagno ma è finita la cartaigenica il router deciderà da
dove far passare l'informazione, il router dirà "do il messaggio alla
sorella di Cartesio che poi lo da al papà che lo consegna alla nonna..ma
il papà di cartesio è a lavoro, quindi mi tocca lasciare il biglietto
sotto la porta del bagno così la nonna appena si accorgerà di questo
biglietto lo leggerà e scoprirà di essere senza carta igenica.

Il router prende tale decisione attraverso i protocolli di routing. il
RIP (Routing Information Protocl) IGRP (Interior Gateway Router
Protocol) EIGRP (Enhanced IGRP), OSPF (Open Shortest Path First).
Il RIP è un protocollo che seleziona il percorso basandosi sul numero di
HOP (salti) che deve fare un pacchetto per arrivare a destinazione, non
è detto che la via che scelga il RIP sia la più veloce.. ma certamente è
quella che comprende meno passaggi..
IGRP A differenza del protocollo RIP, dove l'unica metrica possibile è
l"
hop count" e si ha un limite massimo di 15 hop count, in IGRP le
metriche si basano su 4 parametri:
Bandwidth, Delay, Reliability, Load.
EIGRP e' stato sviluppato da CISCO a partire dalla release software 9.21
sulle basi del protocollo IGRP, rispetto al quale sono stati introdotti
dei miglioramenti.
OSPF Open Shortest Path First... detto tutto.
Stiamo arrivando al router !!! GIOVANI... mentre mia nonna è ancora in
bagno che chiede la cartaigenica.

########################################################################

Struttura del Router
Un router è un dispositivo di rete che ha le stessa caratteristiche di
un pc, e cioè il router ha una Rom, Ram, interfacce, CPU, schedamadre...
etc.. etc...
Prenderemo in esame un esempio di router Cisco.
RAM: nella ram risiede la configurazione corrente del router e le
variabili temporanee necessarei per il corretto funzionamento. Viene
caricata quando viene riavviato un router.
N.b: Il contenuto della RAM viene perso durante lo spegnimento del
router.
NVRAM: (non volatile RAM) è la memoria che non viene persa durante il
riavvio del router.. e contiene il backup della configurazione.
FLASH: Parte di memoria di tipo permanente in cui risiede l'IOS
(Internetwork Operating System).. tutte stè parole per dire .. che nella
memoria FLASH ci sta il sistema operativo.
ROM: è la memoria in cui si trvoa il software di diagnostica.. come nel
PC...
CONSOLE: è una interfaccia seriale
AUX: è una porta seriale usata per il collegamento di altre periferiche
ausiliari, difatti il nome AUX abbreviazione di AUXILIAR significa
proprio ausiliaria.
Interfacce di rete: sono le connessione di rete che possono essere
collocati su schedamadre oppure moduli separati... Esempio Ethernet,
Seriali. o per ISDN .. etc.quindi le interfacce son ciò che ti
permettono di essere in rete . :P proprio una interpretazione
filosofica. come dire "
Cogito Ergo Sum".

Un router ha anche un sistema operativo, ora comincia la parte carina...
Parte fondamentale del router è la configurazione...
Sapendo che il router è alla base della Rete, instrada pacchetti,
seleziona percorsi, possiamo immaginare l'importanza del ruolo che esso
svolge.
E quanto è importante configurare nel modo esatto il router e le
apposite interfacce.

########################################################################

Il router e la CLI (command line interface).. ricordo che parliamo
sempre di router CISCO.

Il prompt del router è:
Nome_router>
il simbolo > indica che si è in una modalità user in cui si possono
eseguire alcuni semplici comandi di visualizzazione delle
configurazioni. Ma con una configurazione non dettagliata del router,
possono venire a galla alcune informazioni preziose per un utente
qualsiasi ma dannose per l'amministratore della rete. Sul router può
essere attivo il server http che permette la visualizzazione di alcune
caratteristiche del router anche da utenti sconosciuti !.. eseguendo il
comando
Nome_router> show running-config
si otterrà una serie di informazioni riguardo configurazioni e stato
delle interfacce di rete del router, esempio:
interface Ethernet 0
ip address 10.1.1.1 255.255.255.0
no ip directed-broadcast
no mop enable
!
poi si possono osservare informazioni riguardo password
line aux 0
line vty 0 4 "
cisco"
password terminal
login
!
tramite http server attivo sul router basta scrivere
ip_del_router/level/16/exec/show/config e si ottiene la stessa lista
sopra elencata.
Ecco che sbuca fuori la password del line vty 0 4 (line virtual terminal

--> le connessioni telnet, 0 4 --> perché possono effettuarsi massimo 5

sessioni telnet contemporaneamente.
di conseguenza è meglio mascherare la password e disattivare il web
server per il controllo da WEB, più avanti spiegherò come.
Per ottenere una lista di comandi che si possono esguire in modalità
USER è possibile.. anzi consigliato eseguire il comando "
?"
Nome_router> ?
e verrà elencata una serie di comandi che possono essere eseguiti dalla
modalità USER il comando "
?" può essere adoperato anche per suggerimento
riguardo il completamento di un comando ad esempio

Nome router> show ?

verrà elencata una serie di comandi da eseguire conseguenti a show..
esempio:
interfaces
running-config
startup-config
version
etc etc.. se la nonna è ancora in bagno...
il comando "
show interfaces" serve per mostrare lo stato e le
caratteristiche di tutte le interfacce presenti nel router, quali
Ethernet 0 .. Ethernet 1, Serial 0, Serial 1.. etc.
Quando si parla di "
stato dell'interfaccia" del router si intende lo
stato fisico e lo stato di rete di tale interfaccia del router, vale a
dire i "
cavi, dispositivi etc.. etc.." e i protocolli "ip, tcp, ipx,
appletalk, routing". Di conseguenza quando viene visualizzato nello
stato di un'interfaccia dopo aver eseguito "
show interfaces" `Interface
Serial 0 is up, line protocol is up` vuol dire che tutto funziona per
il meglio, che tutto è UP che tutto è SU che tutto funge.!
Interface Serial 0 is up, line protocol is down significa che c'è un
problema di rete
Interface Serial 0 is down, line protocol is up significa che c'è un
problema a livello fisico dell'interfaccia
Interface Serial 0 is down, line protocol is down, vuol dire che è
"
IMBAGAGLITA, che non è tutto incasianto".
Interface Serial 0 is administratively down vuol dire che l'interfaccia
è SPENTA dall'amministratore della rete.
Dopo aver spiegato per sommi capi la modalità USER quella limitata
nell'esecuzione dei comandi, vedermo la parte privilegiata. Per entrare
in modalità privilegiata bisogna digitare "
enable" ed invio, e verrà
richiesta una passoword per avere accesso. Tale password non viene
visualizzata da un user qualunque.. però alle volte per disattenzione
degli amministratori le password di "
user" e di "privileged" sono le
stesse.
Somiglia molto ad un prompt di una shell linux difatti, l'accesso
privilegiato al router è caratterizzato dallo sharp = # :) quindi il
prompt del router in modalità superuser sarà
`Nome_router# ` basta digitare il "
?" per avere subito scarrozzata una
lista di tantissimi comandi da eseguire in questa modalità.. tra ciò i
comandi per la configurazione della rete e la configurazione delle
password di accesso.
Uno dei punti relativi alla sicurezza: le password di accesso in chiaro
e criptate. Il router utilizza una opzione per il criptaggio delle
password in modo tale da nascondere anche al semplice utente di guardare
la password di accesso con un semplice "
show running-config". o tramite
un ip_del_router/level/16/exec/show/config. andiamo a disattivare il web
server e criptare la password.
Nome_router> enable
password:
Nome_router# siamo in modalità privilegiata.. la password non viene
scritta neanche con gli asterischi.. in modo tale da non comprendere
nanche la sua lunghezza.
ora entriamo nel settore di "
configurazione globale", in cui si possono
effettuare una serie di configuarzioni quali..esempio hostname..
Entriamo in modalità configurazioni globali con il comando configure
terminal, si otterrà un prompt del genere:
nome_router(config)# hostname Mia_nonna_è_in_bagno
Mia_nonna_è_in_bagno(config)# ... sarà il nome del nostro router.
ecco ora..l'opzione per disattivare il server http del router in modo
tale da esporlo meno ad attacchi.
Mia_nonna_è_in_bagno(config)# hostname Router
Router(config)# scusate ma quel prompt era troppo lungo l'ho dovuto
cambiare.
Router(config)# no ip http server
Router(config)# il comado qui sopra ha disattivato l'http server.
Da notare che nel router cisco la maggiorparte delle volte che si vuol
negare un qualcosa basta mettere davanti il comando "
no". infatti se
noi avessimo voluto rendere attivo "
http server" ci sarebbe bastato
digitare
Router(config)# ip http server
Router(config)# in questa sezione di configurazoine globale del router
si possono settare le password di accesso "
line console 0" "line vty 0
4" e "enable
Router(config)# enable password cisco (=equivale alla password che viene
richiesta quando dalla modalità user si digita enable per entrare in
modalità privilegiata)
Router(config)# line console 0
Router(config-line)# login "invio" (per settare un login nullo)
Router(config-line)# login Nonna (login = nonna)
Router(config-line)# password cisco
Router(config-line)# exit
Router(config)# line vty 0 4
Router(config-line)# login
Router(config-line)# password cisco
per tornare indietro nel prompt non c'è cd .. oppure cd / ma basta
digitare exit per tornare al prompt precedente e cioè Router(config)#
oppure dicitare CTRL+Z per tornare al prompt principale e cioè Router#.

Ed infine la password di enable secret, vale a dire la password criptata
con l'algoritmo MD5, che permette di rendere illegibile tale password a
chiunque con uno show running-config, perché il risultato dell'output
sarà...
service password-encryption
$1812AS39012!|0$091a!"£81! ... uazhazuzhu mi sento redicolo.

Ogni qualvolta si cambia qualche settaggio del router che pretende
essere salvato e ricaricato al prossimo riavvio conviene copiare la
configurazione che è presente attualmente "
cioè in running, per questo
viene chiamata running-config nella startup-config che è quella che
risiede nella memoria NVRAM che passerà le informazioni alla RAM al
prossimo riavvio. Il comando per copiare la configurazione in running in

NVRAM è
Router# copy running-config startup-config
N.b: se fate alcune modifiche a parametri, e settaggi di rete, ma non
eseguite un copy running-config startup-config .. appena il router viene

spento e riacceso le modifiche si riveleranno nulle. Di conseguenza
tocca ripetere le procedure da capo.
########################################################################

Configurazione delle interfacce del router

Il router è formato da interfacce che permettono la comunicazione in
rete, tali interfacce possono trovarsi su scheda madre o possono essere

dei moduli aggiuntivi.
Andiamo ora a configuarare una interfaccia
Router> enable
password:
Router# andiamo in configurazione globale..
Router# configure terminal
Router(config)# interface Ethernet 0 (attenzione Ethernet 0.. può
cambiare a seconda delle varie IOS dei vari router.. ad esempio può
essere FastEthernet 0 oppure FastEthernet 0/0, sesso discorso vale per
seriale..)
Router(config-if)# <-- questo prompt con l'aggiunta di if sta a
simboleggiare che siamo nella configuarzione dell'interfaccia ... nel
caso sopra elencato "Ethernet 0"
non ci resta altro che dare un ip ed una netmask all'interfaccia
Ethernet del nostro router
Router(config-if)# ip address 10.1.1.1 255.255.255.0
Poi settare la nostra interfaccia UP, e cioè metterla in funzione con il
comando

Router(config-if)# no shutdown
Router(config-if)# si può passare da una interfaccia all'altra digitando

semplicemente

Rotuer(config-if)# interface serial 0 (ed inautomatico.. il prompt non
sembrerà cambiato.. ma siamo nella interfaccia serial 0) non tutti i
router cisco.. rispondono così... di conseguenza un consiglio personale
è.. digitare exit che porterà al seguente prompt:

Router(config)# interface serial 0
Router(config-if)# ip address 37.1.1.1 255.255.255.0
Router(config-if)# clock rate 56000 -----> "questo comando lo rimando in

un altro articolo... dipende da l'utilizzo che ne viene fatto di questo

di articolo"

Router(config-if)# no shutdown
Router(config-if)# CTRL+Z
Router#

########################################################################

IOS (Internetworking Operating System) .. Sistema operativo del router.

Il sistema operativo risiede nella memoria falsh del router per ottenere
inforamzioni riguardo alle memorie del router.. basti digitare uno show

version
e si otterrano informazioni relative a:
Piattaforma sulla quale si sta operando, IOS e versione e nome del file,

memorie e le capacità delle memorie.
Sulla memoria flash possono essere copiate più IOS. Nei router Cisco c'è
differenenza tra le realase 11.0 in giù e 11.x in su..
Occhio ragazzi .. se siete su un router non digitate mai ... dico mai ..

Router# erase flash

questo significherebbe cancellare il sistema operativo. Di conseguenza
una reazione a catena di rompimenti di balle...E' come se io facessi
ERASE CARTAIGENICA.. mia nonna potrà stare in bagno per sempre oppure
... ? ... pulirsi in qualche altro modo.
Il nome del file del sistema operativo è sempre un file binario di
conseguenza caratterizzato da una estenzione .bin. Anche il nome del
file ha delle specifichè che indicano Piattaforma, caratteristiche,
fattori di compressione... che forse il 90 % degli utenti... ignorerà.

Un esempio: c7200-ajs56-mz
c7200: Router Cisco Serie 7200
-
a: supporto protocollo APPN;
j: supporto di caratteristiche Enterprise;
s: supporto di NAT,ISL,VPDN/L2F
56: supporto di crittografia a 56 bit
-
m: esecuzione in RAM
z: file compresso con Zip.
E' possibile copiare una IOS da un TFTP (trivial file transfer protocol)

che utilizza il protocollo UDP non affidabile ma veloce, per trasferire
il file.
Perché caricare una IOS da un TFTP ? per fare eseprimenti, testando IOS,
compatibilità tra interfacce e IOS, per avere a disposizione molte più
IOS, difatti la memoria di un router è molto limitata e non riesce a
contenere un gran numero di IOS, di conseguenza su un server TFTP la
quantità e la varietà di IOS disponibili sarebbe maggiore..
Si può configurare anche il router in modo da decidere la procedura di
avvio... come settare il bios del pc quando selezioni "1) floppy 2) Cd -
rom 3)HD... etc..
in un router basta andare in modalità di configurazione generale...di
conseguenza digitare configure terminal
Router(config)# boot system flash nome_file.bin
Router(config)# boot system tftp nome_file.bin ip_del_server.
etc...
per avere più completezza di conoscenza basta digitare
Router(config)# boot system ?
e comparirà una lista di comandi disponibili.. non sto ad elencarvi..
perché non sono qui.. a descrivere tutti i comandi di un router.. ma le
cose essenziali.. per approcciare ad esso.

########################################################################

Siamo giunti al termine dell'Articolo, però voglio che si sappia che
questo articolo.. è solo per un approccio al router e non un tutorial
completo, perché ho omesso parecchie parti anche importanti ma che
inserirò o aggiornerò in seguito ad eventuali richieste... Vedremo se
questi accorgimenti sono serviti.. e se qualcuno si è interessato in tal
modo da chiedermi un articolo più dettagliato. Per finire.

Accorgimenti sicurezza:
Disattivare le porte Echo UDP e Chargen UDP, possono essere causa di un

DoS che provocherebbe il collasso del router.
Disattivare il server http
Attivare l'enable secret "
password"
Alcuni router sono vulnerabili ad un attacco del genere
http://ip_router/%% che possono causare un blocco del sistema.

scritto da "
CaRTeSio".. cogito ergo sum.
e-mail: comunicazione@polizia.it


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x04][C0DiNG] 0PENSSL E RSA C0DiNG [binduck]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

Article && sources by binduck - binduck@coder.hu

0. Intro
1 RSA - Teoria
1.1 OpenSSL/RSA [genrsa,rsa,rsautl]
(generare chiave,cifrare/decifrare,firmare,...)
1.1.1 GPG
1.2 Impariamo ad implementare RSA nei nostri programmi
1.2.1 RSA - Headers
1.2.2 RSA - Le chiavi
1.2.3 RSA - Cifrare e decifrare

0. Intro

In questo articolo voglio illustrarvi le basi che vi permetteranno di
capire e implementare il potenete algoritmo RSA.
1]Vedremo, quindi, come utilizzare il toolkit openssl (ne vedremo le
principali funzioni, come creare le chiavi, come cifrare/decifrare
dati).
2]Vedremo insieme le basi delle librerie openssl, e in particolare
quelle che vi permetteranno di implementare l'algoritmo nei vostri
programmi.

Per affrontare la lettura di questo articolo avrete bisogno di
conoscere:
- il linguaggio C
- se volete comprendere la parte matematica dovrebbe bastare
l'infarinatura di matematica fornitavi nelle elementari e nelle medie.
- dovreste avere openssl e le librerie (/usr/include/openssl/) se vi
mancano....installatele.

1 RSA - Teoria

RSA fu sviluppato da tre matematici del MIT:Rivest, Shamir e Adleman:

- Cifrario a chiave pubblica (usato nel PGP e in molti altri programmi)
- Si basa sulla teoria matematica dei numeri; infatti le chiavi sono
costituite dal prodotto di due numeri primi molto grandi(piu' di
trecento cifre)
- Cifrazione/Decifrazione di dati
- Firma/Controllo firma
- Supporta chivi di diversa grandezza in modo da fornire vari livelli
di sicurezza (grandezza minima consigliata e' 1024).

Riprendiamo alcuni concetti basilari della crittografia che ci saranno
utili. Un cifrario a chiave pubblica (o cifrario asimmetrico) e'
basato sull'utilizzo di due chiavi,una per cifrare e una per decifrare.
La prima chiave puo' essere distribuita a chi vuole farci pervenire
messaggi cifrati in modo che solo noi possiamo decifrarli con la nostra
chiave segreta(che DEVE rimanere segreta).
Il fatto che la chiave pubblica possa essere tranquillamente
distribuita anche via email, in messaggi in chiaro, elimina la
necessita' di utilizzare canali altamente sicuri per lo scambio della
chiave utilizzata nei cifrari a chiave semplice.

Iniziamo a vedere l'algoritmo in generale ed a analizzarlo dal punto
di vista matematico.
1)Il cifrario RSA e' basato sul prodotto di due numeri primi di grandi
dimensioni, che possono superare le 300 cifre.
Ricordo inanzitutto che un numero si dice primo solo se e' divisibile
per 1 e se stesso.
Noi prenderemo due numeri primi piccoli per fare un esempio:
P = 7 e Q = 11
2)Ora chiamiamo N il prodotto dei due: N = P * Q = 77
3)Ora calcoliamo il valore della funzione di Eulero di N in questo
modo:
M = f(n) = (P-1)*(Q-1)
Nel nostro caso M = 60
Vi ricordo che il valore della funzione di Eulero dovra' rimanere
segreto.
4)Ora scegliamo un numero E tale che E e M siano primi tra loro.
Poiche' 1 < E < M allora iniziamo a dividere M per tutti i numeri
maggiori di 1 fino a che non troviamo un numero che abbia una parte
decimale.
60 / 2 = 30
60 / 3 = 20
60 / 4 = 15
60 / 5 = 12
60 / 6 = 10
60 / 7 = 8.57
Quindi E = 7
5)Ora calcoliamo D usando la formula D = ((H * M) + 1) / E
in modo che H sia il piu' piccolo valore per cui D sia intero.
Quindi iniziamo a sostituire H con i numeri 1,2,3,... fintanto che il
risultato trovato non sia intero.
D = ((1 * 60) + 1) / 7 -> NO
D = ((2 * 60) + 1) / 7 -> NO
....
....
D = ((5 * 60) + 1) / 7 = 301 / 7 = 43

D = 43
6)Bene ora abbiamo generato la nostra chiave che useremo per
crifrare/decifrare i dati. Per cifrare usiamo la formula:
C = (B^E) mod N
Dove C e' il testo cifrato, mentre B e' il valore decimale del
testo che vogliamo cifrare.
Prendiamo come esempio B = 3 ->
C = (3 ^ 7) mod 77 = 31
Per decifrare usiamo la formula:
L = (C^D) mod N
Prendiamo ora il numero precedentemente ottenuto e decifriamolo:
L = (31 ^ 43) mod 77 = 3

Nota: si potrebbe pensare che si possa semplicemente calcolare la
funzione di Eulero senza utilizzare i numeri primi P e Q. Questo non e'
impossibile ma richiede un ingente quantita' di tempo, avendo lo stesso
grado di complessita' della fattorizzazione di N.

1.1 OpenSSL/RSA

Vediamo ora come utilizzare openssl in combinazione con RSA.
Creiamo inanzitutto le chiavi RSA, e per fare questo si usa openssl
genrsa:
openssl genrsa [-out nome_file] [-passout arg] [-des] [-des3]
[-idea] [-f4] [-3] [-rand file(s)] [numbits]

binduck@fuzzy:~$ openssl genrsa -des3 1024 > key.pem
Con questo comando generiamo una chiave RSA di lunghezza 1024 bits
e la salviamo nel file key.pem nel formato PEM.
Verra' chiesto di inserire una password per cifrare la chiave
con 3des prima di salvarla(possiamo sostituire -des3 con -des o -idea).
Se invece non vogliamo cifrarla basta fare eliminarel'opzione -des3:
binduck@fuzzy:~$ openssl genrsa 1024 > key.pem

Vediamo le altre opzioni del comando openssl genrsa:
-out file_out :Il nome del file di output.
-passout arg :Il file sorgente delle password per l'output.
-F4|-3 :L'esponente pubblico da usare(65537 o 3(default 65537))
numbits :Numero di bits(minimo consigliato 1024).

PER MAGGIORI INFORMAZIONI SULLE OPZIONI: genrsa(1)


Ora ricaviamo la chiave pubblica da quella privata appena generata,
utilizzando openssl rsa:
openssl rsa[-inform] [-outform |PEM|NET|DER] [-in filename]
[-passin arg] [-out filename] [passout arg] [-sgckey] [-des] [-des3]
[-idea] [-text] [-noout] [-modulus] [-check] [-pubin] [-pubout]
-in file :Legge la chiave RSA contenuta nel file passato come
argomento.(Per default la chiave sara' intesa come privata).
-pubin :Dichiara che la chiave letta con -in file sara'
pubblica.
-pubout :Dichiara che l'output sara' una chiave pubblica.

binduck@fuzzy:~$ openssl rsa -in key.pem -pubout > pub.pem
o in maniera equivalente:
binduck@fuzzy:~$ openssl rsa -in key.pem -pubout -out pub.pem
Vediamo le altre opzioni interessanti:
-check :Controlla la struttura di una chiave privata RSA.
binduck@fuzzy:~$ openssl rsa -check -in key.pem

Altre opzioni utili sono quelle che permettono di cifrare una chiave,
con un algoritmo a scelta tra des, 3des, idea, in un secondo momento
dalla creazione della chiave.
binduck@fuzzy:~$ openssl rsa -in key.pem -des3 -out key.pem
A questo punto bastera' digitare la pass phrase, confermarla e la
vostra chiave sara' cifrata.

Ovviamente possiamo essere interessati a rimuovere la password
dalla chiave:
binduck@fuzzy:~$ openssl rsa -in key.pem -out keydec.pem
Dovrete inserire la pass phrase per eliminare la cifratura e il
gioco e' fatto.

-modulus :Stampa il modulo della chiave.
binduck@fuzzy:~$ openssl rsa -in key.pem -modulus

PER MAGGIORI INFORMAZIONI SULLE ALTRE OPZIONI: rsa(1)


Ora passiamo a vedere come cifrare/decifrare,firmare/verificare dati
usando l'algoritmo RSA.
openssl rsautl [-in file][-out file][-inkey file][pubin][-certin]
[-sign][-verify][-encrypt] [-decrypt][-pkcs][-ssl][-raw][-hexdump]
[-asn1parse]

NOTA: rsautl, poiche' usa l'algoritmo RSA direttamente puo' essere
usato solo per cifrare dati di piccole dimensioni.
Se tentiamo di firmare un file troppo grande verra' generato un errore
che ci dira' appunto che la dimensione del file era troppo elevata per
la chiave usata.
-inkey file :Usa la chiave contenuta nel file passato come argomento.
(Per default rsautl si aspettera' una chiave privata).
-pubin :Dichiara che la password contenuta nel file letto
con -inkey file e' una chiave pubblica.
-certin :Dichiare che l'input sara' un certificato contentente una
chiave RSA.

Prendiamo come esempio un file contenente una password.
echo "
rsa_algorithm" > pass.txt
Ora per cifrare il file digitiamo:
binduck@fuzzy:~$ openssl rsautl -encrypt -in pass.txt -inkey pub.pem
-pubin> crypto
Ora avremo la nostra password cifrata nel file "
crypto".
Ovviamente pub.pem e' la chiave pubblica precedentemente generata con
openssl genrsa.

Vediamo ora il processo inverso, decifriamo la password:
binduck@fuzzy:~$ openssl rsautl -decrypt -in crypto -inkey key.pem
Questo stampera' sullo stdout la password in chiaro.
key.pem e' la chiave privata, poiche' stiamo decifrando.

Mettiamo il caso che vogliamo semplicemente firmare un documento
usando la nostra chiave privata(anche nel caso delle firme possiamo
firmare solo dati di piccole dimensioni).
binduck@fuzzy:~$ openssl rsautl -sign -in small_file.txt -inkey key.pem
-out signed_file

Vediamo ora come verificare dei dati firmati:
binduck@fuzzy:~$ openssl rsautl -verify -in signed_file -inkey key.pem

PER MAGGIORI INFORMAZIONE SULLE ALTRE OPZIONI: rsautl(1)

1.1.1 GPG
Se vi siete rotti delle limitazioni insite nell'uso di rsautl a causa
della dimensione dei file vi consiglio di usare gpg.
Pero' per quanto riguarda l'utilizzo di RSA in gpg bisogna ricordare
che questo algoritmo e' usato solo per le firme.
Ci sono ottime guide dedicate a questo programma, prima fra tutte il
manuale che potrete trovare a questo indirizzo:
http://www.gnupg.org/(en)/documentation

1.2 Impariamo ad implementare RSA nei nostri programmi.

Ora passiamo alla parte piu' interessante:
Impariamo come implementare RSA nei nostri programmi.


1.2.1 RSA - Headers
Qui e' riportata la lista di headers che utilizzeremo per creare dei
programmini di base.

#include<openssl/rsa.h>
Funzioni per generare le chiavi, cifrare, decifrare, firmare,
verificare dati.
-----------------------------
#include<openssl/pem.h>
Funzioni per leggere e scrivere strutture in formato PEM.
Noi le utilizzeremo per salvae le chiavi RSA nei files.
Ricordate openssl genrsa che salva le chiavi generate in formato PEM.
-----------------------------
#include<openssl/err.h>
Funzioni per accedere ai codici di errore generati dalle funzioni della
libreria openssl.
-----------------------------


1.2.2 RSA - Le chiavi

Vediamo la struttura di una chiave RSA:
struct
{
BIGNUM *n; //public modulus
BIGNUM *e; //public exponent
BIGNUM *d; //private exponent
BIGNUM *p; //secret prime factor
BIGNUM *q; //secret prime factor
BIGNUM *dmp1; // d mod (p-1)
BIGNUM *dmq1; // d mod (q-1)
BIGNUM *iqmp; // q^-1 mod p
};
RSA

Ovviamente nelle chiavi pubbliche l'esponente privato(d) e i relativi
valori segreti (p e q) sono NULL (per chiarirvi le idee potete andare
a rivedervi la trattazione matematica al paragrafo 1).
Nelle chiavi private p, q, dmp1, dmq1 e iqmp potrebbero anche essere
NULL, ma le operazioni effettuate usando questa chiave risulterebbero
piu' lente.
Ricordiamo infatti che per decifrare si usa solo d (guardatevi sempre
il par.1).

Vediamo come creare le chiavi.
Prima presentero' le funzioni necessarie e poi vedremo un semplice
programma per creare le nostre chiavi.
Inanzitutto dobbiamo allocare e iniziare una struttura RSA.
Questo e' possibile con la funzione RSA_new():
---------------------------------------------------------------------
#include<openssl/rsa.h>
RSA * RSA_new(void);
Ritorna NULL se fallisce l'allocazione; l'errore puo' essere ottenuto
usando ERR_get_error() (spiegata piu' avanti).
Come potete vedere dal programma per crear le chiavi usando la funzione
RSA_generate_key() la struttura viene direttamente inizializzata.
---------------------------------------------------------------------
RSA_free() libera la memoria occupata dalla struttura RSA, ma prima di
fare cio' cancella la chiave.
#include <openssl/rsa.h>
RSA_free(RSA *rsa);
Ricordatevi di liberare sempre la memoria occupata dalle vostre chiavi.
---------------------------------------------------------------------
Per creare le chiavi usiamo la funzione RSA_generate_key():
#include <openssl/rsa.h>
RSA *RSA_generate_key(int num, unsigned long e, void(*callback)
(int,int,void *), void *cb_arg);
int num: numero di bit. Come dice la man page e' meglio scegliere una
chiave >= di 1024 bit.
unsigned long e: e' l'esponente. Deve essere un numero dispari,
tipicamente si sceglie 3, 17 o 65537.
Se la funzione fallisce ritorna NULL. Il numero dell'errore puo'
essere ottenuto sempre con ERR_get_error().
---------------------------------------------------------------------
#include <openssl/rsa.h>
int RSA_check_key(RSA *rsa);
Questa funzione controlla se la chiave e' una vera chiave RSA.
Se e' corretta allora la funzione ritorna 1, se non e' una chiave
ritorna 0, mentre se la funzione fallisce ritorna -1.
---------------------------------------------------------------------
Ora che sappiamo come creare la nostra chiave vediamo come scrivere
la chiave privata in un file.
Possiamo usare una funzione presente in pem.h:
PEM_write_RSAPrivateKey():
#include <openssl/pem.h>
int PEM_write_RSAPrivateKey(FILE *fp,RSA *x,const EVP_CIPHER *enc,
unsigned char *kstr,int klen,pem_password_cb *cb,void *u);
---------------------------------------------------------------------
Mentre per scrivere la chiave pubblica useremo
PEM_write_RSAPublicKey():
#include <openssl/pem.h>
int PEM_write_RSAPublicKey(FILE *fp,RSA *x);
Tutte e due queste funzioni di scrittura ritornano 1 se hanno successo
e 0 se falliscono.
---------------------------------------------------------------------
Per leggere la chiave privata scritta in formato pem usiamo
PEM_read_RSAPrivateKey():
#include <openssl/pem.h>
RSA *PEM_read_RSAPrivateKey(FILE *fp,RSA **x,pem_password_cb *cb,
void *u);
---------------------------------------------------------------------
Per leggere la chiave pubblica usiamo PEM_read_RSAPublicKey():
#include <openssl/pem.h>
RSA *PEM_read_RSAPublicKey(FILE *fp,RSA **x,pem_password_cb *cb,
void *u);
Le funzioni di lettura ritornano un puntatore alla struttura letta o
NULL se c'e' un errore.
Settiamo enc, cb, kstr e u a NULL e poniamo klen a 0 poiche' non
necessitiamo di crittare le strutture PEM.
---------------------------------------------------------------------
Ora sappiamo come creare e salvare le nostre chiavi.
Prima di vedere un esempio pratico introduciamo anche le funzioni
per il controllo degli errori.
#include <openssl/err.h>
unsigned long ERR_get_error(void);
Questa funzione ritorna il codice dell'ultimo errore.
---------------------------------------------------------------------
#include <openssl/err.h>
char *ERR_error_string(unsigned long e, char *buf);
Genera una stringa "
human-readable".
Un utilizzo di queste due funzioni combinate e' questo:
printf("
%s\n",ERR_error_string(ERR_get_error(),NULL));
---------------------------------------------------------------------
Bene ora vediamo un piccolo esempio su come creare una chiave e
salvarla su dei files.
-----------------------------keygen.c--------------------------------
/*
Simple RSA Key generator. Saves keys in PEM format.
Coded by binduck - <binduck@coder.hu>
Compile: gcc -lssl keygen.c -o keygen
Usage: ./keygen <numbits>
Ex. ./keygen 1024

*/

#include <stdio.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#define SECFILE "
sec.pem"
#define PUBFILE "
pub.pem"
int main(int argc, char *argv[])
{
RSA *key;
FILE *fp;
int keylen=0;
if(argc!=2)
{
fprintf(stderr,"
Error: too many/few arguments.\n "
"
Usage: %s <numbits>\n",argv[0]);
exit(0);
}
keylen = atoi(argv[1]);
if((key = RSA_generate_key(keylen,3,NULL,NULL)) == NULL)
{
fprintf(stderr,"
%s\n",ERR_error_string(ERR_get_error(),NULL));
exit(-1);
}
if(RSA_check_key(key) < 1)
{
fprintf(stderr,"
Error: Problems while generating RSA Key.\nRetry.\n");
exit(-1);
}
fp=fopen(SECFILE,"
w");
if(PEM_write_RSAPrivateKey(fp,key,NULL,NULL,0,0,NULL) == 0)
{
fprintf(stderr,"
Error: problems while writing RSA Private Key.\n");
exit(-1);
}
fclose(fp);
fp=fopen(PUBFILE,"
w");
if(PEM_write_RSAPublicKey(fp,key) == 0)
{
fprintf(stderr,"
Error: problems while writing RSA Public Key.\n");
exit(-1);
}
fclose(fp);
RSA_free(key);
printf("
RSA key generated.\nLenght = %d bits.\n",keylen);
return 0;
}

---------------------------------------------------------------------
Per compilare digitate: gcc -lssl keygen.c -o keygen
Usage: ./keygen <numbits>
binduck@fuzzy:~$ ./keygen 1024
Eseguendo il programma, questo ci creera' due files 'pub.pem' e
'sec.pem' in cuiverranno salvate le nostre chiavi RSA.
Ovviamente potrete specificare la lunghezza da voi desiderata
(Es. 64 o 512 o 2048).
Ecco come risulterebbe una chiave privata a 1024 bit generata.

-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDWxE5+qNqE20NQWqRi71vkqroI+52CgzGmKv42BsE6kk1QiVSS
9Qm75bKfEP7Jz83NO/YO5DPHp6ZVr38nY6HCY3Ta/fNU0O/xNE3UiOQSK8dn7lbw
7itshIwjPpK09cgL6wos4Sm+lcHjdRk5DyTKcRNadWyXSxNsdrR5FWtImwIBAwKB
gQCPLYmpxecDPNeK5xhB9Oftxyawp75XAiEZcf7Oryt8YYjgW423TgZ9Q8xqC1SG
iokzfU60mCKFGm7jylTE7RaAXuiq84NDz71wmFjOgqGzePHl4CH5WnPi0GTwfssI
cEBr5WjUu2DTNKAUdk+1jLJFXFY6UIws6tzZB1SPNNagewJBAPAvdSm765vsMcOL
zfIks0rMpx9c4QZrXAgm+IoAN3EQOvZ9KNAK1xkUYDPJEyJ93GY5ZjyPaFnM9t+x
n97l1r8CQQDk6GVm8oN9Z5aMPNDSzNGrj+f+xxngEjxDzcH2YfDcVy8cb8T4Daqt
vWSP2JWZm4YAWJzACuuQ+zAMBAJnQ4ElAkEAoB+jcSfyZ/LL17KJTBh3hzMaFOiW
BEeSsBn7BqrPoLV8pFNwirHku2LqzTC3bFPoRCZEKF+a5oiklSEVP0PkfwJBAJia
7kShrP5FDwgoizczNnJf7/8vZpVhfYKJK/mWoJLkyhL1Lfqzxx5+QwqQY7u9BAA7
Eyqx8mCndV1YAZotAMMCQQDYZg4eFgmTJuq/J/Ls7NusdBxuwh0fKRt2KPhCZw5r
5mEeCDflQ3ATBjeUt6Z77JG0aEmQUq5NOqd/ju1VtBst
-----END RSA PRIVATE KEY-----


Ed ecco la chiave pubblica relativa:

-----BEGIN RSA PUBLIC KEY-----
MIGHAoGBANbETn6o2oTbQ1BapGLvW+Squgj7nYKDMaYq/jYGwTqSTVCJVJL1Cbvl
sp8Q/snPzc079g7kM8enplWvfydjocJjdNr981TQ7/E0TdSI5BIrx2fuVvDuK2yE
jCM+krT1yAvrCizhKb6VweN1GTkPJMpxE1p1bJdLE2x2tHkVa0ibAgED
-----END RSA PUBLIC KEY-----


1.2.3 Cifrare/Decifrare

Nell'header openssl/rsa.h troviamo le funzioni di cui abbiamo
bisogno: RSA_private_decrypt() e RSA_public_encrypt().
---------------------------------------------------------------------
#include <openssl/rsa.h>
int RSA_public_encrypt(int flen, unsigned char *from,
unsigned char *to,RSA *rsa,int padding);
unsigned char *from: stringa da crittografare.
unsigned char *to: buffer dove la funzione mette l'output
crittografato.
RSA *rsa: chiave pubblica RSA.
Come padding impostiamo RSA_PKCS1_OAEP_PADDING
Ritorna la grandezza dei dati crittografati.
---------------------------------------------------------------------
int RSA_private_decrypt(int flen,unsigned char *from,unsigned char
*to,RSA *rsa,int padding);
unsigned char *from: stringa da decrittare.
unsigned char *to: buffer che conterra' l'output in chiaro.
RSA *rsa: chiave privata RSA.
Come padding impostiamo sempre RSA_PKCS1_OAEP_PADDING.
Ritorna la grandezza del testo in chiaro.
---------------------------------------------------------------------
Bene ora vediamo un programmino che cifra e decifra il contenuto di
un file.

RICORDO CHE STIAMO USANDO DIRETTAMENTE L'ALGORITMO RSA, QUINDI
POSSIAMO INTERVENIRE SU UNA PICCOLA QUANTITA' DI DATI(ricordate
rsautl nella prima parte dell'articolo?).

--------------------------file_crypt.c-------------------------------
/*

  


Simple RSA encrypting/decrypting tool.
Coded by binduck - <binduck@coder.hu>
Compile: gcc -lssl file_crypt.c -o file_crypt
Usage: ./file_crypt -h for help

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#define SECFILE "sec.pem"
#define PUBFILE "pub.pem"
#define HELP 'h'
#define GENKEY 'g'
#define CIPHER 'c'
#define DECIPHER 'd'

RSA* readpemkeys(int type);
void genkey(int size);
int main(int argc,char *argv[])
{
int ch,size=0,len=0,ks=0;
FILE *fp;
RSA *key;
char char_opt[] = {HELP,GENKEY,CIPHER,DECIPHER};
unsigned char *out,*in,*mex;

printf("RSA Cipher/Decipher Signer/Verifier.\n");
printf("Coded by Binduck - <binduck@coder.hu>\n\n");
if (argc == 1)
{
printf("%s -h for help.\n",argv[0]);
}
while((ch=getopt(argc,argv,char_opt)) != -1)
{
switch(ch)
{
case HELP:
printf(" %s -g <num_bits> :generates RSA keys and save them "
"in PEM format.(1024 or 2048 are strongly suggested).\n",argv[0]);
printf(" %s -c <file_in> <file_out> :crypt datas in 'file_in' "
"and stores output in 'file_out'.\n",argv[0]);
printf(" %s -d <file_in> <file_out> :decript datas in 'file_in' "
"and stores output in 'file_out'.\n",argv[0]);
break;

case GENKEY:
if(argc != 3)
{
fprintf(stderr,"Error: check arguments.\n%s -h for "
"help.\n",argv[0]);
exit(0);
}
printf("Generating RSA keys...\n");
size=atoi(argv[2]);
printf("%d bits.\n",size);
genkey(size);
printf("Private Key saved in %s file.\n",SECFILE);
printf("Public Key saved in %s file.\n",PUBFILE);
printf("Done.\n");
break;

case CIPHER:
if(argc != 4)
{
fprintf(stderr,"Error: check arguments.\n%s -h for "
"help.\n",argv[0]);
exit(0);
}
key = readpemkeys(0);
if(!(fp = fopen(argv[2],"r")))
{
fprintf(stderr,"Error: No input file.\n");
exit(-1);
}
ks = (RSA_size(key)/2);
if((mex = (char *) malloc(ks)) == NULL)
{
fprintf(stderr,"Error: malloc()\n");
}
memset(mex,'\0',ks);
fread(mex,1,ks,fp);
fclose(fp);
if((out = (unsigned char*)malloc(RSA_size(key))) == NULL)
{
fprintf(stderr,"Error: malloc()\n");
exit(-1);
}
memset(out,'\0',RSA_size(key));
len = strlen(mex)*(sizeof(char));
printf("Encrypting max %d bytes from '%s' file.\n",ks,argv[2]);
if(RSA_public_encrypt(len,mex,out,key,RSA_PKCS1_OAEP_PADDING)==-1)
{
fprintf(stderr,"%s\n",ERR_error_string(ERR_get_error(),NULL));
exit(-1);
}
fp = fopen(argv[3],"w");
fwrite(out,1,strlen(out),fp);
fclose(fp);
RSA_free(key);
free(out);
free(in);
printf("Done.\n");
break;

case DECIPHER:
if(argc != 4)
{
fprintf(stderr,"Error: check arguments.\n%s -h for "
"help.\n",argv[0]);
exit(0);
}
key = readpemkeys(1);
if((in = (unsigned char*)malloc(RSA_size(key))) == NULL)
{
fprintf(stderr,"Error: malloc()\n");
exit(-1);
}
memset(in,'\0',RSA_size(key));
fp = fopen(argv[2],"r");
fread(in,1,RSA_size(key),fp);
fclose(fp);
if((out = (unsigned char*)malloc(RSA_size(key)))== NULL)
{
fprintf(stderr,"Error: malloc()\n");
exit(-1);
}
memset(out,'\0',RSA_size(key));
printf("Decrypting '%s' file.\n",argv[2]);
if(RSA_private_decrypt(RSA_size(key),in,out,key,
RSA_PKCS1_OAEP_PADDING) == -1)
{
fprintf(stderr,"%s\n",ERR_error_string(ERR_get_error(),NULL));
exit(-1);
}
fp = fopen(argv[3],"w");
fprintf(fp,"%s",out);
fclose(fp);
free(out);
RSA_free(key);
printf("Done.\n");

}
}
}
void genkey(int size)
{
RSA *key=NULL;
FILE *fp;
if((key = RSA_generate_key(size,3,NULL,NULL)) == NULL)
{
fprintf(stderr,"%s\n",ERR_error_string(ERR_get_error(),NULL));
exit(-1);
}
if(RSA_check_key(key) < 1)
{
fprintf(stderr,"Error: Problems while generating RSA Key.\n "
"Retry.\n");
exit(-1);
}
fp=fopen(SECFILE,"w");
if(PEM_write_RSAPrivateKey(fp,key,NULL,NULL,0,0,NULL) == 0)
{
fprintf(stderr,"Error: problems while writing RSA Private Key.\n");
exit(-1);
}
fclose(fp);
fp=fopen(PUBFILE,"w");
if(PEM_write_RSAPublicKey(fp,key) == 0)
{
fprintf(stderr,"Error: problems while writing RSA Public Key.\n");
exit(-1);
}
fclose(fp);
RSA_free(key);
printf("RSA keys generated.\n");
}

RSA* readpemkeys(int type)
{
FILE *fp;
RSA *key=NULL;
if(type == 0)
{
if((fp = fopen(PUBFILE,"r")) == NULL)
{
fprintf(stderr,"Error: Public Key file doesn't exists.\n");
exit(-1);
}
if((key = PEM_read_RSAPublicKey(fp,NULL,NULL,NULL)) == NULL)
{
fprintf(stderr,"Error: problems while reading Public Key.\n");
exit(-1);
}
fclose(fp);
return key;
}
if(type == 1)
{
if((fp = fopen(SECFILE,"r")) == NULL)
{
fprintf(stderr,"Error: Private Key file doesn't exists.\n");
exit(-1);
}
if((key = PEM_read_RSAPrivateKey(fp,NULL,NULL,NULL)) == NULL)
{
fprintf(stderr,"Error: problmes while reading Private Key.\n");
exit(-1);
}
fclose(fp);
if(RSA_check_key(key) == -1)
{
fprintf(stderr,"Error: Problems while reading RSA Private Key in %s "

"file.\n",SECFILE);
exit(-1);
}
else if(RSA_check_key(key) == 0)
{
fprintf(stderr,"Error: Bad RSA Private Key readed in %s "
"file.\n",SECFILE);
}
else return key;
}
}
---------------------------------------------------------------------
readpemkeys() e' un esempio di come e' possibile leggere le chiavi
salvate in formato PEM.
Il codice e' molto semplice e quindi non ritengo necessario commenti.

E con questo programmino io avrei terminato questo articolo, che spero
vi abbia chiarito le idee su RSA e openssl.
Per eventuali chiarimenti potete scrivermi a binduck@coder.hu

Grazie a tutti e alla prossima.

binduck


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x05][C0DiNG] DiAL0G0 S0PRA i DUE MASSiMi FiLESYSTEM [eazy]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

1. Scopo del gioco

2. Il filesystem
2.1 Struttura del filesystem
2.2 Strutture dati

3. Funzionamento di Suxfs

4. suxfs.c


1. Scopo del gioco

Questo articolo si pone l'obiettivo di illustrare come sia possibile
scrivere un semplice filesystem fatto in casa.
Lo scopo di questo progetto è quello di permettere al lettore di
analizzare la struttura e il funzionamento di un filesystem semplificato,
nonchè permettere al sottoscritto di superare l'esame di sistemi
operativi :)

Il filesystem in questione ci permette di memorizzare porzioni di dati in
file organizzati in una struttura gerarchica a directory secondo la
convenzione adottata dai sistemi Unix.
E' inoltre possibile effettuare altre operazioni elementari quali la
rimozione di file e directory o ottenere la lista dei file presenti nel
filesystem.


2. Il filesystem

Cos'è un filesystem?
Per filesystem si intende l'insieme delle strutture dati e i metodi di
accesso utilizzati per memorizzare e accedere alle informazioni
conservate in un dato supporto.

Perchè scrivere un proprio filesystem?
Per imparare come funziona, per venire a diretto contatto con le
problematiche inerenti la progettazione e l'implementazione di un
filesystem, per implementare delle funzionalità non previste.

Di cosa ho bisogno?
L'unica cosa indispensabile per implementare un proprio filesytem è una
discreta conoscenza di un linguaggio di programmazione, nel mio caso ho
utilizzato il C che si presta piuttosto bene per questo tipo di
applicazioni.


2.1 Struttura del filesystem

A questo punto eviterei di dilungarmi ulterioriormente in chiacchiere ma
passerei all'analisi della struttura che compone il nostro filesystem.
Per semplificare l'esposizione e facilitare il lettore nella comprensione
dell'argomento ho scelto un approcio basato su esempi.
D'ora in avanti ogni componente del filesystem verrà rappresentato per
mezzo di grafica ASCII secondo un preciso modello logico che mi appresto
a descrivere.

Possiamo suddividere il filesystem in questione in quattro componenti
principali, ognuno dei quali ricopre una particolare funzione. Segue una
breve descrizione di tali componenti accompagnata dalla rappresentazione
grafica del proprio modello logico:

1) puntatore al primo inode libero: la sua funzione è quella di indicare
la posizione del primo inode libero all'interno dell'array di inode;

_
|_|
^
|
|___ puntatore al primo
inode libero


2) array di inode: è un array di puntatori, ognuno dei quali può
puntare al prossimo inode libero nell'array oppure ad una directory
oppure ancora ad un file;

_______________________
|__|__|__|__|__|__|__|__|
^
|
|___ array di inode


3) mappa blocchi: è un array di interi, ogni elemento indica se il
relativo blocco è libero o occupato, la mappa è composto da un numero di
elementi pari al numero di blocchi, di conseguenza l'n-esimo elemento
della mappa si riferisce allo stato dell'n-esimo blocco del filesystem;

_________________________
|_|_|_|_|_|_|_|_|_|_|_|_|_|
^
|
|___ mappa blocchi


4) array di blocchi: è un array di elementi che possono contenere dei
dati (es. parte del contenuto di un file) oppure la struttura di una
directory oppure ancora un array di puntatori ad altri blocchi;

_____________________________
|____|____|____|____|____|____|
^
|
|___ array di blocchi


Non posso negare il fatto che la descrizione precedente sia alquanto
imprecisa tuttavia ho preferito semplificare le cose per poi analizzarle
nel dettaglio qualora ve ne fosse bisogno.

Iniziamo analizzando la funzione init() alla quale viene delegato il
compito di inizializzare l'intera struttura del nostro filesystem.


/*
Provvede all'inizializzazione delle strutture fondamentali
del filesystem
*/


int init(char *file){

int fd, i;
Ptr_Inode free_inode;
Map map;
Inode inode[INODE_NO];
Block blocchi[BLOCK_NO];

if( (fd = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) < 0){
printf("open error\n");
exit(0);
}

/*
Inizializzo il puntatore al primo inode libero.
inode[0] contiene il puntatore al directory block relativo alla
root directory e risulta pertanto occupato, inode[1] e' invece
libero
*/

free_inode = 1;
lseek(fd, 0, SEEK_SET);
if(write(fd, &free_inode, PTR_SIZE) < 0){
printf("write error\n");
exit(0);
}
.
.
.


Come prima cosa questa funzione apre il file che conterrà il nostro
filesystem virtuale (e lo crea se necessario) e che verrà utilizzato per
contenerne la struttura e i dati che lo popoleranno.
Successivamente impostiamo il valore del puntatore al primo inode libero,
tale valore è pari a 1 ovvero il secondo inode (a partire da 0) in quanto
il primo dovrà puntare al blocco contenente la directory radice del
nostro filesystem.
Come possiamo osservare in realtà il puntatore al primo inode altro non
è che un valore int che indica la posizione dell'inode all'intero del
file contenente la struttura del nostro filesystem.
Ne risulta che lo stato attuale del nostro filesystem possa essere
rappresentato in questo modo:

_
|1|
^
|
|___ puntatore al primo
inode libero


Prima di procede all'analisi della prossima porzione di codice è
necessaria una premessa, o meglio un approfondimento riguardo la
struttura dell'inode. Prendiamo il nostro array di inode:


_______________________
|__|__|__|__|__|__|__|__|
^
|
|___ array di inode


Ogni elemento che compone l'array è una union dichiarata in questo modo:

typedef union {
Ptr_Inode free;
Inode_Used used;
} Inode;

Pertanto, ogni inode può essere un puntatore al prossimo inode libero
(Ptr_Inode free) oppure un inode utilizzato (Inode_Used used).
Nel caso di un puntatore al prossimo inode libero avremmo qualcosa del
genere:

_______________________
|_1|__|__|__|__|__|__|__|
| ^
|___|


Nel caso, invece, in cui l'inode sia utilizzato si possono presentare
due differenti configurazioni a seconda che esso si riferisca ad un file
o ad una directory:

typedef struct {
double dimensione;
Ptr_Block ptr[FILE_PTR_NO];
Ptr_Block ptr_index;
} File;

typedef union {
File file;
Ptr_Block dir;
} Inode_Type;

typedef struct {
int flag;
Inode_Type type;
} Inode_Used;


Come possiamo vedere Inode_Used è una struttura composta da un intero e
da una union. La variabile di tipo intero flag ha la funzione di indicare
se l'inode utilizzato si riferisce ad un file (flag = 0) o ad una
directory (flag = 1).
L'union rappresentata dal campo type sarà acceduta a seconda del valore
assunto dalla variabile flag.
Di conseguenza il singolo inode potrà apparire come segue nel caso di una
directory:


array inode array blocchi
_______________________ _____________________________
|__|__|__|__|__|__|__|__| |____|____|____|____|____|____|
/ \ ^
/_____\ |
|__1__| flag |
|__0__| type.dir |
| |
|______________________________|




oppure nel seguente modo nel caso di un file:


array inode array blocchi
_______________________ _____________________________
|__|__|__|__|__|__|__|__| |____|____|____|____|____|____|
/ \ ^ ^ ^
/_____\ | | |
|__0__| flag | | |
|__x__| dimensione | | |
|__0__| ptr[0] ___________________| | |
|__1__| ptr[1] ________________________| |
~ ~ |
~_____~ |
|__N__| ptr[FILE_PTR_NO-1] ____________________________|
|_NULL| ptr_index



Torniamo a questo punto al nostro codice:


/*
Inizializzo la lista degli inode liberi
*/

bzero(inode, INODE_SIZE * INODE_NO);
for(i = 0; i < INODE_NO; i++)
inode[i].free = i + 1;

/*
Faccio puntare il primo inode al primo data block che conterra'
le directory entry relative alla root directory
*/

inode[0].used.flag = 1;
inode[0].used.type.dir = 0;
if(write(fd, inode, INODE_SIZE * INODE_NO) < 0){
printf("write error\n");
exit(0);
}
.
.
.


Vediamo che la funzione init() procede ora all'inizializzazione
dell'array di inode in modo tale che ognuno di essi punti al successivo
inode libero.
Inoltre, il primo inode viene utilizzato e viene fatto puntare al primo
blocco che conterrà la struttura della directory radice.
Lo stato attuale del nostro filesystem al termine dell'esecuzione di
queste poche righe di codice può essere riassunto così:


________
| |
| v__ __ __
| | | | | | |
|_ ___|__v__|__v__|__v____
|1| |__|_2|_3|_4|_5|_6|_7|_8|
/ \ | ^ | ^ | ^ ^
/_____\ |__| |__| |__| |
|__1__| flag |
|__0__| type.dir |
| |
|______________________________|



/*
Inizializzo a 0 il bitmap e pongo a 1 il bit relativo alla root dir
*/

bzero(map, MAP_SIZE);
map[0] = 1;
if(write(fd, map, MAP_SIZE) < 0){
printf("write error\n");
exit(0);
}
.
.
.


Viene creata la mappa dei blocchi, il primo elemento della mappa viene
posto a 1 per segnalare il fatto che il primo blocco dell'array di
blocchi sarà occupato dalla directory radice.



________
| |
| v__ __ __
| | | | | | |
|_ ___|__v__|__v__|__v____ _______
|1| |__|_2|_3|_4|_5|_6|_7|_8| |1|0|0|0|
/ \ | ^ | ^ | ^ ^
/_____\ |__| |__| |__| |
|__1__| flag |
|__0__| type.dir |
| |
|_______________________________________|



/*
Inizializzo il directory block relativo alla root directory.
Inserisco le entry relative alla current e alla parent dir
*/

bzero(blocchi, BLOCK_SIZE * BLOCK_NO);
strncpy(blocchi[0].dirent[0].filename, ".", FILENAME_LEN - 1);
blocchi[0].dirent[0].inode = 0;
strncpy(blocchi[0].dirent[1].filename, "..", FILENAME_LEN - 1);
blocchi[0].dirent[1].inode = 0;

/*
Inizializzo i restanti data block del filesystem
*/

if(write(fd, blocchi, BLOCK_SIZE * BLOCK_NO) < 0){
printf("write error\n");
exit(0);
}
}


Infine, alloco l'array di blocchi e inizializzo la directory radice
inserendo le due entry che costituiscono il default per ogni directory,
ovvero l'entry . (current dir) e .. (parent dir). E' importante notare
che la directory root rappresenta un caso paricolare inquanto la sua
directory parent è se stessa.


________
| |
| v__ __ __
| | | | | | |
|_ ___|__v__|__v__|__v____ _______ ___________________
|1| |__|_2|_3|_4|_5|_6|_7|_8| |1|0|0|0| |____|____|____|____|
/ \ | ^ | ^ | ^ / \
/_____\ |__| |__| |__| /______\
|__1__| flag |. __|0|____
|__0__| type.dir |.. _|0|____|_
| ^ ^ |____|_| | |
| | | ~ ~ | |
| | | ~______~ | |
| | | |____|_| | |
| | | |____|_| | |
| | | ^ | |
| | |_____________________________________|________| |
| |_______________________________________|__________|
| |
| |
|_________________________________________|



Le strutture fondamentali del nostro filesystem sono ora inizializzate a
dovere, non ci resta che popolare il sistema con i nostri dati.
Passiamo quindi all'analisi di una particolare porzione della funzione
copy() che provvede alla copia dei dati all'interno del filesystem:


/*
...altrimenti copia il file specificato nel filesystem virtuale
*/

else if(strncmp(dst, "VD:", 3) == 0){
dst+=3;
if((fd = open(src, O_RDONLY)) <= 0){
printf("open error\n");
exit(0);
}

/*
Ricava la directory genitore e il nome del file di destinazione
della copia
*/

dir = getParentFromPath(fs, dst, file);
.
.
.

La funzione getParentFromPath() prende in ingresso il pathname assoluto
della destinazione in cui si vuole copiare il file (dst) e restituisce
due valori: un puntatore all'inode di tale directory (dir) e il nome del
file di destinazione privato del proprio path (file).

/*
Controllo se esiste un file con lo stesso nome e interrompo la
copia nel caso la destinazione contenga gia' una voce per tale
file.
Il controllo se pur presente nella funzione newDirEntry() viene
eseguito anticipatamente per inibire l'allocazione di inode e data
block che poi risulterebbero inutilizzati
*/

checkIfExist(fs, dir, file);

/*
Scrive il contenuto del file nel filsystem virtuale
*/

inode = writeFileToInode(fs, fd);
.
.
.

Dopo aver controllato che non esista già un file con lo stesso nome nella
stessa directory, scrive il contenuto del file nell'array di blocchi e
memorizza il valore dell'inode restituito dalla funzione
writeFileToInode() ottenuto a sua volta con una chiamata a getFreeInode()
che restituisce il primo inode libero.

/*
Aggiunge una directory entry per il file appena creato nella
directory restituita come valore di ritorno da
getParentFromPath()
*/

newDirEntry(fs, dir, file, inode);
}
.
.
.

Infine viene aggiunta una nuova entry nella parent directory per il file
appena creato, l'entry sarà costituita da due valori: il filename e il
puntatore all'inode del file.
Lo stato attuale del nostro filesystem viene riassunto con maggiore
chiarezza nello schema seguente:


____________________________________________________________
| |
| |
__v__ |
|__0__| flag |
|__x__| dimensione |
|__0__| ptr[0] _______________________________ |
|__1__| ptr[1] _______________________________|____ |
~ ~ ___v____v____ |
~_____~ |conten|o nel | |
|_NULL| ptr[n] |uto de|filesy| |
|_NULL| ptr_index |l file|stem | |
_____| | __ __ |copiat|______| |
| \ /| | | | | \ / |
|_ __\__/_v_|__v__|__v____ _______ ____\_________/____ |
|2| |__|_2|_3|_4|_5|_6|_7|_8| |1|1|1|0| |____|____|____|____| |
/ \ | ^ | ^ | ^ / \ |
/_____\ |__| |__| |__| /______\ |
|__1__| flag |. __|0|____ |
|__0__| type.dir |.. _|0|____|_ |
| ^ ^ |file|1|____|_|___________|
| | | ~ ~ | |
| | | ~______~ | |
| | | |____|_| | |
| | | |____|_| | |
| | | ^ | |
| | |_____________________________________|________| |
| |_______________________________________|__________|
| |
| |
|_________________________________________|



2.2 Strutture dati

Ora che abbiamo osservato le modalità con le quali i dati vengono
memorizzati all'interno del filesystem possiamo passare all'analisi delle
strutture dati da esso utilizzate.

Come abbiamo avuto modo di vedere nel precedente paragrafo, durante la
fase di inizializzazione verrà istanziato un file contenente le strutture
dati fondamentali del nostro fs. La sua dimensione dipenderà da alcune
costanti definite all'interno del file sorgente:

#define BLOCK_NO 2048
il numero di blocchi presenti nel filesystem, tale valore va scelto a
seconda della quantità di dati che si desidera memorizzare;

#define INODE_NO 256
il numero di inode presenti nel filesystem, tale valore va scelto in
funzione del numero di file e directory che si intendono creare;

#define DATA_SIZE 1024
la dimensione massima di dati che ogni blocco è in grado di contenere,
tale valore va scelto in base alla dimensione media assunta dai file che
si desidera memorizzare nel filesystem.

Prima di procedere alla compilazione del sorgente possiamo intervenire su
tali parametri modificandoli in modo coerente. Possiamo inoltre calcolare
la dimensione richiesta per memorizzare il nostro filesystem così:

fs size = PTR_SIZE + INODE_NO * INODE_SIZE + MAP_SIZE +
+ BLOCK_NO * BLOCK_SIZE


Il filesytem usa il file come forma di astrazione per rappresentare i
dati che vengono memorizzati, questa convenzione logica permette di
attribuire un nome univoco ad ogni porzione di dati memorizzata in modo
da facilitarne la ricerca e la classificazione. Vediamo come si presenta
la struttura di un generico file:


#define FILE_PTR_NO 4
#define DATA_SIZE 1024
#define N 10

typedef char Data[DATA_SIZE];

typedef struct {
double dimensione;
Ptr_Block ptr[FILE_PTR_NO];
Ptr_Block ptr_index;
} File;

typedef union {
Ptr_Block indice[N];
Dirent dirent[DIR_ENTRY];
Data data;
} Block;

____________________________
| |
struct File | data data data data | data data data
_____ | _____________________v____________ _ _ _
|__0__| flag | |____|____|____|____|____|____|____|_ _ _
|__x__| dimensione | ^ ^ ^ ^ / \ ^ ^ ^
|__0__| ptr[0] _______|________| | | | /______\ | | |
|__1__| ptr[1] _______|_____________| | | |___5__|_| | |
|__2__| ptr[2] _______|__________________| | |___6__|_____| |
|__3__| ptr[4] _______|_______________________| |___7__|__________|
|__4__| ptr_index ____| |___8__|____________...
|___9__|____________...
|__10__|____________...
|__11__|____________...
|__12__|____________...
|__13__|____________...
|__14__|____________...

indice


Come possiamo vedere ogni file può indirizzare un massimo di quattordici
blocchi, quattro ad accesso diretto e dieci ad accesso indicizzato,
ognuno dei quali può contenere un massimo di 1024 byte.
Ne consegue che la grandezza massiama che può assumere un file nel nostro
filesystem può essere calocalta in questo modo:

max file size = (FILE_PTR_NO + N) * DATA_SIZE
= (4 + 10) * 1024
= 14 Kb

Nel sorgente originale tali costanti sono già state modificate a volori
più sensati in modo da poter ospitare file di almeno 220 Kb.
Tuttavia agendo sulle costanti FILE_PTR_NO, DATA_SIZE e N è possibile
adattare il filesystem alle proprie esigenze.


3. Funzionamento di Suxfs

Se volete testare sulla vostra macchina il funzionamento di Suxfs dovete
compilarlo con il comando:

$ gcc suxfs.c -o suxfs

Per prima cosa dovete provvedere all'inizializzazione del filesystem per
mezzo del comando:

$ ./suxfs -i


Terminata la fase di inizializzazione è possibile eseguire alcune
operazioni elementari come la copia di dati nel/dal filesystem,
provvedere alla creazione di file e directory e alla loro rimozione,
listare il contenuto dei file al suo interno.
Seguono alcuni esempi:


$ ./suxfs -c in.txt VD:/in.txt

Se richiamato con l'opzione -c il programma suxfs copia il file in.txt
nel filesystem virtuale che per convenzione viene rappresentato dalla
sigla VD: (virtual disk).


$ ./suxfs -c VD:/in.txt out.txt

Stessa cosa ma questa volta il file viene copiato dal filesystem virtuale
nel file out.txt.
E' necessario precisare che in entrambi i casi il nome del file di
destinazione NON può essere omesso.


$ ./suxfs -l VD:/
.
..
in.txt

Lo switch -l ci permette di listare il contenuto di una directory, in
questo caso la directory radice.


$ ./suxfs -m VD:/dirname

Con l'opzione -m possiamo creare una nuova directory.


$ ./suxfs -r VD:/in.txt

L'opzione -r, invece, ci permette di rimuovere un file o una directory
insieme a tutto il suo contenuto.


4. suxfs.c


/*
* suxfs.c: Semplice filesystem che runna in userspace.
*
*
* Copyright (c) 2003, eazy <eazy@ondaquadra.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of the Networks Associates Technology, Inc nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define BLOCK_NO 2048
#define INODE_NO 256
#define PTR_BLOCK_NULL BLOCK_NO
#define PTR_INODE_NULL INODE_NO
#define FILENAME_LEN 15
#define PATHNAME_LEN 255
#define DIR_ENTRY 128
#define FILE_PTR_NO 10
#define DATA_SIZE 1024
#define N 100

typedef int Ptr_Block;
typedef int Ptr_Inode;

typedef struct {
double dimensione;
Ptr_Block ptr[FILE_PTR_NO];
Ptr_Block ptr_index;
} File;

typedef union {
File file;
Ptr_Block dir;
} Inode_Type;

typedef struct {
int flag;
Inode_Type type;
} Inode_Used;

typedef union {
Ptr_Inode free;
Inode_Used used;
} Inode;

typedef int Map[BLOCK_NO];

typedef struct {
char filename[FILENAME_LEN];
Ptr_Inode inode;
} Dirent;

typedef char Data[DATA_SIZE];

typedef union {
Ptr_Block indice[N];
Dirent dirent[DIR_ENTRY];
Data data;
} Block;

#define PTR_SIZE sizeof(Ptr_Block)
#define BLOCK_SIZE sizeof(Block)
#define INODE_SIZE sizeof(Inode)
#define MAP_SIZE sizeof(Map)


void moveToBlock(int fd, Ptr_Block ptr){

lseek(fd, PTR_SIZE + INODE_SIZE * INODE_NO + MAP_SIZE
+ BLOCK_SIZE * ptr, SEEK_SET);

}

void moveToInode(int fd, Ptr_Inode ptr){

lseek(fd, PTR_SIZE + INODE_SIZE * ptr, SEEK_SET);

}

void moveToMap(int fd){

lseek(fd, PTR_SIZE + INODE_SIZE * INODE_NO, SEEK_SET);

}

/*
Restituisce un puntatore al primo data block libero e aggiorna il
relativo bit nella mappa dei blocchi. Se non sono data block liberi
restituisce -1
*/


Ptr_Block getFreeBlock(int fd){

int r;
Ptr_Block i;
Map bitmap;

moveToMap(fd);
if( (r = read(fd, bitmap, MAP_SIZE)) <= 0){
printf("read error getFreeBlock: %d\n", r);
exit(0);
}
for(i = 0; i < BLOCK_NO; i++)
if(bitmap[i] == 0)
break;
if(i < BLOCK_NO){
bitmap[i] = 1;
moveToMap(fd);
if(write(fd, bitmap, MAP_SIZE) <= 0){
printf("write error\n");
exit(0);
}
return i;
}
else
return -1;

}

/*
Restituisce un puntatore al primo inode libero e aggiorna la lista
degli inode in maniera coerente. Se non ci sono inode liberi
restituisce -1
*/


Ptr_Inode getFreeInode(int fd){

Ptr_Inode ptr;
Inode inode;

lseek(fd, 0, SEEK_SET);
if(read(fd, &ptr, PTR_SIZE) <= 0){
printf("read error getFreeInode\n");
exit(0);
}
if(ptr != PTR_INODE_NULL){
moveToInode(fd, ptr);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error getFreeInode\n");
exit(0);
}
lseek(fd, 0, SEEK_SET);
if(write(fd, &inode.free, PTR_SIZE) <= 0){
printf("write error\n");
exit(0);
}
return ptr;
}
else
return -1;

}

/*
Verifica se esiste un dato filename esiste in una directory.
L'argomento dir specifica l'inode della directory, mentre filename
e' un puntatore al nome del file di cui eseguire il check
*/


void checkIfExist(int fd, Ptr_Inode dir, char *filename){

int i;
Inode inode;
Block blocco;

moveToInode(fd, dir);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error checkIfExist\n");
exit(0);
}

/*
Verifico che il secondo argomento passato alla funzione sia
effettivamente una directory, in caso contrario restituisco un
errore e termino il programma
*/

if(inode.used.flag == 1){
moveToBlock(fd, inode.used.type.dir);
}
else{
printf("checkIfExist: Impossibile creare il file\n");
exit(0);
}
if(read(fd, &blocco, BLOCK_SIZE) <= 0){
printf("read error checkIfExist\n");
exit(0);
}

/*
Se esiste una directory entry il cui nome corrisponde a quello
cercato restituisco un messaggio di errore e termino il programma al
fine di garantire la coerenza del filesystem
*/

for(i = 0; i < DIR_ENTRY; i++){
if(strcmp(blocco.dirent[i].filename, filename) == 0){
printf("%s esiste gia'\n", filename);
exit(0);
}
}
}


/*
Dato in ingresso un pathname assoluto resituisce un puntatore
all'inode del file specificato da tale pathname. Il terzo argomento
passato alla funzione consiste in un valore di ritorno che
restituisce il nome del file privato del proprio path, l'allocazione
dello spazio necessario ad ospitare il valore di ritorno deve
avvenire ad opera del chiamante
*/


Ptr_Inode getFileFromPath(int fd, char *pathname, char *filename){

int i;
Ptr_Inode ptr = 0;
char *str, path[PATHNAME_LEN], token[FILENAME_LEN];
Block blocco;
Inode inode;

strncpy(path, pathname, PATHNAME_LEN - 1);

/*
Elimina da path la prima occorrenza del carattere slash e ritorna
il resto della stringa fino al prossimo slash o al fine stringa.
Se path e' composto da un singolo carattere slash (root directory)
o e' una stringa nulla la funzione ritorna ptr = 0 ovvero l'inode
della directory root
*/

if( (str = strtok(path, "/")) == NULL)
return ptr;

strncpy(token, str, FILENAME_LEN - 1);
moveToInode(fd, ptr);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error getFileFromPath\n");
exit(0);
}

/*
Verifica che l'inode si riferisca effettivamente ad una directory
*/

if(inode.used.flag == 1)
moveToBlock(fd, inode.used.type.dir);
else{
printf("Non e' una directory\n");
exit(0);
}
if(read(fd, &blocco, BLOCK_SIZE) <= 0){
printf("read error getFileFromPath\n");
exit(0);
}

/*
Cerca nella directory root se esiste una directory entry relativa
al token ottenuto dal parsing effettuato da strtok() sul pathname
*/

for(i = 0; i < DIR_ENTRY; i++){

/*
Se esiste ne salva il valore dell'inode in ptr
*/

if(strcmp(token, blocco.dirent[i].filename) == 0){
ptr = blocco.dirent[i].inode;
break;
}
}
if(i == DIR_ENTRY){
printf("getFileFromPath: Path errato\n");
exit(0);
}

/*
Chiama strtok() fino a che path contiene ulteriori token da parsare
e ne copia il valore di ritorno nella variabile token.
All'uscita dal while ptr puntera' all'inode del file che stiamo
cercando e token ne conterra' il nome
*/

while( (str = strtok(NULL, "/")) != NULL &&
strncpy(token, str, FILENAME_LEN - 1)){
moveToInode(fd, ptr);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error getFileFromPath\n");
exit(0);
}
if(inode.used.flag == 1)
moveToBlock(fd, inode.used.type.dir);
else{
printf("Non e' una directory\n");
exit(0);
}
if(read(fd, &blocco, BLOCK_SIZE) <= 0){
printf("read error getFileFromPath\n");
exit(0);
}

/*
Cerca nella directory puntata da ptr se esiste una directory entry
relativa al token ottenuto dal parsing effettuato da strtok() sul
pathname
*/

for(i = 0; i < DIR_ENTRY; i++){

/*
Se esiste ne salva il valore dell'inode in ptr
*/

if(strcmp(token, blocco.dirent[i].filename) == 0){
ptr = blocco.dirent[i].inode;
break;
}
}
if(i == DIR_ENTRY){
printf("getFileFromPath: Path errato\n");
exit(0);
}
}

/*
Copia nella variabile filename il token corrispondente al file
specificato da pathname privato del proprio percorso assoluto
*/

strncpy(filename, token, FILENAME_LEN - 1);

/*
Vado a NULL terminare la stringa nel caso sia stata troncata
a FILENAME_LEN - 1
*/

filename[FILENAME_LEN - 1] = 0;

return ptr;

}


/*
Dato in ingresso un pathname assoluto resituisce un puntatore
all'inode della directory genitore in cui risiede il file specificato
da tale pathname. Il terzo argomento passato alla funzione consiste in
un valore di ritorno che restituisce il nome del file privato del
proprio path, l'allocazione dello spazio necessario ad ospitare il
valore di ritorno deve avvenire ad opera del chiamante
*/


Ptr_Inode getParentFromPath(int fd, char *pathname, char *filename){

int i;
Ptr_Inode ptr = 0;
char *str, path[PATHNAME_LEN], token[FILENAME_LEN],
token_next[FILENAME_LEN];
Block blocco;
Inode inode;

strncpy(path, pathname, PATHNAME_LEN - 1);

/*
Elimina da path la prima occorrenza del carattere slash e ritorna
il resto della stringa fino al prossimo slash o al fine stringa.
Se path e' composto da un singolo carattere slash (root directory)
o e' una stringa nulla la funzione ritorna ptr = 0 ovvero l'inode
della directory root
*/

if( (str = strtok(path, "/")) == NULL){
printf("strtok error\n");
exit(0);
}

/*
Salva il valore del token ottenuto dalla prima chiamata a strtok()
*/

strncpy(token, str, FILENAME_LEN - 1);

/*
Chiama strtok() fino a che path contiene ulteriori token da parsare
e ne copia il valore di ritorno nella variabile token_next.
All'uscita dal while ptr puntera' all'inode della directory genitore
del file che stiamo cercando e token conterra' il nome di tale file
*/

while( (str = strtok(NULL, "/")) != NULL &&
strncpy(token_next, str, FILENAME_LEN - 1)){
moveToInode(fd, ptr);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error parsePath\n");
exit(0);
}
if(inode.used.flag == 1)
moveToBlock(fd, inode.used.type.dir);
else{
printf("Non e' una directory\n");
exit(0);
}
if(read(fd, &blocco, BLOCK_SIZE) <= 0){
printf("read error parsePath\n");
exit(0);
}

/*
Cerca nella directory puntata da ptr se esiste una directory entry
relativa al token ottenuto dalla chiamata a strtok() di DUE volte
fa. Il valore di ritorno dell'ultima chiamata a strtok() si trova
in token_next mentre il valore di ritorno della chiamata
precedente a strtok() si trova in token ed e' di tale valore che
ci serviamo per il confronto.
All'uscita dal while ptr conterra' il valore dell'inode della
directory genitore del file specificato da pathname e token
conterra' il nome di tale file
*/

for(i = 0; i < DIR_ENTRY; i++){

/*
Se esiste si sposta al valore specificato dal relativo inode
*/

if(strcmp(token, blocco.dirent[i].filename) == 0){
moveToInode(fd, blocco.dirent[i].inode);
break;
}
}
if(i < DIR_ENTRY){
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error parsePath\n");
exit(0);
}

/*
Il fatto che token_next non sia NULL ci assicura che il valore
di token si riferisce per forza di cose ad una directory.
Di consegueza verifichiamo che l'inode si riferisca
effettivamente ad una directory e ne salviamo il valore in ptr
*/

if(inode.used.flag == 1){
ptr = blocco.dirent[i].inode;
}
else{
printf("Non e' una directory\n");
exit(0);
}
}
else{
printf("getParentFromPath: Path errato\n");
exit(0);
}

/*
Copiamo in token il valore di token_next ottenuto dall'ultima
chiamata a strtok()
*/

strncpy(token, token_next, FILENAME_LEN - 1);
}
if(filename != NULL){

/*
Copia nella variabile filename il token corrispondente al file
specificato da pathname privato del proprio percorso assoluto
*/

strncpy(filename, token, FILENAME_LEN - 1);

/*
Vado a NULL terminare la stringa nel caso sia stata troncata
a FILENAME_LEN - 1
*/

filename[FILENAME_LEN - 1] = 0;
}

return ptr;

}

/*
Provvede alla creazione di una nuova directory. Viene passato come
secondo argomento alla funzione un puntatore all'inode della directory
genitore e restituisce come valore di ritorno un puntatore al proprio
inode ottenuto tramite una chiamata a getFreeInode()
*/


Ptr_Inode writeDirToInode(int fd, Ptr_Inode ptr_parent){

Ptr_Inode ptr;
Inode inode;
Block blocco;

/*
Inizializza il campo flag (file = 0, directory = 1) e il puntatore
al data block contentente le directory entry
*/

inode.used.flag = 1;
if( (inode.used.type.dir = getFreeBlock(fd)) < 0){
printf("No free block\n");
exit(0);
}

/*
Ottiene un inode libero (se esiste), altrimenti restituisce un
errore e termina il programma
*/

if( (ptr = getFreeInode(fd)) < 0){
printf("No free inode\n");
exit(0);
}

/*
Crea le directory entry relative alla directory corrente (.) e alla
directory genitore (..) e le inizializza ai relativi valori di inode
*/

bzero(&blocco, sizeof(Block));
strncpy(blocco.dirent[0].filename, ".", FILENAME_LEN);
blocco.dirent[0].inode = ptr;
strncpy(blocco.dirent[1].filename, "..", FILENAME_LEN);
blocco.dirent[1].inode = ptr_parent;

/*
Scrive le modifiche alla lista degli inode e al nuovo data block
allocato su file
*/

moveToInode(fd, ptr);
if(write(fd, &inode, INODE_SIZE) <= 0){
printf("write error writeDirToInode\n");
exit(0);
}
moveToBlock(fd, inode.used.type.dir);
if(write(fd, &blocco, BLOCK_SIZE) <= 0){
printf("write error writeDirToInode\n");
exit(0);
}
return ptr;

}

/*
Rimuove il file o la directory (e tutto il suo contenuto) puntato da
filename. La variabile parent contiene il valore dell'inode della
directory genitore nella quale e' contenuto il file o la directory
da rimuovere
*/


void rm(int fd, Ptr_Inode parent, char *filename){

int i, count;
Map map;
Ptr_Inode ptr, ptr_first;
Ptr_Block parent_block;
Inode inode, inode_list[INODE_NO];
Block blocco, index, blocco_rm;

moveToInode(fd, parent);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}

/*
Salvo il puntatore al blocco della directory genitore contenente
la directory entry relativa al file o alla directory da rimuovere
*/

parent_block = inode.used.type.dir;

/*
Controllo che il valore specificato come inode della directory
genitore si riferisca effettivamente ad una directory
*/

if(inode.used.flag == 1){
moveToBlock(fd, parent_block);
if(read(fd, &blocco, BLOCK_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}

/*
Cerco il file da rimuovere tra tutte le directory entry della
directory genitore fornita come argomento della funzione.
*/

for(i = 0; i < DIR_ENTRY; i++)

/*
Se il file o la directory da rimuovere esiste ne salvo il valore
dell'inode e azzero la directory entry relativa a tale file
*/

if(strcmp(blocco.dirent[i].filename, filename) == 0){
ptr = blocco.dirent[i].inode;
strncpy(blocco.dirent[i].filename, "", FILENAME_LEN - 1);
blocco.dirent[i].inode = 0;
break;
}
if(i < DIR_ENTRY){
moveToInode(fd, ptr);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}

/*
Verifico se l'elemento da rimuovere e' un file oppure una
directory
*/

if(inode.used.flag == 0){ /* se e' un file... */
moveToMap(fd);
if(read(fd, &map, MAP_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}

/*
Vado a marcare come libero nella mask ogni data block puntato
dall'indice dei blocchi diretti
*/

count = 0;
while(count < FILE_PTR_NO &&
inode.used.type.file.ptr[count] != PTR_BLOCK_NULL){
map[inode.used.type.file.ptr[count]] = 0;
count++;
}

/*
Vado a marcare come libero nella mask ogni data block puntato
dall'indice dei blocchi indiretti
*/

if(count == FILE_PTR_NO &&
inode.used.type.file.ptr_index != PTR_BLOCK_NULL){
moveToBlock(fd, inode.used.type.file.ptr_index);
if(read(fd, &index, BLOCK_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}
i = 0;
while(i < N && index.indice[i] != PTR_BLOCK_NULL){
map[index.indice[i]] = 0;
i++;
}

/*
Dealloco il data block contenente l'indice ai blocchi
indiretti
*/

map[inode.used.type.file.ptr_index] = 0;
}

  
}

/* altrimenti se e' una directory... */
else if(inode.used.flag == 1){
moveToBlock(fd, inode.used.type.dir);
if(read(fd, &blocco_rm, BLOCK_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}

/*
Richiamo ricorsivamente la funzione rm() per ogni directory
entry della directory da rimuovere. Skippo i valori nulli e
quelli relativi alla directory corrente e a quella genitore
*/

for(i = 0; i < DIR_ENTRY; i++)
if(strcmp(blocco_rm.dirent[i].filename, "") != 0 &&
strcmp(blocco_rm.dirent[i].filename, ".") != 0 &&
strcmp(blocco_rm.dirent[i].filename, "..") != 0)
rm(fd, ptr, blocco_rm.dirent[i].filename);
moveToMap(fd);
if(read(fd, &map, MAP_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}

/*
Vado a marcare come libero il data block relativo alla
directory rimossa
*/

map[inode.used.type.dir] = 0;
}
else{
printf("Tipo file non supportato\n");
exit(0);
}

/*
Provvedo ad aggiornare in maniera coerente la lista degli inode
liberi inserendo l'inode liberatosi in seguito alla rimozione
del file
*/

lseek(fd, 0, SEEK_SET);
if(read(fd, &ptr_first, PTR_SIZE) <= 0){
printf("read error rm\n");
exit(0);
}
moveToInode(fd, 0);
if(read(fd, &inode_list, INODE_SIZE * INODE_NO) <= 0){
printf("read error rm\n");
exit(0);
}

/*
Se l'inode che ho liberato in seguito alla rimozione del file
precede il primo inode libero nella lista, eseguo un inserimento
in testa...
*/

if(ptr < ptr_first){
inode_list[ptr].free = ptr_first;
ptr_first = ptr;
lseek(fd, 0, SEEK_SET);
if(write(fd, &ptr_first, PTR_SIZE) <= 0){
printf("write error rm\n");
exit(0);
}
}

/*
...altrimenti eseguo un inserimento in coda
*/

else{
i = ptr_first;
while(inode_list[i].free < ptr)
i = inode_list[i].free;
inode_list[ptr].free = inode_list[i].free;
inode_list[i].free = ptr;
}

/*
Scrivo su file le modifiche apportate alla mappa dei data block,
alla directory genitore e alla lista degli inode
*/

moveToMap(fd);
if(write(fd, &map, MAP_SIZE) <= 0){
printf("write error rm\n");
exit(0);
}
moveToBlock(fd, parent_block);
if(write(fd, &blocco, BLOCK_SIZE) <= 0){
printf("write error rm\n");
exit(0);
}
moveToInode(fd, 0);
if(write(fd, &inode_list, INODE_SIZE * INODE_NO) <= 0){
printf("write error rm\n");
exit(0);
}
}
else{
printf("Impossibile trovare il file o la dir da rimuovere\n");
exit(0);
}
}
else{
printf("Non e' una directory\n");
exit(0);
}

}

/*
Crea una nuova directory entry. Attraverso la variabile dir viene
passato alla funzione il valore dell'inode della directory in cui
si vuole creare una entry composta dalla coppia di valori passati
attraverso *dst e inode_entry. I due volori rappresentano
rispettivamente il nome del file e l'inode che compongono l'entry
*/


int newDirEntry(int fd, Ptr_Inode dir, char *dst, Ptr_Inode inode_entry){

int i;
Block blocco;
Inode inode;

moveToInode(fd, dir);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error newDirEntry\n");
exit(0);
}

/*
Verifico che il valore passato alla funzione si riferisca
effettivamente all'inode di una directory, altrimenti restituisco
un errore e termino il programma
*/

if(inode.used.flag == 1){
moveToBlock(fd, inode.used.type.dir);
}
else{
printf("newDirEntry: Impossibile creare il file\n");
exit(0);
}
if(read(fd, &blocco, BLOCK_SIZE) <= 0){
printf("read error newDirEntry\n");
exit(0);
}

/*
Se esiste una directory entry il cui nome corrisponde a quello della
entry da creare restituisco un messaggio di errore e termino il
programma
*/

for(i = 0; i < DIR_ENTRY; i++){
if(strcmp(blocco.dirent[i].filename, dst) == 0){
printf("%s esiste gia'\n", dst);
exit(0);
}
}

/*
Inizializzo la prima directory entry libera nel genitore con i
valori passati come argomento della funzione
*/

for(i = 0; i < DIR_ENTRY; i++){
if(strcmp(blocco.dirent[i].filename, "") == 0){
strncpy(blocco.dirent[i].filename, dst, FILENAME_LEN);
blocco.dirent[i].inode = inode_entry;
break;
}
}

/*
Se ho trovato una directory entry libera nel genitore scrivo su file
le modifiche apportate
*/

if(i < DIR_ENTRY){
moveToBlock(fd, inode.used.type.dir);
if(write(fd, &blocco, BLOCK_SIZE) <= 0){
printf("write error\n");
exit(0);
}
}
else{
printf("nessuna entry disponibile nella directory\n");
exit(0);
}
}

/*
Crea la directory specificata da pathname
*/


void makedir(int fd, char *pathname){

Ptr_Inode parent, new_inode;
char dirname[FILENAME_LEN];

if(pathname == NULL){
printf("list: argomento non valido\n");
exit(0);
}

if(strncmp(pathname, "VD:", 3) == 0)
pathname+=3;
else{
printf("makedir: Path errato\n");
exit(0);
}

/*
Ricava da pathname il valore relativo all'inode della directory
genitore che dovra' ospitare la directory creata

dirname is a return value
*/

parent = getParentFromPath(fd, pathname, dirname);

/*
Controlla che la directory genitore non contenga gia' un file
o una directory con lo stesso nome della directory da creare
*/

checkIfExist(fd, parent, dirname);

/*
Alloco l'inode e il data block necessari ad ospitare la nuova
directory
*/

new_inode = writeDirToInode(fd, parent);

/*
Scrivo nella directory genitore una entry relativa alla directory
appena creata, contenente il nome della directory e il proprio inode
*/

newDirEntry(fd, parent, dirname, new_inode);

}

/*
Se pathaname si riferisce ad un file list() stampa il nome del file,
altrimenti se si riferisce ad una directory list() stampa ogni
directory entry contenuta nella directory specificata da pathname
*/


void list(int fd, char *pathname){

int i;
Ptr_Inode ptr;
Inode inode;
Block blocco;
char file[FILENAME_LEN];

if(pathname == NULL){
printf("list: argomento non valido\n");
exit(0);
}

if(strncmp(pathname, "VD:", 3) == 0)
pathname+=3;
else{
printf("list: Path errato\n");
exit(0);
}

ptr = getFileFromPath(fd, pathname, file);
moveToInode(fd, ptr);
if(read(fd, &inode, INODE_SIZE) <= 0){
printf("read error list\n");
exit(0);
}

/*
Se il pathname si riferisce ad un file printa a schermo
il nome del file
*/

if(inode.used.flag == 0)
printf("%s\n", file);

/*
Se il pathname di riferisce ad una directory...
*/

else if(inode.used.flag == 1){
moveToBlock(fd, inode.used.type.dir);
if(read(fd, &blocco, BLOCK_SIZE) <= 0){
printf("read error list\n");
exit(0);
}

/*
...printa a schermo il nome di ogni entry contenuta
nella directory specificata da pathname
*/

for(i = 0; i < DIR_ENTRY; i++)
if(strcmp(blocco.dirent[i].filename, "") != 0)
printf("%s\n", blocco.dirent[i].filename);
}
else{
printf("list: file type not supported\n");
exit(0);
}

}

/*
Scrive un file sul filesystem virtuale preoccupandosi di allocare
l'inode e i data block necessari alla sua memorizzazione
*/


Ptr_Inode writeFileToInode(int fd_dst, int fd_src){

Inode inode;
Data buf;
Ptr_Inode ptr;
int k, j, r, i = 0, count = 0, bool = 1;
Block blocco[FILE_PTR_NO], index, blocco_index[N];

bzero(buf, sizeof(Data));
bzero(&inode, INODE_SIZE);
bzero(blocco, BLOCK_SIZE * FILE_PTR_NO);
bzero(blocco_index, BLOCK_SIZE * N);

/*
Inizializzo a 0 (file = 0, directory = 1) il campo flag della
struct Inode_Used e a PTR_BLOCK_NULL sia i puntatori a data block
diretti che indiretti
*/

inode.used.flag = 0;
inode.used.type.file.ptr_index = PTR_BLOCK_NULL;
for(k = 0; k < FILE_PTR_NO; k++)
inode.used.type.file.ptr[k] = PTR_BLOCK_NULL;
for(k = 0; k < N; k++)
index.indice[k] = PTR_BLOCK_NULL;
lseek(fd_src, 0, SEEK_SET);

/*
Continua a leggere fino a che non raggiunge EOF
*/

while( (r = read(fd_src, buf, sizeof(Data))) > 0){

inode.used.type.file.dimensione += r;

/*
Alloca un data block e inizializza un puntatore diretto
a tale blocco
*/

if(count < FILE_PTR_NO){
if( (inode.used.type.file.ptr[count] = getFreeBlock(fd_dst)) < 0){
printf("no free block\n");
exit(0);
}

/*
Copia nel data block i dati precedentemente letti dal
file tramite la chiamata a read()
*/

memcpy(blocco[count].data, buf, sizeof(Data));
bzero(buf, sizeof(Data));
count++;
continue;
}

/*
Se FILE_PTR_NO puntatori a data block diretti non dovessero
bastare ad indirizzare tutti i data block necessari a contenere
il file vengono allocati dei puntatori a data block indiretti
*/

if(bool &&
(inode.used.type.file.ptr_index = getFreeBlock(fd_dst)) < 0){
printf("no free block\n");
exit(0);
}
bool = 0;

/*
Se N puntatori a data block indiretti non dovessero bastare ad
indicizzare tutti i data block necessari a contenere il file
visualizza un messaggio di errore e termina il programma
*/

if(i == N){
printf("file troppo grande\n");
exit(0);
}

/*
Alloca un data block e inizializza un puntatore indiretto
a tale blocco
*/

if( (index.indice[i] = getFreeBlock(fd_dst)) < 0){
printf("no free block\n");
exit(0);
}

/*
Copia nel data block i dati precedentemente letti dal
file tramite la chiamata a read()
*/

memcpy(blocco_index[i].data, buf, sizeof(Data));
bzero(buf, sizeof(Data));
i++;
}
if(r != 0){
printf("read error writeFileToInode\n");
exit(0);
}

/*
Se ho scritto almeno un data block alloco un inode per il file
*/

if(count != 0){
if( (ptr = getFreeInode(fd_dst)) < 0){
printf("No free inode\n");
exit(0);
}
}
else{
printf("Nessun dato da leggere\n");
exit(0);
}

/*
Scrivo su file il mio inode nel punto in cui lo avevo
precedentemente allocato
*/

moveToInode(fd_dst, ptr);
if(write(fd_dst, &inode, INODE_SIZE) <= 0){
printf("write error\n");
exit(0);
}

/*
Scrivo su file i data block relativi ai puntatori diretti
*/

j = 0;
while(j < FILE_PTR_NO &&
inode.used.type.file.ptr[j] != PTR_BLOCK_NULL){
moveToBlock(fd_dst, inode.used.type.file.ptr[j]);
if(write(fd_dst, &blocco[j], BLOCK_SIZE) <= 0){
printf("write error\n");
exit(0);
}
j++;
}

/*
Scrivo su file l'indice dei puntatori indiretti
*/

if(inode.used.type.file.ptr_index != PTR_BLOCK_NULL){
moveToBlock(fd_dst, inode.used.type.file.ptr_index);
if(write(fd_dst, &index, BLOCK_SIZE) <= 0){
printf("write error\n");
exit(0);
}

/*
Scrivo su file i data block relativi ai puntatori indiretti
*/

j = 0;
while(j < N && index.indice[j] != PTR_BLOCK_NULL){
moveToBlock(fd_dst, index.indice[j]);
if(write(fd_dst, &blocco_index[j], BLOCK_SIZE) <= 0){
printf("write error\n");
exit(0);
}
j++;
}
}
return ptr;
}

/*
Esporta il contenuto di un file memorizzato nel filesystem virtuale
in un file specificato dall'utente
*/


void writeInodeToFile(int dbfd, Ptr_Inode dir, char *filename, int fd){

int i, count, dim;
Inode dir_inode, inode;
Block blocco, index;

moveToInode(dbfd, dir);
if(read(dbfd, &dir_inode, INODE_SIZE) <= 0){
printf("read error writeInodeToFile\n");
exit(0);
}

/*
Verifico che il valore passato alla funzione si riferisca
effettivamente all'inode di una directory, altrimenti restituisco
un errore e termino il programma
*/

if(dir_inode.used.flag == 1){
moveToBlock(dbfd, dir_inode.used.type.dir);
}
else{
printf("Impossibile leggere la directory\n");
exit(0);
}
bzero(&blocco, BLOCK_SIZE);
if(read(dbfd, &blocco, BLOCK_SIZE) <= 0){
printf("read error writeInodeToFile\n");
exit(0);
}

/*
Cerco il file da copiare tra tutte le directory entry della
directory genitore fornita come argomento della funzione
*/

for(i = 0; i < DIR_ENTRY; i++)

/*
Se lo trovo mi sposto al relativo inode per effettuare
una lettura
*/

if(strcmp(blocco.dirent[i].filename, filename) == 0){
moveToInode(dbfd, blocco.dirent[i].inode);
break;
}
if(i < DIR_ENTRY){
if(read(dbfd, &inode, INODE_SIZE) <= 0){
printf("read error writeInodeToFile\n");
exit(0);
}

/*
Verifico che l'inode si riferisca ad un file
*/

if(inode.used.flag == 0){
dim = inode.used.type.file.dimensione;

/*
Scrivo sul file il contenuto dei data block relativi ai
puntatori diretti
*/

count = 0;
while(count < FILE_PTR_NO &&
inode.used.type.file.ptr[count] != PTR_BLOCK_NULL && dim > 0){
moveToBlock(dbfd, inode.used.type.file.ptr[count]);
bzero(&blocco, BLOCK_SIZE);
if(read(dbfd, &blocco, BLOCK_SIZE) <= 0){
printf("read error writeInodeToFile\n");
exit(0);
}
if(dim >= sizeof(Data)){
if(write(fd, &blocco.data, sizeof(Data)) <= 0){
printf("write error writeInodeToFile\n");
exit(0);
}
dim -= sizeof(Data);
}
else{
if(write(fd, &blocco.data, dim) <= 0){
printf("write error writeInodeToFile\n");
exit(0);
}
dim = 0;
}
count++;
}

/*
Se non ci sono puntatori diretti inutilizzati ed e' stato
allocato un indice dei puntatori indiretti, scrivo sul file
il contenuto dei data block indicizzati dai puntatori
indiretti
*/

if(count == FILE_PTR_NO &&
inode.used.type.file.ptr_index != PTR_BLOCK_NULL && dim > 0){
moveToBlock(dbfd, inode.used.type.file.ptr_index);
if(read(dbfd, &index, BLOCK_SIZE) <= 0){
printf("read error writeInodeToFile\n");
exit(0);
}
i = 0;
while(i < N && index.indice[i] != PTR_BLOCK_NULL && dim > 0){
moveToBlock(dbfd, index.indice[i]);
if(read(dbfd, &blocco, BLOCK_SIZE) <= 0){
printf("read error writeInodeToFile\n");
exit(0);
}
if(dim >= sizeof(Data)){
if(write(fd, &blocco.data, sizeof(Data)) <= 0){
printf("write error writeInodeToFile\n");
exit(0);
}
dim -= sizeof(Data);
}
else{
if(write(fd, &blocco.data, dim) <= 0){
printf("write error writeInodeToFile\n");
exit(0);
}
dim = 0;
}
i++;
}
}
}
else{
printf("Impossibile leggere il file\n");
exit(0);
}
}
else{
printf("File inesistente\n");
exit(0);
}
}

/*
Gestisce la copia di file da e verso il filesystem virtuale
*/


int copy(char *dbfile, char *src, char *dst){

int fd, fs;
char file[FILENAME_LEN], src_token[4], dst_token[4];
Ptr_Inode inode, dir;

if(src == NULL || dst == NULL){
printf("copy: argomento non valido\n");
exit(0);
}

if((fs = open(dbfile, O_RDWR)) <= 0){
printf("open error\n");
exit(0);
}

/*
Se la sorgente e' il filesystem virtuale scrivi sul file
specificato...
*/

if(strncmp(src, "VD:", 3) == 0){
src+=3;
if((fd = open(dst, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) <= 0){
printf("open error\n");
exit(0);
}

/*
Ricava la directory genitore e il nome del file da esportare
dal filesystem virtuale
*/

dir = getParentFromPath(fs, src, file);

/*
Copia il contenuto del file contenuto nel filesystem virtuale
su un file di destinazione
*/

writeInodeToFile(fs, dir, file, fd);
}

/*
...altrimenti copia il file specificato nel filesystem virtuale
*/

else if(strncmp(dst, "VD:", 3) == 0){
dst+=3;
if((fd = open(src, O_RDONLY)) <= 0){
printf("open error\n");
exit(0);
}

/*
Ricava la directory genitore e il nome del file di destinazione
della copia
*/

dir = getParentFromPath(fs, dst, file);

/*
Controllo se esiste un file con lo stesso nome e interrompo la
copia nel caso la destinazione contenga gia' una voce per tale
file.
Il controllo se pur presente nella funzione newDirEntry() viene
eseguito anticipatamente per inibire l'allocazione di inode e data
block che poi risulterebbero inutilizzati
*/

checkIfExist(fs, dir, file);

/*
Scrive il contenuto del file nel filsystem virtuale
*/

inode = writeFileToInode(fs, fd);

/*
Aggiunge una directory entry per il file appena creato nella
directory restituita come valore di ritorno da getParentFromPath()
*/

newDirEntry(fs, dir, file, inode);
}
else{
printf("cp: argomenti comando errati\n");
exit(0);
}

}

/*
Provvede all'inizializzazione delle strutture fondamentali
del filesystem
*/


int init(char *file){

int fd, i;
Ptr_Inode free_inode;
Map map;
Inode inode[INODE_NO];
Block blocchi[BLOCK_NO];

if( (fd = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) < 0){
printf("open error\n");
exit(0);
}

/*
Inizializzo il puntatore al primo inode libero.
inode[0] contiene il puntatore al directory block relativo alla
root directory e risulta pertanto occupato, inode[1] e' invece
libero
*/

free_inode = 1;
lseek(fd, 0, SEEK_SET);
if(write(fd, &free_inode, PTR_SIZE) < 0){
printf("write error\n");
exit(0);
}

/*
Inizializzo la lista degli inode liberi
*/

bzero(inode, INODE_SIZE * INODE_NO);
for(i = 0; i < INODE_NO; i++)
inode[i].free = i + 1;

/*
Faccio puntare il primo inode al primo data block che conterra'
le directory entry relative alla root directory
*/

inode[0].used.flag = 1;
inode[0].used.type.dir = 0;
if(write(fd, inode, INODE_SIZE * INODE_NO) < 0){
printf("write error\n");
exit(0);
}

/*
Inizializzo a 0 il bitmap e pongo a 1 il bit relativo alla root dir
*/

bzero(map, MAP_SIZE);
map[0] = 1;
if(write(fd, map, MAP_SIZE) < 0){
printf("write error\n");
exit(0);
}

/*
Inizializzo il directory block relativo alla root directory.
Inserisco le entry relative alla current e alla parent dir
*/

bzero(blocchi, BLOCK_SIZE * BLOCK_NO);
strncpy(blocchi[0].dirent[0].filename, ".", FILENAME_LEN - 1);
blocchi[0].dirent[0].inode = 0;
strncpy(blocchi[0].dirent[1].filename, "..", FILENAME_LEN - 1);
blocchi[0].dirent[1].inode = 0;

/*
Inizializzo i restanti data block del filesystem
*/

if(write(fd, blocchi, BLOCK_SIZE * BLOCK_NO) < 0){
printf("write error\n");
exit(0);
}
}

void print_usage(char *prog){

printf("Usage: %s [ -h ] [ -i ] [ -c [VD:]/path [VD:]/path ]", prog);
printf(" [ -l VD:/path ] [ -m VD:/path ] [ -r VD:/path ]\n");
printf("\t-h questo help\n");
printf("\t-i inizializza il filesystem\n");
printf("\t-c copia un file nel/dal filesystem\n");
printf("\t-l elenca i file presenti nella directory\n");
printf("\t-m crea una nuova directory\n");
printf("\t-r rimuove il file o la directory e tutto il suo");
printf(" contenuto\n");

}

int main(int argc, char **argv){

int fd, i;
Ptr_Inode parent;
Block blocco;
char opt, filename[FILENAME_LEN];

opterr = 0;
if( (opt = getopt(argc, argv, "chilmr")) != -1){
switch(opt){
case 'c': argc -= optind;
argv += optind;
copy("dbfs", argv[0], argv[1]); break;
case 'h':
print_usage(argv[0]);
break;
case 'i': init("dbfs"); break;
case 'l':
argc -= optind;
argv += optind;
if( (fd = open("dbfs", O_RDONLY)) < 0){
printf("open error\n");
exit(0);
}
list(fd, argv[0]);
break;
case 'm':
argc -= optind;
argv += optind;
if( (fd = open("dbfs", O_RDWR)) < 0){
printf("open error\n");
exit(0);
}
makedir(fd, argv[0]);
break;
case 'r':
argc -= optind;
argv += optind;
if( (fd = open("dbfs", O_RDWR)) < 0){
printf("open error\n");
exit(0);
}
if(argv[0] == NULL){
printf("rm: argomento non valido\n");
exit(0);
}

if(strncmp(argv[0], "VD:", 3) == 0)
argv[0]+=3;
else{
printf("rm: Path errato\n");
exit(0);
}

parent = getParentFromPath(fd, argv[0], filename);
rm(fd, parent, filename);
break;
default:
print_usage(argv[0]);
}
}
else
print_usage(argv[0]);

}


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x06][SECURITY] SQL-iNJECTi0N [Master^Shadow]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

Indice
* Introduzione
* Capire il problema
* Metodologie di testing
* I problemi più diffusi
o Accessi non desiderati
o Visualizzare dati non accessibili
o Analisi delle query tramite l'introduzione di errori
o Advanced SELECT Injection
o INSERT Injection
o STORED PROCEDURES Injection
o Le feature e i problemi dei DBMS più diffusi
* Scrivere codice sicuro

[Introduzione]
L'evoluzione delle esigenze del pubblico internauta ha comportato
l'introduzione di tecnologie dinamiche (creazione di contenuti "on
demand"
) nei più disparati servizi offerti dal web. Sono sempre più
numerosi i server, anche gratuiti, che offrono la possibilità di
elaborare script per la creazione di contenuti web e sempre più utenti
stanno convertendo i propri siti statici in vere e proprie applicazioni
residenti sul web. Tali applicazioni utilizzano, nella maggioranza dei
casi, dati provenienti dall'utente ed introducono quindi la possibilità
di modifica arbitraria delle variabili utilizzate dallo script, rendendo
così possibile l'insorgere di vulnerabilità sfruttabili da remoto.In
questo paper analizzeremo una fra le più diffuse vulnerabilità delle
applicazioni web-related: la vulnerabilità da SQL Injection.

[Capire il problema]
La maggior parte delle applicazioni web vengono realizzate
interfacciando l'applicazione stessa ad un server DBMS ed interagiscono
con tali server tramite l'utilizzo del linguaggio standard SQL.
SQL Injection è una tecnica di exploiting delle web application che
utilizzano dati provenienti dai client nelle query, senza controllare la
presenza di caratteri potenzialmente pericolosi.
Contrariamente a quello che si può pensare, questo tipo di vulnerabilità
è molto diffusa e tale diffusione è spesso dovuta alla mancanza di
professionalità di molti programmatori, spesso improvvisati, e alla
mancanza di solide basi di programmazione sicura.

[Metodologie di testing]
Controllare le applicazioni per trovare eventuali vulnerabilità dovute
all'SQL Injection potrebbe essere molto complesso ma ci sono casi dove
la ricerca di script potenzialmente vulnerabili è molto semplice.
Se, ad esempio, l'aggiunta di un apice in un form ritorna una pagina
bianca o piena di errori riportati dal server DBMS siamo già ad un buon
punto di partenza per quanto comporta la ricerca delle vulnerabilità.
Un buon programmatore dovrebbe controllare l'input di ogni variabile e
non pensare che l'utente, utilizzatore della web application, formatti
correttamente le variabili. Ogni possibile campo dovrebbe essere testato
per tutte le vulnerabilità che potrebbero coinvolgerlo, in modo da
evitare ripercussioni in tutto lo script, spesso legato ad altri.Un buon
metodo di controllo delle applicazioni web potrebbe essere quello di
inserire in ogni campo una comando tipico di SQL preceduto da un apice,
cercando così eventuali problemi sulle singole variabili.
Dopo aver provato i campi uno alla volta potrebbe essere utile riempire
il form con dati formalmente corretti e ripetere la procedura
"apice+comando" per ogni campo, mantenendo formalmente corretti
gli altri campi.
Ipotizziamo di passare ad uno script questi parametri formalmente corretti:

script.php?nome=pinco&cognome=pallino&email=pinco@pallino.ext

Lo script funziona alla perfezione. Proviamo ora a modificare la stringa
con un apice nel parametro "nome":

script.php?nome='pinco&cognome=pallino&email=pinco@pallino.ext

Ipotizzando che lo script sia vulnerabile, siamo andati a modificare i
parametri inviati al server DBMS dalla query, modificandone
strutturalmente la sintassi (ricordo che l'apice (') è un operatore SQL).

Cosa potrebbe accadere utilizzando una stringa di parametri come quella
proposta dall'esempio?

I risultati potrebbero essere molteplici: dalla semplice pagina bianca
ai più disparati errori provenienti dal server DBMS. Potrebbe riportare
l'errore che gli altri paramentri non sono stati inseriti o addirittura
mostrare dati che non dovrebbero essere visualizzati con quella query.
Dopo aver trovato una possibile vulnerabiltà da SQL Injection, la parte
più importante è l'interpretazione degli errori. Se l'errore viene
generato del server DBMS siamo sicuramente davanti ad una vulnerabilità
ad SQL Injection ma gli errori spesso sono tutt'altro che ovvi.
Controlliamo sempre gli errori che si riferiscono ad ODBC, alla
sintassi, al server SQL. Bisogna inoltre prestare attenzione alle più
minimali modifiche della pagina, segno di SQL Injection Exploiting,
poichè molti programmatori possono nascondere le informazioni mettendo,
ad esempio, eventuali errori negli header del documento HTML e non
mostrandone traccia nel body, la parte visualizzata dal browser.Non
bisogna fermarsi all'analisi della singola pagina colpita da SQL
Injection ma è buona abitudine seguire i link presenti nella pagina,
alla ricerca di eventuali ripercussioni sull'intera applicazione ed è
importante seguire anche eventuali redirect verso una pagina di errore
predefinita, spesso preceduta da una schermata di errore propria del
database.

[I problemi più diffusi]
Passiamo ora all'analisi dei più diffusi problemi di sicurezza dovuti
alla vulnerabilità da SQL Injection.

[Accessi non desiderati]
L'autenticazione delle web application è spesso delegata ad uno script
di login che ha il compito di processare la coppia login/password
proveniente dal client e di confrontarla con le coppie presenti nel
database. In caso di corrispondenza lo script setterà gli appositi flag
per consentire il nostro accesso o, nel caso opposto, ci vieterà l'accesso.
Consideriamo il seguente codice PHP:

$user = $_GET["nome"];
$passwd = $_GET["password"];
$dati = mysql_query("SELECT * FROM utenti WHERE user='".$user."'...
...AND passwd='"
.$passwd."'");
if (mysql_num_rows($dati) == 0)
$logged = 0
else
$logged = 1

Questo breve script prende i dati dalla querystring e li mette nella
query senza controllare la presenza di eventuali caratteri pericolosi.
Ipotizziamo di modificare entrambi gli argomenti passati allo script in
" ' OR ''='' " e di far processare la pagina al server.
La parte condizionale della query passata al server DBMS (in questo caso
MySQL) diventa:

...WHERE user='' OR ''='' AND passwd='' OR ''=''

Come potrete ben capire entrambe queste condizioni sono sempre
verificate e mysql_num_rows() restituirà un valore sicuramente diverso
da zero (se la tabella contiene dati) consentendo così il login a
qualsiasi persona a conoscenza di questo problema.

[Visualizzare dati non accessibili]
Il mancato parsing dei parametri per caratteri maligni ha introdotto la
possibilità di editare, a piacimento dell'attaccante, la query verso il
database.
Abbiamo appena visto che è possibile entrare con il massimo dei
privilegi in un'applicazione web ma potremmo decidere di accedere a dati
non direttamente accessibili dall'applicazione stessa.
Lo standard SQL permette la creazione di SELECT multiple tramite il
comando UNION e tale fatto può essere sfruttato per gli scopi
dell'attaccante.
Prendiamo in esame la seguente query:

SELECT nome FROM users WHERE paese='".$var."'"

La variabile $var dovrebbe contenere il paese di provenienza degli
utenti, dei quali stiamo cercando il nome ma, su di essa, non viene
fatto nessun controllo ed è quindi possibile scrivere codice SQL
direttamente nella variabile.
Nel nostro caso $var conterrà:

' UNION ALL SELECT nome, passwd FROM users WHERE ''='

Vediamo il contenuto della query una volta settata $var:

SELECT nome FROM users WHERE paese='' UNION ALL ...
... SELECT nome, passwd FROM users WHERE ''=''

Evidentemente la prima SELECT non restituirà nessun record (supponendo
che nessun utente ha il campo paese vuoto) mentre la seconda SELECT è
incondizionata e restituirà tutte le coppie nome/password.
Il problema è quello della visualizzazione dei dati: i dati ora sono
stati estratti dal database ma l'applicazione considera solo il campo
""nome"" e non ""password"". Fortunatamente (!) il linguaggio SQL
permette l'aliasing dei campi tramite il comando AS che può essere
sfruttato per fare l'output dei dati non visualizzabili ordinariamente.

[Analisi delle query tramite l'introduzione di errori]
Molti server web restituiscono parte delle query in caso di errore.
Normalmente questa funzionalità è utile, anzi direi necessaria, durante
il debugging delle applicazioni web ma può essere usata impropriamente
per analizzare le query e quindi carpire informazioni sulla
realizzazione di una web application vulnerabile da SQL Injection.
E' sempre utile testare un'applicazione inserendo volutamente errori di
sintassi nei campi che interagiscono con l'utente, magari utilizzando
costrutti SQL incompleti come "
valore'", "'", "'valore", "' OR '", ";" e
"
0,1,2".

[Advanced SELECT Injection]
Non sempre le web application elaborano query semplici e lineari.
Capita a volte che siano presenti istruzioni racchiuse da parentesi,
selezione sulla base di wildcards e campi non direttamente modificabili.
Iniettare codice sintatticamente corretto e funzionante all'interno di
queste tipologie di query potrebbe richiedere l'utilizzo di piccoli
accorgimenti, a volte non immediati.

SELECT nome FROM utenti WHERE (paese='campovariabile')

Estrapolare dati da una query di questo tipo non può essere fatto con il
metodo visto nelle pagine precedenti poichè ci verrebbero segnalati
diversi errori di sintassi.
Occorre quindi modificare la query utilizzando un campo di questo genere

') UNION ALL SELECT campo FROM altraTabella WHERE (''='

che, sostituita nella precedente, soddisfa perfettamente la sintassi SQL:

SELECT nome FROM utenti WHERE (paese='') UNION ALL ...
... SELECT campo FROM altraTabella WHERE (''='')

Il trucco sta semplicemente nel completare parentesi e apici con piccoli
trucchi in modo da far risultare la sintassi corretta e impostare
correttamente le variabili booleane di confronto.
Altra sintassi che potrebbe creare problemi è quella dovuta al costrutto
di confronto LIKE.

SELECT nome FROM utenti WHERE nome LIKE '%campovariabile%'

I simboli di percentuale funzionano nelle query SQL come wildcards e un
eventuale completamento %% ritornerebbe tutti i record e quindi non
sarebbe applicabile il costrutto UNION.
Bisogna quindi pensare di inserire una stringa che non risulti in
nessuno dei record, come potrebbe essere ad esempio "
!?!".
Un campo tipico per iniettare codice in questa query potrebbe essere il
seguente:

!?!% UNION ALL SELECT campo FROM tab WHERE campo LIKE '%

Sostituendo la query SQL diventa:

SELECT nome FROM utenti WHERE nome LIKE '%!?!% UNION ALL ...
... SELECT campo FROM altraTabella WHERE campo LIKE '%%'

[INSERT Injection]
Finora abbiamo considerato SQL Injection un problema legato al costrutto
SELECT ma possiamo tranquillamente dire che questa vulnerabilità si può
estendere a qualsiasi query contentente input dell'utente non
appositamente controllato.
Un altro costrutto potenzialmente vulnerabile è INSERT, necessario per
l'inserimento di nuovi record all'interno di una tabella. Controllare la
vulnerabilità di una query di inserimento comporta le stesse tecniche
viste per la query di selezione ma la forzatura di queste query potrebbe
segnalarci all'amministratore dell'applicazione in quanto verrebbero
riportate nei record parti di sintassi SQL.
Iniettare codice nella query INSERT consente all'utente smaliziato di
prelevare dati da un database e utilizzarli per la propria
registrazione. Ad esempio è possibile registrarsi al posto di un altro
utente senza aver nessun dato su di lui. L'unico trucco necessario per
assicurarsi dell'effettiva estrazione dei dati è quella di visualizzarli
(ad esempio tramite il pannello di controllo).
Per portare a termine questo tipo di exploit il server DBMS deve
supportare le SUBSELECT (ad esempio MySQL non supporta questa feature) e
si devono conoscere i nomi di campi e tabella, ricavabili con un po' di
reverse engineering.

Ipotizziamo di avere la seguente query di inserimento:

INSERT INTO tab (nome, cognome) VALUES ('campo1', 'campo2')

Se al posto di campo1 inseriamo

' + SELECT nome FROM tab LIMIT 1 + '

e al posto di campo2 inseriamo

' + SELECT cognome FROM tab LIMIT 1 + '

otteniamo una perfetta replica di un record esistente all'interno del
database poichè la query in questione diventa:

INSERT INTO tab (nome, cognome) VALUES ('' + SELECT nome FROM tab LIMIT
1 + '', '' + SELECT cognome FROM tab LIMIT 1 + '')

Per cambiare l'utente da selezionare basta scorrere i record con
l'offset offerto dall'istruzione LIMIT o, eventualmente, usare la
sintassi NOT IN () per ciclare i record.

[STORED PROCEDURES Injection]
Exploitare le stored procedures è generalmente molto più semplice che
agire sul costrutto SELECT.
Le stored procedures sono parti di codice SQL richiamabili nei costrutti
SQL tramite l'utilizzo di EXEC e sono praticamente degli script batch,
atti ad effettuare operazioni direttamente all'interno del server DBMS.
Hanno la particolarità di essere abbastanza veloci e sono molto
utilizzate per le operazioni transizionali.
Non tutti i server DBMS permettono l'esecuzione delle stored procedures
e, per richiamare l'esecuzione delle stesse, è necessario che lo stesso
server permetta l'utilizzo di statement multipli (istruzioni SQL
distinte separate da un punto e virgola).
Diversi server DBMS hanno comunque queste feature e possiedono, inoltre,
diverse Stored Procedures predefinite, molte delle quali possono
compiere operazioni molto interessanti per un attaccante.

Esaminiamo la vulnerabilità più evidente nel server DBMS di MicroSoft,
MS SQL Server. Tale server attiva di default innumerevoli stored
procedures, fra le quali troviamo xp_cmdshell ovvero un frontend per
l'interprete dei comandi dei sistemi basati su kernel NT. Il server in
questione supporta statement multipli e basta quindi una qualsiasi
vulnerabilità da SQL Injection per accedere al sistema con i permessi
del server SQL.

Consideriamo la seguente query:

SELECT * FROM tab WHERE nome='campovariabile'

Terminando il primo costrutto con un nome arbitrario ed applicando la
sintassi di EXEC sulla procedura xp_cmdshell otteniamo la sequente query:

SELECT * FROM tab WHERE nome='ed';EXEC master.dbo.xp_cmdshell 'cmd.exe
comando'

Al posto di "
comando" possiamo immettere qualsiasi stringa di comando
interpretabile dalla shell dei sistemi NT ovvero il sistema è in balia
dei nostri comandi.

Ovviamente il sistema server SQL di Microsoft non è l'unico ad offrire
stored procedures predefinite ma è stato preso in considerazione per la
gravità della situazione che combinata con errori di programmazione web
permette l'accesso a persone non desiderate.
E' quindi bene disabilitare, o rimuovere, le stored procedures non
necessarie al corretto funzionamento del server e assicurarsi che i
permessi sulle parti vitali del sistema siano più restrittivi possibile.

[Le feature e i problemi dei DBMS più diffusi]

MySQL
Supporta 'INTO OUTFILE'
Supporta 'UNION' (dalla versione 4.x)
Spesso gira come ""root""
Molti moduli e librerie non supportano statement multipli

Oracle
Supporta le SubSelect
Supporta 'UNION'
Supporta le stored procedures
Non supporta statement multipli
Molte stored procedures preimpostate, alcune delle quali pericolose

DB2
Supporta le SubSelect
Supporta 'UNION'
Supporta le stored procedures
Non supporta statement multipli

Postgres
Supporta 'COPY' se fatto girare come superutente
Supporta le SubSelect
Supporta 'UNION'
Supporta le stored procedures
Supporta gli statement multipli

MS SQL Server
Supporta le SubSelect
Supporta 'UNION'
Supporta le stored procedures
Supporta gli statement multipli
Molte store procedures preimpostate sono pericolose

[Scrivere codice sicuro]
Ogni web application che si rispetti deve seguire piccole norme di
programmazione per elevare la sicurezza al massimo possibile.
Se l'applicazione riceve input dall'utente questo deve essere processato
appropriatamente in modo da escludere eventuali caratteri maligni, che
potrebbero interferire con le query previste dai programmatori.
Raddoppiare gli apici o farli precedere dal carattere backslash "
\"
spesso è poco utile e si può trovare il modo di aggirare l'ostacolo.
E' quindi necessario lasciar passare solo i caratteri necessari e
filtrare tutti gli indesiderati tramite l'utilizzo di parser appositi
(magari una regular expression ben congengnata).
Ad esempio per lasciar passare solo i caratteri alfabetici la regexp
seguente è l'ideale:

s/^a-zA-z//g

mentre per far passare i soli numeri si può applicare:

s/^0-1//g

Lasciare solo i caratteri alfanumerici è quasi sempre un'ottima
soluzione al nostro problema ma può capitare di aver bisogno di altri
tipi di caratteri.
In questo caso è bene sostituire ai caratteri la loro codifica nello
standard UniCode o nello standard HTML.
Ovviamente è sempre bene utilizzare il minimo numero possibile di
caratteri non alfanumerici per evitare potenziali problemi di iniezione.
Altra feature interessante per aumentare la sicurezza delle web
application potrebbe essere l'individuazione di tentativi di iniezione
del codice prima del passaggio a stored procedures e a query.

All'interno delle stored procedures è bene non creare le query
dinamicamente ma cercare di mantenere fisso più codice possibile.
Realizzare una procedura di questo tipo richiede la creazione di una
procedura contentente la sola query statica ed il semplice passaggio
delle variabili come parametri.
Ad esempio:

CREATE PROC usr_prendiUtentedaNick
@nick nvarchar(255)
AS
SELECT nome, cognome, indirizzo, telefono
FROM utenti
WHERE nick = @nick
RETURN

La configurazione del server deve essere più restrittiva possibile e
deve evitare la possibilità di accedere liberamente a parti del sistema
non normalmente disponibili.
Disabilitare le stored procedure non necessarie agli utenti potrebbe
essere la soluzione ai nostri problemi ma un'attenta policy di permessi
potrebbe evitare la mutilazione eccessiva del server DBMS.

[Ringraziamenti]
Ringrazio tutti gli amici di RoLUG, HX, OndaQuadra, S.P.I.N.E. e tutti
gli sviluppatori di software libero del mondo.
Happy hacking to everybody!

Master^Shadow <master(at)spine-group(dot)org>


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x07][SECURITY] SAM CRACKiNG [h23]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

what: HOW TO - SAM cracking
who: h23
where: interno-notte
when: notte a cavallo tra 10&11 ottobre 2003
why: just to share knowledge :)

update: 13 ottobre 2003

_________________________
| |
| PREFAZIONE/DISCLAIMER |
|_________________________|

"
Considerate la vostra semenza:
fatti non foste a viver come bruti,
ma per seguir virtute e canoscenza"
Dante Alighieri, Inferno Canto XXVI

Questo ragazzo era proprio un genio (come ama ripetere - piu' che
giustamente -la mia prof di letteratura
italiana) e, proprio a causa della sua genialita', conclude questo
canto facendo dire ad Ulisse

"
Tre volte il fe' girar con tutte l'acque:
e la quarta levar la poppa in suso
e la prora ire in giu', come altrui piacque,
infin che 'l mar fu sovra noi richiuso"
Dante Alighieri, ibidem

MORALE: nonostante la sua intelligenza, la sua sagacia, la sua abilita',
anche Ulisse colo' a picco a causa del suo
peccato di iubris (orgoglio).
Tutto questo giro di parole per dire cosa?
La sete di conoscenza e' legittima, ma occhio: si puo' restare fregati.

Quindi io me ne tiro fuori.
Se un giorno questo HOW TO andra' in giro, sappiate che io sto
scrivendo solo per soddisfare l'arsura del vostro cervello assetato di
conoscenza, ma non mi rendo minimamente responsabile per l'uso che
saprete/vorrete farne.

____________
| |
| READY?!? |
|____________|
|
+-INTRO
|
| Come saprete (visto che state leggendo questo documento), i file
| SAM sono quelli che contengono le informazioni
| rigurdanti username(s) e password(s) nei sistemi operativi Windows
| NT, Windows 2000, Windows XP.
| Quando lo zio Bill, o chi per lui, li creo', sostenne che erano si
| curi poiche', a partire dall'hash della password
| contenuto nel file SAM, non era possibile risalire alla password
| originaria.
| Gli dimostrarono che tramite un attacco brute-force si poteva
| faclmente ovviare al problema cosi', da Windows
| 2000 in poi, decise di attivare di default una protezione aggiunti
| va (chiamata SYSKEY) la cui abilitazione su NT
| era a discrezione dell'utente.
| Bene, noi qui non vogliamo far altro che dimostrare che anche
| questo tipo di protezione puo' essere aggirata
| (e neanche troppo difficilmente).
| Ultima nota: la tipologia di cracking qui descritta implica
| l'avere accesso fisico al computer di cui si vuole
| trovare la password.
| Tra i tanti metodi possibili, questo HOW TO descrive quello in cui
| si lavora per il minor tempo sulla macchina
| vittima: da quest'ultima ricaveremo solo le informazioni
| indispensabili. Il resto (il cracking vero e proprio)
| dovremo farlo su un secondo computer.
|
|
+-INGREDIENTI (ossia cio' di cui abbiamo bisogno prima d'iniziare)
|
| I programmi di cui abbiamo bisogno non sono molti:
|
| 1)cosa: SAMInside (24KB) - purtroppo e' disponibile solo
| in versione demo
| dove: http://www.insidepro.com/eng/saminside.shtml
| note: sara' la nostra punta di diamante ;)
|
| 2)cosa: RAR 3.20 for DOS and OS/2 (427KB)
| dove: http://www.rarlab.com/download.htm
| note: in alternativa va bene qualsiasi compressore purche'
| funzioni da linea di comando
|
| 3)cosa: L0phtCrack (~3940KB) - anche questo e' disponibile solo
| "
free to try"
| dove: http://www.atstake.com/research/lc/
| note: nel campo dei software adibiti a sferrare brute force e'
| senz'altro uno dei migliori;
| nel nostro caso, pero', puo' essere usato insieme a SAMInside,
| o se ne puo' fare del tutto a meno
|
| 4)cosa: disco di boot NTFS
| io mi sono trovato molto bene usando per il boot il floppy di
| avvio di win98 (semplicemente perche'
| ce l'avevo gia' pronto) e poi "
montando" una partizione NTFS
| tramite NTFSDOS (39KB)
| dove: http://www.sysinternals.com/ntw2k/freeware/NTFSDOS.shtml
| note: come ho gia' detto sopra va bene un qualsiasi altro disco
| di boot.
| Il nostro scopo e' poter accedere all'hard disk (nel 99% con
| file system NTFS) senza dover passare
| per Windows (per il perche' di questa stranezza basta continuare
| a leggere)
|
|
+-OPERAZIONI PRELIMINARI

Poiche' per motivi di sicurezza Windows preclude ogni accesso in
lettura/scrittura ai files che ci servono (questo
vuol dire che non e' possibile nemmeno copiarli, rinominarli etc.),
dovremo avviare il computer in modo che win non venga mai lanciato.
Ora si capisce il perche' del floppy di boot.
Come detto sopra, sul computer vittima faremo solo parte del lavoro.
Questo vuol dire che, quando smanetteremo su quella povera macchina,
non ci serviranno tutti i programmi elencati sopra.
Per l'occasione ho provveduto a preparare tre floppy (la scelta del
dischetto e' dovuta principalmente alla possibilita' di scrittura)

floppy 1: disco di boot NTFS
floppy 2: RAR*
floppy 3: -vuoto-

*N.B. Nel floppy 2 non va inserito il file rarx320.exe scaricato
dal sito rarlab.com: quel file va avviato
sul proprio computer, il che generera' una cartella di nome
RAR che conterra' il programma vero e proprio. Sara' quella cartella a
dover essere copiata sul floppy 2.

Ora abbiamo tutto il necessario per cominciare! :)


_______________
| |
| LET'S GO!!! |
|_______________|
|
+-PARTE 1: operazioni da compiere sul computer vittima
|
| Inserite il disco di boot (floppy 1) e avviate il comp.
| Durante il caricamento date un'occhiata alla lettera che viene
| assegnata al
| vostro hard disk (ad esempio, nel caso di NTFSDOS se il vostro hd
| era C:\ ora e' accessibile come D:\).
| Togliete dal drive del floppy il disco 1, ed inserite il disco 3
| (quello vuoto).
| Dato che la posizione canonica del nostro SAM e'
| C:\WINDOWS\SYSTEM32\CONFIG\SAM [per windows xp] *
| C:\WINNT\SYSTEM32\CONFIG\SAM [per windows nt/2000] *
| [*N.B. Il file SAM e' privo di estensione!]
| lanciamo un bel copy
| copy C:\WINDOWS[o WINNT, a seconda dei casi]\SYSTEM32\CONFIG\SAM A:\
| ...
| 1 file/s copiato/i
| Voila', meta' del lavoro e' compiuto.
| Se il file SAM non fosse criptato SYSKEY, il nostro compito sarebbe
| finito qui; ma dato che lo e', dobbiamo
| eseguire qualche operazione in piu'.
| Togliete il disco 3 (che ora conterra' il SAM) e inserite il floppy
| 2 (quello con RAR).
| Per craccare questa ulteriore forma di protezione abbiamo bisogno
| di prelevare un altro file.
| Piu' precisamente il SYSTEM contenuto nella stessa cartella.
| Il problema e' che questo file e' abbastanza pesante (generalmente
| si aggira intorno ai 3 Mb), da qui la necessita'
| di usare un compressore come RAR.
| Portatevi nel floppy e aprite la directory RAR (che dovrebbe essere
| quella contenente il programma dopo averlo
| estratto - ricordate le OPERAZIONI PRELIMINARI? - )
| A:\cd RAR
| A:\RAR\
| La directory RAR occupa 852KB. Questo vuol dire che sul floppy ne
| avremo poco piu' di 570 a nostra disposizione.
| Dai test effettuati ho visto che RAR comprime un file SYSTEM fino
| all'83-84%.
| Questo vuol dire che un file da 2800KB diventa da circa 470KB:
| pienamente compatibile, quindi, con il nostro
| floppy 2!
| A questo punto da
| A:\RAR\
| eseguiamo
| RAR32 a -s A:\system.rar C:\WINDOWS[o WINNT, a seconda dei casi]
| \SYSTEM32\CONFIG\SYSTEM
| vederemo la percentuale aumentare e, alla comparsa dell'OK, sapremo
| che sul dischetto 2, insieme al rar, c'e' il
| file SYSTEM compresso.
| [N.B. Anche il file SYSTEM e' privo di estensione]
| Parte 1 conclusa.
| Riprendetevi tutti i floppy, spegnete il comp e andatevene.
| Se invece siete fanatici di mission impossible, ricordatevi di
| cancellare le impronte digitali sulla tastiera,
| riattivare le telecamere perimetrali che avevate disinserito e
| togliere il guardiano tramortito da quell'angusto
| armadietto (ricordate, voi siete i buoni, e i buoni non uccidono
| la gente ;)
|
|
+-PARTE 2: operazioni da effettuare sul vostro computer di fiducia (
possibilimente abbastanza potente, altrimenti non
finirete mai col brute force!)

Bene, se tutto e' andato liscio ora dovreste essere davanti al vostro
schermo, leggendo queste parole e, soprattutto,
avendo 3 dischetti in mano, 2 dei quali dovrebbero contenere
informazioni vitali per quello che ci siamo prepostidi fare.
La prima cosa da fare e' crackare syskey.
Per fare questo basta aprire SAMInside ed importare il SAM
File -> ImportSAM...
Il programma riconoscera' automaticamente la protezione SYSKEY e
chiedera' il file SYSTEM che voi avrete
precedentemente decompresso in un qualsiasi luogo del vostro comp.
Vedrete quindi comparire nella schermata azzurrina gli username e i
relativi hash delle password privi di crittazione SYSKEY.
A questo punto potete seguire due strade:
1) ordinate a SAMInside di eseguire il bruteforce, vi mettere l'anima
in pace e aspettate
2) nel caso in cui abbiate scaricato LC4, potreste copiare uno per uno
i caratteri dell'LM hash e dell'NT
hash, unirli insieme mediante questa sintassi
(nel caso di administrator)
"
Administrator:500:" + LMhash + ":" + NThash + ":::"
[anche se a questi livelli non dovrei nemmeno dirlo, ribadisco il
concetto che la stringa vada copiata SENZA virgolette]
salvarli come *.txt e darli in pasto al LOphtCrack, il quale sferrera'
anche lui un egregio attacco di forza
bruta.
[Finezza vorrebbe che, al posto di copiare 64 caratteri, consultaste
il file SAMInside.ini...]
Non mi dilunghero' sull'argomento LMhash NThash (ci sono una infinita'
di documenti in proposito). Sappiate solo che, quando sara' emersa la pass in
chiaro, dovrete tener conto della NT password e non della LM password (basta
anche un minimo di osservazione per rendersene conto!)

_________
| |
| OUTRO |
|_________|
|
+-CONCLUSIONI
|
| Che dire gente, se tutto e' andato per il meglio ora dovreste aver
| e per le mani la password di quel computer che
| bramavate tanto.
|

  

|
+-BENCHMARKS
|
| Come vi ho detto nella PARTE 2, avete due possibilita' per sferrar
| e il brute force: via SAMInside o via LOpht Crack.
| Riporto qui il tempo che ho impiegato con i due programmi per trov
| are una stessa password (9 lettere, no numeri,
| no caratteri speciali).
| SAMInside on P4 @ 2GHz: 21 minuti
| LOphtCrack on P4 @ 2GHz: 22 minuti
| Come potete vedere non c'e' una grande differenza tra i due...
|
| *** Benchmarks estremi ***
| Per darvi un'idea di quanto potrebbe durare il vostro cracking (e
| per capire se e' una cosa fattibile o meno),
| aggiungo delle tabelle che, ad ogni tot di caratteri, fanno corris
| pondere il tempo stimato per risalire alla
| password.
| ATTENZIONE: Questi dati NON li ho sperimentati di persona (li ho t
| rovati in rete) ma, a giudicare dai risultati che
| ho ottenuto nei miei test, li ho reputati attendibili.
|
| solo lettere:
|
| LUNGHEZZA PERMUTAZIONI AMD ATHLON @ 1GHz
| 01 26 < 1 secondo
| 02 676 < 1 secondo
| 03 17,576 < 1 secondo
| 04 456,976 < 1 secondo
| 05 11,881,376 04 secondi
| 06 308,915,776 02 minuti 19 secondi
| 07 8,031,810,176 01 ora 06 minuti 08 secondi
| 08 208,827,064,576 01 ora 06 minuti 05 secondi
| 09 5,429,503,678,976 01 ora 06 minuti 02 secondi
| 10 141,167,095,653,376 01 ora 06 minuti 05 secondi
| 11 3,670,344,486,987,776 01 ora 06 minuti 03 secondi
| 12 95,428,956,661,682,176 01 ora 06 minuti 05 secondi
| 13 2,481,152,873,203,736,576 01 ora 06 minuti 12 secondi
| 14 64,509,974,703,297,150,976 01 ora 10 minuti 14 secondi
|
|
| lettere e numeri:
|
| LUNGHEZZA PERMUTAZIONI AMD ATHLON @ 1GHz
| 01 36 < 1 secondo
| 02 1,296 < 1 secondo
| 03 46,656 < 1 secondo
| 04 1,679,616 < 1 secondo
| 05 60,466,176 24 secondi
| 06 2,176,782,336 16 minuti 18 secondi
| 07 78,364,164,096 10 ore 41 minuti 57 secondi
| 08 2,821,109,907,456 10 ore 41 minuti 38 secondi
| 09 101,559,956,668,416 10 ore 41 minuti 48 secondi
| 10 3,656,158,440,062,976 10 ore 42 minuti 43 secondi
| 11 131,621,703,842,267,136 10 ore 41 minuti 48 secondi
| 12 4,738,381,338,321,616,896 10 ore 43 minuti 04 secondi
| 13 170,581,728,179,578,208,256 10 ore 44 minuti 33 secondi
| 14 6,140,942,214,464,815,497,216 11 ore 22 minuti 48 secondi
|
|
| lettere, numeri e caratteri speciali (uk)**
|
| LUNGHEZZA PERMUTAZIONI AMD ATHLON @ 1 GHz
| 01 71 < 1 secondo
| 02 5,041 < 1 secondo
| 03 357,911 < 1 secondo
| 04 25,411,681 09 secondi
| 05 1,804,229,351 12 minuti 27 secondi
| 06 128,100,283,921 16 ore 20 minuti 47 secondi
| 07 9,095,120,158,391 circa 52 giorni, non testato completamente
|
| ** N.B. questo test e' stato effettuato con i caratteri speciali
| di una tastiera inglese, escluso l'euro, che sono
| !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£¬
| C'e' da far notare che, pero', nella tastiera italiana il range
| si allarga notevolmente perche' si
| possono utilizzare anche le lettere accentate che, sulle
| tastiere straniere, sono digitabili solo grazie
| alla combinazione ALT + sequenza_numerica
|
|
+-THANKS TO
|
| zi' Tufa -> per tutto il tempo buttato assieme dietro ad uno schermo
| BianConiglio -> il Prof, colui che mi ha indicato la Via.
| isa -> semplicemente per cio' che e'
|
|
+-IPSE DIXIT

"
Salimmo su', el primo e io secondo,
tanto ch'i' vidi de le cose belle
che porta 'l ciel, per un pertugio tondo.
E quindi uscimmo a riveder le stelle"
Dante Alighieri, Inferno Canto XXXIV

h23


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::



.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x08][APPRENDiSTA STREG0NE] C0DiCE iNVERS0: CRiTT0GRAFiA [Zer0]
:: DiGiTALE AVANZATA PARTE 7 ::
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

Siamo cosi' giunti alla fine di questo lunghissimo articolo, in cui
illustrero' alcune applicazioni "
pratiche" di crittografia & related. Vi
anticipo subito inoltre che visto che la sola introduzione alle curve
ellittiche richiederebbe un capitolo a parte NON le trattero' in questa
sede. Non sono poi nulla di troppo fondamentalmente diverso da quel che
abbiamo visto finora, ma potrebbero creare moltissima confusione su chi
non abbia una preparazione abbastanza "
matematica"...

Dal momento inoltre che ho notato il fiorire di oscenita' sull'argomento
crypto che stanno spuntando come funghi ovunque in questi ultimi tempi,
faccio presente che NON mi dilunghero' nel commentare tali oscenita'.
Non ne ho tempo ne' voglia. Ma checcazzo, e' scoppiata una moda?!

Non vi nascondo che sono lieto di mettere finalmente la parola fine a
quest'opera *mastodontica*, per tre motivi:

Il primo e' che il tempo e' denaro ma soprattutto e' tempo.

Il secondo e' che, ci crediate o no, non sono contento di cio' che ho
scritto e di come lo ho scritto. Dipende sicuramente dal fatto che sono
ormai piu' di due anni che la storia va avanti.
Io mi evolvo, e CODiCE iNVERSO si evolve con me.
Non illudetevi neanche lontanamente di aver capito di cosa sto parlando.
Non lo capite, e non mi aspetto che lo capiate. A darmi fastidio e'
principalmente il fatto che l'articolo avrebbe potuto essere trattato
molto meglio sin dall'inizio, salvo che poi alla fine avrei voluto
ricominciare di nuovo tutto da capo.

Il terzo motivo e' che, come mi sembra di aver gia' detto, mi sento
pericolosamente braccato dalla banalita' del mondo. Il leggere tra i
titoli di una rivista per computer (una di quelle stronzate tipo "
pc
facile" o roba del genere) una scrittona gigantesca "SPECIALE
CRITTOGRAFIA: TUTTI I SEGRETI PER TENERE A BADA GLI HACKERS" mi fa
accapponare la pelle. Cristo, ma cosa sta succedendo?

Giorno dopo giorno sento il fiato del Sistema sul collo. Io non vorrei
che leggiate questo articolo, non ve lo meritate. Almeno, il 90% di voi
non se lo meritano, e non e' gratificante sapere di scrivere solo per
quattro stronzi che non vogliono arrendersi e che vogliono continuare a
lottare.

Ma io continuero' a lottare con voi.

Non e' una promessa, e' di piu'. E' un sacrifico, un gesto di
fratellanza. E' la mia, la nostra scelta. La scelta di chi ha deciso che
continuera' a combattere anche quando rimarra' da solo.

E se sono solo, che scrivo a fare?

Ma un impegno e' un impegno, ed io ho preso l'impegno, una sera su IRC
di tanto tempo fa, di finire questo articolo senza lasciarlo a meta', ed
e' quello che faro'. Temo che, di tutte le cose che ci spingono a
vivere, il senso di responsabilita' sia quello piu' intenso e
irrazionale. Ma e' anche una di quelle cose, di quelle poche cose, per
cui vale la pena vivere...


<Errata Corrige delle puntate precedenti>

Cap.2: le matrici si scrivono tra tonde, non si smettera' mai di fare
confusione.

Funzione lineare significava in effetti "
con tutti i termini di
grado 1" (e finiamola qui, anche se non sarebbe correttissimo).

In generale tutto il capitolo 2 e' un grande cumulo di immondizia
e nulla di piu'. Molti di voi se ne saranno gia' accorti a suo
tempo. Esso aveva comunque solo lo scopo di far capire al newbie
i concetti di base, e non di fornire all'esperto spiegazioni
dettagliate, cha tralaltro potra' trovare in qualsiasi buon
testo di algebra. Spero che lettori con una discreta
preparazione matematica lo abbiano semplicemente ignorato.

Cap.9: paragrafo "
generazione di numeri primi nel DSA"
l'algoritmo che ho riportato io e' sbagliato. Cioe', la procedura
e' giusta, ma l'algoritmo:

W = V[0]
for i = 1 to (n - 1):
W = W (V[i] << (160 * n))
next i
W = W (V[n] && ((1 << b) - 1))

va sostituito con questo (in pratica mi ci e' finita una "
n" al
posto di una "
i") :

W = V[0]
for i = 1 to (n - 1):
W = W (V[i] << (160 * i))
next i
W = W (V[n] && ((1 << b) - 1))

</Errata Corrige delle puntate precedenti>




+----------------------------------------------------------------------+
APPLICAZIONI DELLA CRITTOGRAFIA
. . . . . . *--------------------------------------------------------+
Capitolo 10 / Ha la forza di undici aquile...
+-----------*

Orbene, giunti a questo punto siamo consapevoli del fatto di aver fatto
una bella sgobbata, di esserci accresciuti "
culturalmente" e di aver
perso una buona parte di quei 2^8 neuroni pronosticati
nell'introduzione.

Ma forse avete la sensazione che manchi ancora qualcosa...

L'ingrediente che manca e', naturalmente, il "
cemento", ovvero qualcosa
che ci permetta di solidificare tante belle nozioni
informatico-matematiche in qualcosa di concreto... In un'arma magari.
E' per questo che sarebbe stato sciocco non trattarlo
nell'ULTIMO capitolo. Questo "
qualcosa" sono le "istruzioni pratiche", e
cioe' le direttive di base per realizzare un programma di crittografia,
per creare comunicazioni schermate dall'esterno, per difendersi dagli
attacchi che attivamente possono essere portati alle infrastrutture a
cui affidiamo la nostra difesa, nonche' le indicazioni topografiche
per orientarsi nel mondo dei codici e dell'informazione occulta, un
mondo pieno di fantasmi e luoghi comuni.



===========================
*** SISTEMI DI ARMORING ***
===========================

Avevo gia' accennato al cap.5 gli algoritmi di armoring: servono a
trasformare un qualsiasi file (tipicamente file binario) in un file
composto esclusivamente di caratteri stampabili, allo scopo di poterlo
spedire per posta ad esempio senza che si verifichino problemi di
malinterpretazione dovuti ai diversi codici di controllo che cambiano da
sistema a sistema.
Un elementare algoritmo di armoring potrebbe essere la codifica
esadecimale: le 16 cifre dell'hex sono tutte appartenenti al range
"
stampabile" del charset ascii (uso i termini "stabile" e "stampabile"
piu' o meno come sinonimi), e infatti andrebbero bene al nostro
scopo. Solo che, come avevo gia' fatto osservare, per ogni byte
servirebbero DUE caratteri hex, quindi le dimensioni del messaggio
raddoppierebbero.

Si puo' pero' tentare di fare una cosa simile tentando di "
mitigare"
questo fenomeno di aumento delle dimensioni, con la seguente
considerazione: nel caso esadecimale porto un messaggio da pacchetti di
8 bit (byte) a pacchetti di 4 bit (nibble), ognuno dei quali viene di
nuovo codificato in un pacchetto di 8 bit "
stabile" (cifra esadecimale).
Cio' lo posso fare perche' esistono almeno 2^4 = 16 caratteri "
stabili"
nel charset ascii. Il rapporto dimensioni e' di 8 a 4, cioe' raddoppia.
Ma nel charset ascii esistono ben piu' di 16 caratteri stabili, perche'
non sfruttarli?

Vediamo... riesco a trovare almeno 32 caratteri stabili nel charset
ascii? Certo che si', ad esempio le 10 cifre e le prime 22 lettere
minuscole. Allora bingo! Posso creare un algoritmo simile che prenda
pacchetti di 8 bit, li "
spezzi" in pacchetti di 5 bit (perche 2^5 = 32)
e li ricodifichi poi ciascuno con un singolo carattere ascii tra quei 32
che ho scelto. Visto che 5 non divide 8 dovremo prendere 40 bit per
volta (5 pacchetti da 8 bit) e splittarli in 8 pacchetti da 5 bit
(perche' il minimo comune multiplo tra 5 e 8 e' 40). In questo caso il
rapporto dimensioni e' di 8 a 5, cioe' e' MENO del doppio della
dimensione originale, quindi gia' ho ottenuto qualcosa di piu'
vantaggioso rispetto all'hex.

Ma come avrete capito si puo' fare di meglio. Ne riesco a trovare 64 di
caratteri stabili? Si', ad esempio:
- le 10 cifre
- le 26 lettere minuscole
- le 26 lettere maiuscole
- altri due caratteri scelti tra quelli stampabili, ad esempio "
+" e
"
/"
In questo caso il rapporto e' di 8:6, cioe' 4:3, cioe' un messaggio
codificato cosi' aumenta solo di un terzo le sue dimensioni! Visto che
m.c.m.(8, 6) = 24 bisognera' prendere 3 pacchetti da 8 bit e splittarli
in 4 pacchetti da 6 bit, per poi ricodificarne ciascuno con uno dei 64
caratteri stabili da 8 bit che abbiamo scelto.
Ecco, questa che ho descritto e' esattamente la codifica Base64 usata
dal PGP e dalla maggior parte dei client di posta elettronica. Ecco la
tabella per la codifica:

0 = A 16 = Q 32 = g 48 = w
1 = B 17 = R 33 = h 49 = x
2 = C 18 = S 34 = i 50 = y
3 = D 19 = T 35 = j 51 = z
4 = E 20 = U 36 = k 52 = 0
5 = F 21 = V 37 = l 53 = 1
6 = G 22 = W 38 = m 54 = 2
7 = H 23 = X 39 = n 55 = 3
8 = I 24 = Y 40 = o 56 = 4
9 = J 25 = Z 41 = p 57 = 5
10 = K 26 = a 42 = q 58 = 6
11 = L 27 = b 43 = r 59 = 7
12 = M 28 = c 44 = s 60 = 8
13 = N 29 = d 45 = t 61 = 9
14 = O 30 = e 46 = u 62 = +
15 = P 31 = f 47 = v 63 = /

Quando prendo un messaggio codificato in questo modo e vado in lettura,
dovro' prendere 4 caratteri per volta (32 bit), riportarli ciascuno
all'equivalente di 6 bit (24 bit in tutto), quindi risegmentarli nei 3
byte originali.

Problema 1: il messaggio cosi' facendo dovra' avere un numero di byte
multiplo di 3, perche' ne prendo 3 per volta. Analizziamo i casi
possibili considerando "
l" = lunghezza in byte del messaggio:

a) l Mod 3 = 0 : In questo caso il messaggio e' multiplo di 3
byte, quindi non ci sono problemi e il codice in output sara'
multiplo di 4 byte.

b) l Mod 3 = 1 : c'e' un byte "
extra" rimasto, in questo caso
bisogna aggiungere in coda a questi 8 bit 4 zeri (SHL 4), in
modo da avere 12 bit. Poi spezzo in due blocchi da 6 bit e li
codifico normalmente. In questo caso il messaggio in output
sara' multiplo di 4 byte, piu' due byte (caratteri) extra.

c) l Mod 3 = 2 : analogamente: ci sono 2 byte "
extra". Bastera'
aggiungere 2 zeri in coda (SHL 2) per ottenere un blocco da
18 bit, da segmentare poi in 3 blocchi da 6 bit e codificarli
normalmente. In questo caso il testo in output sara' multiplo
di 4 piu' tre caratteri extra.

Problema 2: se prendo un messaggio codificato Base64 dovro' accertarmi
che la sua lunghezza in byte sia multiplo di 4, oppure analizzare i vari
casi, indicando con "
L" la lunghezza del testo codificato:

a) L Mod 4 = 0 : nulla da segnalare, il testo originale era
multiplo di 3 byte, quindi posso prendere 4 byte per volta e
decodificarli normalmente.

b) L Mod 4 = 1 : se guardate i casi precedenti vi accorgete che
questa eventualita' non era contemplata (puo' esserlo solo se
invece che byte state codificando una sequenza di bit non
multipla di 8 bit, ma in questo caso il tutto diventa un po'
piu' complicato), quindi significa che si e' verificato un
errore di trasmissione!

c) L Mod 4 = 2 : questo sarebbe il caso b) riportato per la
codifica: dopo aver decodificato i due byte extra in un blocco
da 12 bit dovrete togliere i 4 zeri finali ed ottenere un unico
blocco da 8 bit.

d) L Mod 4 = 2 : analogamente: basta invertire il passo c) della
fase di codifica.

Lo standard Base64 inoltre prevede che il testo codificato sia SEMPRE
multiplo di 4 byte, per farlo basta aggiungere in coda al testo
codificato ottenuto tanti caratteri "
=" (segno uguale) quanti ne
occorrono per arrivare a lunghezza multipla di 4 (quindi uno o due).
Tali caratteri "
=" saranno semplicemente ignorati in fase di decodifica.

E questo era il Base64. Resta una domanda: non si poteva fare di meglio?
Non era possibile una codifica a 7 bit? La risposta e' no, perche'
purtroppo non riesco a trovare 2^7 = 128 caratteri ascii "
stabili". O
meglio, alcuni sistemi di trasporto prevedono anche una codifica a 7
bit, ma e' valida solo in determinate circostanze (es.: conversione
automatica tra sistemi diversi), quindi non e' un vero e proprio sistema
di armoring, non del tutto versatile almeno. Un altro sistema molto
diffuso e' l'Uuencode, ma e' cmq funzionalmente uguale al Base64.

Sempre in appendice troverete delle routine per la conversione e
deconversione dei dati in hex e base64 in VB (e per il base64 anche in
C) che ho codato nelle notti insonni. Per le specifiche tecniche vi
rimando al readme allegato.



==========================================
*** GENERATORI DI NUMERI PSEUDOCASUALI ***
==========================================

Come dovreste ormai aver capito, il cuore di ogni sistema crittografico
sicuro e' dato dal generatore di numeri random. La crittografia infatti
non "
vive" senza caos, la scelta di un buon RNG e' la prima cosa da fare
per progettare un crittosistema.

<lettore> e' da un pezzo che lo ripeti, non gli starai dando un po'
troppa importanza a sta' cosa?

Assolutamene no! La generazione di dati random e' alla base di TUTTO,
tuttavia e' anche la cosa meno banale. I sistemi di generazione che
trovate nella maggior parte delle applicazioni infatti (cito, ad
esempio, la funzione Rnd() del VB) sono poco piu' che giocattoli, del
tutto inadeguati ad applicazioni crittografiche, ma comunque validi per
i giochi ad esempio, o per la maggior parte delle simulazioni
scientifiche. Quello che serve nella crittografia e' un generatore
random progettato per uso espressamente crittografico, che deve avere
quindi proprieta' diverse da quelle di un comune RNG.

Come gia' accennato al cap.4 naturalmente, l'esistenza di un vero
generatore di numeri random e' una mera utopia. Questo perche' un
computer (qualsiasi computer) e' una macchina deterministica, che puo'
assumere solo un numero finito di stati: esso accetta in ingresso degli
input, effettua su di essi delle operazioni algebriche totalmente
prevedibili e da' in uscita un output che e' diretta conseguenza dei
processi che sono avvenuti all'interno di esso. Jon Von Neumann
affermava che "
chiunque tenti di riprodurre numeri casuali tramite
metodi algebrici, evidentemente vive nel peccato". Questa frase riassume
bene l'impossibilita' di ottenere dati veramente random da un computer:
come gia' detto, per avere anche solo una parvenza di casualita' bisogna
inevitabilmente appoggiarsi all'ambiente esterno.

Il punto e': a scopo crittografico (in generale per qualsiasi scopo) non
c'e' bisogno di avere dati strettamente random, in pratica e'
sufficiente che tali dati SI COMPORTINO come se fossero random. Qui
andiamo un po' nel filosofico, ma e' lo stesso discorso che si faceva a
proposito del fatto che il Caos deriva semplicemente dall'incapacita'
dell'essere umano di comprendere relazioni complesse. Se una sequenza di
dati random generata con un computer (e quindi NON E' random...) appare
agli occhi di chiunque, persino a quelli di un altro computer,
totalmente casuale, passa tutti i test statistici di rumore, ha entropia
del 100% etc., allora posso affermare che e' abbastanza casuale da
poterla considerare random. In effetti in questi casi e' piu' giusto
parlare di dati (e quindi di generatore) PSEUDORANDOM, cioe' QUASI
random.

In pratica un generatore si dice pseudorandom se ha la seguente
proprieta':

- Sembra random. Cioe' i dati prodotti passano tutti i test di
casualita'.

Ecco alcuni test tipici:
- misura dell'entropia (deve essere del 100% o quasi)
- bilanciamento 0-1 (cioe' devono esserci circa meta' zeri e meta' 1)
- controllo del periodo (un generatore e' per forza di cose,
periodico, ma il suo periodo deve essere grandissimo)
- distribuzione run lenght (run lenght e' la lunghezza di una stringa
di zeri adiacenti, oppure di uni adiacenti, es: la stringa
"
01100001" ha una zero run lenght massima di 4 ed una one run lenght
massima di 2. In una sequenza pseudorandom la meta' delle run lenght
deve essere uno, un quarto deve essere due, un ottavo deve essere
tre etc... Inoltre la distribuzione deve essere la stessa per gli
zero e gli uno)

Questo non e' ancora sufficiente per la crittografia. Un generatore si
dice "
generatore crittografico sicuro" se ha la seguente proprieta':

- E' imprevedibile. Deve essere arduo conoscere quale sara' il
prossimo bit in output, anche conoscendo tutti i bit di output
usciti finora.

Infine, un generatore ideale e' quello "
veramente random", che ha questa
proprieta':

- Non puo' essere riprodotto, cioe' se metti gli stessi identici
input in uno stesso identico generatore dello stesso tipo ottieni
output totalmente diversi.

La dicitura "
stesso identico" e' filosoficamente controversa, e non solo
filosoficamente. Anche in termini fisicamente teorici non avro' mai
il modo di dire che due oggetti sono "
identici", poiche' non posso
misurarne tutti gli stati contemporaneamente (Heisenberg rulez...).
Per quel che se ne sa questa ultima citata e' una proprieta' tipica
soltanto di sistemi complessi e delicati al punto di essere molto
sensibili alle fluttuazioni quantistiche ambientali, parafrasando
significa che per ottenerla e' necessario l'intervento di Dio (e'
esattamente cio' che in pratica afferma la fisica quantistica, ovvero
che "
da qualche parte la' fuori c'e' una fonte di Caos").
Queste fluttuazioni non sono apprezzabili nei moderni chip in silicio,
che anzi sono studiati per essere insensibili all'effetto tunnel ad
esempio, per ottenere questa proprieta' e' necessario appoggiarsi ad un
qualche rumore di fondo, misurando gli intervalli di tempo dei
keystrokes, degli spostamenti del mouse, l'uso della cpu o della
memoria, lo spazio utilizzato su disco, l'entropia dell'intero hard
disk, il noise ambientale etc. Riguardo al noise un metodo interessante
ed usato in applicazioni professionali e' quello di catalogare la
corrente di fondo prodotta da un dispositivo hardware "
sensibile", come
un contatore Geiger o un diodo Zener.

Per ottenere dei buoni RNG bisogna prima catalogare una buona quantita'
di questi dati, poi processarli tramite funzioni irreversibili per
"
distillarli" (tipo con una funzione hash), poi magari ricombinarli con
i dati prodotti da dispositivi "
logici" come i LFSR (Linear Feedback
Shift Register) o cifrari a flusso, o generatori modulari etc... E poi
magari riprocessare e ridistillare il tutto tramite funzioni
irreversibili. Ogni volta che avete bisogno di dati random ricaricate la
vostra "
pool" di noise ambientale, riprocessate i dati random che
avevate salvati la volta precedente, reinizializzate tutti gli IV, tutte
le chiavi, mixate il tutto... Una volta finito salvate su disco lo
"
stato interno" del generatore in un file di randseed, crittate il
randseed. La prossima volta ri-decrittatelo, raccogliete nuovamente
rumore di fondo, ri-processate, etc... Come al solito' pero' la
complessita' non e' sinonimo di sicurezza, quindi diffidate dei
generatori fatti in casa.

Tra i generatori random ad uso crittografico piu' conosciuti cito il
"
Marsaglia's mother of all RNG" e lo Yarrow, della Counterpane.



==========================
*** TEST DI PRIMALITA' ***
==========================

Al cap.2 avevo descritto il Test di Fermat (ora che ci penso, si legge
"
ferma'"...) per la primalita', ma poi avevo anche accennato che quello
era uno dei piu' elementari, e che in genere erano altri i test di
primalita' utilizzati normalmente per la generazione di numeri primi.

Per approfondire alcuni di questi test servirebbe di fare un'aggiunta,
anche abbastanza sostanziosa, al cap.2, cosa che a grande richiesta del
pubblico NON FARO'. In compenso ce n'e' uno di questi test che sara'
possibile trattare qui, che tralaltro e' anche il migliore di tutti, ed
e' quello che viene comunemente usato nelle librerie aritmetiche
multiprecisone come la GMP e da applicazioni come PGP. E' il Test di
Rabin-Miller:

0) Scegli un numero random "
p" da testare. Calcola "b" = numero di
volte che 2 divide (p - 1). Poi calcola "
m" tale che
p = m * 2^b + 1 (nella pratica: setta il bit low-end di p a
zero, b e' il numero di zeri low-end di quel che ottieni ed m
e' p >> b).

1) scegli un numero random "
a", maggiore di 1 e minore di p.

2) setta le variabili "
j" = 0 e "z" = a^m (mod p).

3) se z = 1 oppure z = p - 1, allora p passa il test e potrebbe
essere primo.

4) se j > 0 e z = 1 allora p non e' primo.

5) incrementa j (j = j + 1).
Se j < b e z != (p - 1) allora:
- setta z = z^2 (mod p)
- torna al punto 4).
Se z = p - 1, allora p passa il test e potrebbe essere primo

6) se j = b e z != (p - 1), allora p non e' primo.

L'efficienza di questo test e' doppia rispetto a quella degli altri test
standard: la possibilita' che un numero composto passi il test e' solo
di un quarto per ogni test, quindi facendo "
n" tentativi su un singolo
numero, se questo li passa tutti la possibilita' che questo non sia
primo e' solo di 1 / 4^n.

Nella pratica, per generare un numero primo di lunghezza "
l" si fa
cosi':

- genera una sequenza random di l bit
- setta il bit high-end a 1 (per assicurarsi che il numero sia
della dimensione voluta)
- setta il bit low-end a 1 (per assicurarsi che il numero sia dispari)

(trucco: se volete generare un DH-prime settate i DUE bit low-end a 1, e
non solo l'ultimo bit, lascio al lettore lo spippolamento matematico per
comprendere il perche' di questa scelta, cosa peraltro ESTREMAMENTE
elementare)

- controllane la non-divisibilita', tramite trial division, per i primi
piccoli numeri primi da 3 in su (Lenstra consiglia fino a 1999)
- se il numero supera questa prima fase fai alcuni test di Rabin-Miller
(5 o 6 in genere bastano), se il numero li passa tutti, allora e'
probabilmente primo, altrimenti generane un altro (oppure incrementa
di due il numero appena testato e ricontrolla, questa opzione di
ricerca incrementale e' in genere piu' veloce anche se a volte meno
sicura)

La trial division dei primi numeri primi (ops! :P) e' opzionale ma e'
un'ottima idea: una semplice divisione e' molto piu' veloce del test di
Rabin-Miller, e scarta a priori la maggior parte dei candidati.
Esempio: testare la non-divisibilita' per 3, 5 e 7 elimina il 54% dei
numeri dispari, testarla per i numeri primi minori di 256 ne elimina
l'80% etc...



===================================
*** DISTRUZIONE SICURA DEI DATI ***
===================================

Quando eliminate un file, questo non viene effettivamente cancellato da
disco, bensi' ne viene rimossa la relativa voce nel file system. In
questo modo, anche se i dati risiedono ancora fisicamente sul supporto,
il sistema considera la relativa area di memorizzazione come se fosse
vuota, e quindi alla prima occasione in cui avra' bisogno di nuovo
spazio per altri dati sovrascrivera' quelli vecchi senza curarsene.
Questo sistema e' molto rapido, ma come avrete capito e' anche molto
pericoloso. Infatti dal momento in cui un file viene cancellato al
momento in cui il sistema ci scrive sopra qualcos'altro possono passare
giorni, settimane, mesi. Se il file da eliminare contiene informazioni
*delicate* questo puo' essere un problema. Potreste ricevere
inaspettatamente a casa vostra la visita di alcuni gentili signori in
giacca e cravatta i quali, utilizzando una semplice utility per il
recupero dei dati, cavano fuori dal vostro pc la lettera di auguri che
vi aveva spedito il vostro amico Osama (quello dei pennarelli) tempo
addietro, e che eravate convinti di aver cancellato. Piuttosto seccante.
Utility del genere si trovano facilmente via Internet o su riviste di
computer e sono alla portata di chiunque. Crittare i file o le e-mail e'
del tutto inutile se poi dovete formattare il disco ogni volta che avete
bisogno di cancellare qualcosa di "
scottante".

Fortunatamente c'e' la soluzione a questo problema, ed e' inclusa in
pacchetti tipo PGP o GPG. La soluzione si chiama WIPE ("
uaip" :P). In
pratica consiste nella cancellazione sicura dei dati. Se avete bisogno
di eliminare un file in maniera sicura, utilizzando lo wipe del PGP ad
esempio, il file verra' PRIMA sovrascritto con dati random, e POI
cancellato dal file system. Naturalmente dovrete usarlo con attenzione
perche' se vi cancellate per sbaglio qualcosa di utile con questo
sistema, tipo le vostre foto porno di Daria Bignardi, poi dovrete
chiamare Dio per riesumarle dal vostro pc.


*** ATTACHI ALLO WIPE ***

Qui andiamo in piena paranoia, cio' significa che questi attacchi sono
attualmente tra i piu' fantascientifici che si e' riusciti a immaginare
(anche se, come vedrete alla sezione del brute-forcing c'e' di peggio ;)
ed e' poco probabile che possano essere sfruttati con successo. Tuttavia
e' girata voce tempo addietro che l'FBI disponeva di una macchina in
grado di recuperare dati sovrascritti, motivo per cui non sara'
superfluo prendere le dovute precauzioni.

E' facile immaginare che agenzie molto potenti abbiano investito fondi
nella ricerca di sistemi per recuperare dati distrutti in questo modo.
Al momento sono state fatte delle ipotesi per lo sfruttamento teorico di
un paio di fenomeni che potrebbero in effetti riuscire nell'intento (si
tratterebbe comunque di tecnologie costosissime).

Il primo fenomeno si basa sull'allineamento delle testine. In un
hard-disk i dati vengono scritti su un piatto di materiale
magnetizzabile da delle minuscole testine, le quali seguono una traccia
circolare sulla superficie. E' logico aspettarsi che l'allineamento di
queste testine cambi nel tempo a causa dell'uso, anzi, teoricamente
potete immaginare che una testina non percorre mai due volte la stessa
identica traccia. Sarebbe quindi possibile utilizzare un "
microscopio
magnetico" per osservare il bordo delle tracce recenti ed osservare le
vecchie tracce, le quali non sono state del tutto "
ricoperte" da quelle
nuove.

Il secondo fenomeno riguarda la magnetizzazione delle aree. Un singolo
bit e' rappresentato in una minuscola area dell'hard disk
magnetizzandola con una certa polarita' (es.: "
1" per polarita'
positiva, "
0" per polarita' negativa). Nella pratica il livello di
magnetizzazione della superficie e' una funzione continua, quindi ci
possono essere dei valori intermedi. Il circuito dell'hard-disk
naturalmente opera una "
quantizzazione" di questo segnale, tollerando
una certa soglia d'errore rispetto alla polarita' prevista, allo scopo
di fornire al computer un'informazione discreta, cioe' 0 o 1 (ne avevamo
gia' parlato al cap.3). Nel momento in cui la testina vada a
sovrascrivere un bit, essa cambia la polarita' della relativa
superficie. Ora, se il bit da sovrascrivere e' uguale a quello che c'era
prima, la testina "
spingera'" ancora di piu' la polarita' verso una
determinata direzione (ad esempio, se la polarita' era negativa, e'
logico aspettarsi che la rendera' ancora un po' piu' negativa). Se
invece il bit era diverso, essa ne invertira' la polarita' e quindi
fara' *piu' fatica*. In pratica, sara' logico aspettarsi di trovare un
"
rumore di fondo" nella magnetizzazione delle aree dovuta a piccole
differenze di valore, a seconda che l'area abbia assunto spesso uno
stesso stato oppure che abbia cambiato frequentemente di polarita'.
Utilizzando un dispositivo molto sensibile potrebbe essere in teoria
possibile "
leggere in trasparenza" i vecchi bit mediante un'approfondita
analisi di questo rumore.


*** CONTROMISURE ***

Fortunatamente e' abbastanza facile proteggersi anche da queste
alienita'. Il metodo piu' semplice e' quello di sovrascrivere PIU' VOLTE
con dati random il file interessato prima di cancellarlo dal file
system. Questo va a scapito della velocita' ma aumenta la sicurezza,
spettera' all'utente trovare il giusto compromesso.



=====================
*** STEGANOGRAFIA ***
=====================

La parola "
steganografia" ha un'etimologia molto simile a quella di
"
crittografia": significa qualcosa del tipo "occultamento del
messaggio". In effetti questa tecnica si e' diffusa molto con l'avvento
della crittografia digitale, ma va troppo spesso confusa.
Mentre lo scopo della crittografia e' quello di rendere un messaggio
illeggibile agli occhi di chi non e' autorizzato, lo scopo della
steganografia e' quello di fare in modo che una persona non autorizzata
NON SI RENDA NEMMENO CONTO dell'esistenza del messaggio.
Alcuni esempi: gli inchiostri invisibili (tipo il succo di limone, che
diventa visibile se avvicinato a una fonte di calore), gli acrostici
(come un testo in cui le iniziali di ogni capoverso lette in sequenza
formano un messaggio nascosto), ma la steganografia e' una tecnica molto
antica. Si racconta di un antico re Persiano che fece radere a zero uno
dei suoi schiavi per tatuargli un messaggio sulla nuca (che quindi
neanche lo schiavo poteva leggere), per poi inviarlo alla sua
destinazione una volta che i capelli gli furono ricresciuti, con
l'ordine di raderseli di nuovo.
La crittografia fallisce nel suo scopo se il nemico legge il messaggio.
La steganografia invece fallisce se il nemico anche solo scopre
l'esistenza del messaggio.

La domanda e': come si fa a implementare la steganografia nel mondo
digitale? Risposta: sfruttando il noise presente in alcuni file, di
solito multimediali. Facciamo un esempio.

Prendiamo un'immagine bitmap con una profondita' di colore di 24 bit.
Tipicamente essa e' salvata in formato RGB, cioe' ogni pixel e'
rappresentato da un valore a 24 bit che ne identifica il colore, valore
composto da 3 byte, ognuno dei quali rappresenta l'intensita' di uno dei
tre colori primari (che nel caso della cinematografia sono rosso, verde
e blu, Red-Green-Blue appunto, e non rosso giallo e blu come accade di
solito). Esempio:

Pixel nero:
rosso: 0 parti su 255
verde: 0 parti su 255
blu: 0 parti su 255

allora il suo RGB sara' (0, 0, 0)
ovvero (00000000 00000000 00000000) = 0.

Pixel rosso acceso:
rosso: 255 parti su 255
verde: 0 parti su 255
blu: 0 parti su 255

allora il suo RGB sara' (255, 0, 0)
ovvero (11111111 00000000 00000000) = 16711680.

Pixel bianco:
rosso: 255 parti su 255
verde: 255 parti su 255
blu: 255 parti su 255

allora il suo RGB sara' (255, 255, 255)
ovvero (11111111 11111111 11111111) = 16777215.

etc...

Allora posso fare la seguente considerazione: se io ho un pixel del tipo
(200, 23, 51) (cioe' molto vicino al rosso) e lo cambio con un pixel del
tipo (206, 21, 55), voi pensate che ad occhio nudo la differenza si
notera' nell'immagine? Potete fare una prova, ma la risposta
naturalmente e' NO. Infatti gli ultimi bit di ognuno dei 3 byte dei
canali del colore influiscono molto poco sulla visione d'insieme: essi
sono infatti RUMORE DI FONDO, e come tali possono essere eliminati
compromettendo pochissimo la qualita' dell'immagine (come accade in
alcuni algoritmi di compressione).
Oppure possono essere... sfruttati. Tipo "
iniettando" dei bit scelti da
noi al posto di quelli di fondo. Ad esempio:

c'ho un pixel di colore (200, 23, 51). La sua rappresentazione in bit
sara':

Byte 1: colore R: 200 = 11001000
Byte 2: colore G: 23 = 00010111
Byte 3: colore B: 51 = 00110010

Ora voglio "
iniettare" in questo pixel i seguenti 9 bit d'informazione:
"
110 101 111". Detto fatto: cambio gli ultimi 3 bit low-end di ogni
canale:

Byte 1: colore R : 11001 110 = 206
Byte 2: colore G : 00010 101 = 21
Byte 3: colore B : 00110 111 = 55

e il risultato sara' praticamente indistinguibile dall'originale. Cosi'
facendo posso iniettare 9 bit d'informazione ogni 24, cioe' il 37.5%.
Bisogna naturalmente trovare un compromesso, perche' iniettarne di piu'
potrebbe alterare visibilmente l'immagine, attualmente forse 9 bit su 24
sono anche un po' troppi. Se posso sostituire ad esempio, il 25%
d'informazione, posso iniettare un file da 200 KB all'interno di uno da
800 KB. Questo descritto e' un metodo generale, si puo' estendere a file
audio, video, etc... Naturalmente nella maggior parte dei casi non si
potranno iniettare bit nel codice RGB, perche' spesso questo non viene
mantenuto (ad esempio nel formato Jpeg), in questi casi bisognera'
sfruttare altre informazioni presenti nel formato specifico in cui e'
presente noise, come i coefficienti di Fourier o i valori delle matrici
per la compressione DCT, i cicli d'onda sonora etc... Ogni volta in cui
posso effettuare una scelta che non compromette in maniera apprezzabile
il medium, tale scelta "
veicola" informazione (come gia' visto al
cap.5), informazione che puo' essere sfruttata.


*** INDIVIDUARE L'INFORMAZIONE OCCULTA ***

Purtroppo e' piu' facile di quel che si crede. Infatti il noise di un
dato formato di file spesso assume delle determinate caratteristiche. E'
quindi possibile analizzare il rumore di fondo di un'immagine per poter
dire se contiene o no informazione occulta, anche se questo metodo e'
molto dispendioso in termini di risorse di calcolo e difficilmente
potrebbe venire implementato da sistemi di monitoraggio automatico come
Carnivores o Echelon. Uno dei concetti base della teoria
dell'Informazione e' la Distanza di Unicita'. Senza entrare nel
dettaglio, se io analizzo un rumore di fondo e questo ha una lunghezza
maggiore di un certo valore e un'entropia inferiore ad una certa soglia,
esiste una possibilita' trascurabile che esso non contenga informazione
occulta. E questi sono metodi di individuazione generali, altri metodi
si basano sul sistema di steganografia scelto e sono molto piu'
efficaci. Ad esempio: iniettare dati mediante la scelta "
pilotata" dei
colori di palette di un'immagine GIF a colori e' molto insicuro, mentre
lo e' di meno se l'immagine e' in scala di grigi.

Ovviare a questi problemi pero' e' possibile. Se ad esempio aumento la
dimensione del file "
contenitore" e riduco la percentuale di bit
occulti, l'informazione segreta viene letteralmente "
esplosa"
all'interno del contenitore, ed e' molto piu' difficile rintracciarla.
Il metodo migliore pero' resta quello di usare la steganografia in
congiunzione con la crittografia. Basta scegliere un medium in cui
l'entropia del rumore di fondo e' molto alta (a livelli dell'entropia di
un cyphertext). In questo modo tale noise ha le stesse caratteristiche
di un cyphertext, quindi e' possibile sostituire il rumore con il testo
crittato e nessuno si accorgera' della differenza. Cosi' facendo
l'esistenza dell'informazione occulta e' tecnicamente dimostrabile SOLO
conoscendo la chiave del cyphertext. Questo metodo, se usato con
criterio, e' uno dei piu' inattaccabili.



============================
*** FILE SYSTEM CRITTATI ***
============================

Una cosa molto interessante e' la possibilita' di crittare non un
singolo file, ma inte

  
re aree dell'hard-disk. Infatti il file system e'
comunque rappresentabile come informazione binaria, e quindi potrebbe
essere in teoria soggetto a crittazione. Esistono programmi appositi con
i quali si puo' ad esempio creare una partizione crittata del disco
fisso, alla quale si puo' accedere solo inserendo una passphrase. I
vantaggi sono notevoli.
Il primo riguarda la comodita': invece che decrittare volta per volta
(inserendo ogni volta la passphrase) ogni singolo file su cui lavorare,
potete salvare i vostri dati riservati sull'apposita partizione
crittata. All'inizio della sessione di lavoro inserite la passphrase per
"sbloccare" la protezione, e quando spegnete il pc la partizione si
riblocca automaticamente.
Il secondo riguarda la sicurezza. Con questo sistema e' possibile
stabilire una combinazione di tasti per lo "smontaggio" rapido della
partizione ad esempio, in modo che appena sentite bussare alla porta o
fare irruzione, se siete svelti avete quei 0.3 secondi sufficienti a
mettervi al sicuro. Inoltre se il nemico vi toglie di botto la corrente,
alla riaccensione del pc i dati non saranno piu' in chiaro, perche' la
partizione viene decrittata dinamicamente (trucco: dotatevi di un
gruppo di continuita', anche da quattro soldi, poiche' in casi di
irruzione delle forze dell'ordine durante indagini in cui si miri anche
al contenuto del pc, la prima cosa che fanno e' togliervi la corrente).
Questo significa che se avete bisogno di un file all'interno di essa,
il sistema non decrittera' l'intera partizione, ma decrittera' SOLO
quel particolare file di cui avete richiesto l'apertura, mantenendolo
pero' soltanto in RAM (che e' volatile), per poi ricrittarlo alla
chiusura del documento nel caso che questo venisse modificato.

Ma le applicazioni sono infinite :)

Potete ad esempio impostare DUE passphrase: una serve effettivamente a
sbloccare la partizione, un'altra serve a formattarla irrimediabilmente.
Se il nemico vi costringesse a rivelargliela, beh... potreste fargli uno
scherzetto. Egli non avrebbe modo di sapere se la passphrase e' quella
giusta se non DOPO che questa ha gia' fatto il suo lavoro.

Inoltre potete impostare il sistema affinche' richieda, oltre ad una
passphrase, un piccolo file-chiave che salverete su un floppy o su un cd
e nasconderete. Una cosa da tener presente durante le perquisizioni e'
che una passphrase e' lunga e difficile da tenere a mente e quindi puo'
essere facilmente *dimenticata* ;) Stesso discorso per la chiave
salvata su supporto esterno ("Accidenti! Non ricordo proprio dove ho
messo quel floppy..."
).

C'e' di piu': il programma che si occupa dello sblocco potrebbe mostrare
all'inizio la schermata di richiesta del floppy, permettendo in realta'
comunque l'accesso alla digitazione della sola passphrase. Un bluff
insomma. Il nemico vedendo la schermata si spacchera' in quattro per
cercare un floppy inesistente.

Infine l'ultima chicca da tenere a mente: l'uso di un file system
crittato e' una cosa veramente efficace se utilizzata in combinazione
con la steganografia. Se avete una partizione crittata e steganografata
all'interno di un file DivX ad esempio, la sua esistenza non e' nemmeno
tecnicamente dimostrabile senza conoscere la chiave giusta.



===========
*** PGP ***
===========

La storia di Pretty Good Privacy (PGP) e' lunga e travagliata e
meriterebbe un articolo a parte. Da quando questa rivoluzionaria
applicazione ha visto la luce il mondo e' cambiato a causa sua: tante
persone lo hanno utilizzato, nuove leggi sono state emanate per
arginarne la diffusione, nuove associazioni sono nate per promuoverla,
intere comunita' di programmatori e crittologhi lo hanno sezionato,
analizzato e corretto, e chilometri di testo sono stati scritti
sull'argomento.
Per ragioni di spazio io quindi mi limitero' solo a descriverne il
funzionamento tecnico.


*** OVERVIEW ***

PGP e' un'utility di crittografia per le masse. E' stato pensato per
mettere a disposizione di qualunque utente, anche abbastanza "incolto"
in materia, la sicurezza della crittografia per la protezione della
propria privacy. Per ottenere questo fa uso di tecnologie che fino a
pochi anni fa erano in mano solo alle agenzie governative: esso impiega
algoritmi "militar-grade" in uno standard pensato per la sicurezza, la
portabilita' e la facilita' d'uso del software. E' improbabile che
persino organismi militari dispongano di tecnologia sufficente a violare
questo standard, il PGP e' stato pensato proprio per non permettere
questo. Da quando e' stato diffuso per la prima volta, e' stato passato
ai raggi X da praticamente chiunque si occupi di crittografia. Non e'
assurdo pensare che nelle ultime release delle versioni "pubbliche"
siano stati corretti praticamente TUTTI i bug. Non e' assurdo pensare
che i protocolli che usa siano in linea di massima inattaccabili. E pur
tuttavia i crittologhi hanno imparato ad essere paranoici: queste
considerazioni vanno prese con la dovuta cautela, specialmente per quel
che riguarda la dimensione delle chiavi. Io personalmente sono piuttosto
scettico sul fatto che una chiave RSA da 1024 bit sia sufficente a
garantire un adeguato livello di sicurezza oggigiorno, o che la stessa
chiave da 2048 bit sia destinata a rimanere inattaccabile per i prossimi
vent'anni, ma queste cose non dipendono dal protocollo bensi'
dall'implementazione dell'algoritmo, e comunque sono stime pressoche'
impossibili da fare a causa dell'imprevedibile sviluppo delle
tecnologie. E comunque attaccare una chiave difficilmente rappresenta il
modo piu' facile per compromettere la sicurezza di PGP.

PGP supporta crittazione dei file con crittografia simmetrica,
crittografia a chiave pubblica, firma digitale e - in molte versioni -
funzioni di wipe dei dati e di crittazione del file system. Vediamo ora
in dettaglio le operazione che il PGP svolge, io considerero' per
semplicita' il funzionamento di una versione con le seguenti specifiche:
algoritmo di hashing: MD5 128 bit, algoritmo di crittografia simmetrica:
IDEA 128 bit, algoritmo per la firma e per la crittazione asimmetrica:
RSA 2048 bit. Si capisce che il metodo e' del tutto generale e non ci
sono problemi volendo sostituire l'IDEA con il CAST o il 3DES ad
esempio, o l'RSA con l'accoppiata DH/DSS.


*** FUNZIONAMENTO DEL PGP ***

[ Generazione delle chiavi ]

Innanzitutto il PGP chiede all'utente un user ID da associare alla
chiave, che puo' essere il vero nome dell'utente, un nickname, perfino
un singolo spazio bianco " " (ma non una stringa vuota), e al quale puo'
essere associato o meno un indirizzo e-mail. Poi chiede di inserire le
specifiche della propria coppia di chiavi, cioe' la dimensione del
modulo, l'algoritmo da utilizzare, etc.

Quindi chiede di inserire la passphrase che servira' a sbloccare la
propria chiave privata. Una passphrase contrariamente ad una singola
password contiene piu' parole, magari combinate senza senso e con
caratteri di punteggiatura e alfanumerici, lettere maiuscole e minuscole
(il PGP e' case sensitive) etc. Questa passphrase dev'essere molto
complicata: il PGP effettua su di essa alcuni test preliminari, e se la
passphrase risulta troppo corta o troppo semplice vi chiede di
sceglierne un'altra; in alcune versioni c'e' una barra che indica, mano
a mano che battete, la "qualita'" della passphrase. L'hash della
passphrase rappresenta quella chiave simmetrica che il PGP usera'
per crittare la vostra chiave privata.
(Consiglio: nella passphrase evitate di usare caratteri diversi da
lettere maiuscole, minuscole, cifre e caratteri di punteggiatura non
stampabili, poiche' se vi doveste trovare a digitare la vostra
passphrase su un altro computer ad esempio, o su una tastiera diversa,
potreste avere dei problemi, tanto piu' perche' PGP non mostra sullo
schermo quello che digitate).

A questo punto il PGP chiede di battere a caso dei tasti sulla tastiera,
o di muovere a caso il mouse, per raccogliere dati random. Utilizzera'
questi dati per inizializzare un motore pseudocasuale crittografico per
la generazione delle chiavi. Appena finito di raccogliere dati vi
chiedera' di attendere mentre lui elabora: in questa fase applica tutti
gli algoritmi di generazione delle chiavi che abbiamo gia' visto, la
cosa puo' impiegare anche molto tempo, a seconda della potenza del
computer e della dimensione della chiave scelta.

Nel caso dell'RSA, il PGP genera due numeri primi random di dimensione
dimezzata rispetto al modulo scelto (es.: se ho scelto una chiave da
2048 bit, generera' due primi da 1024 bit), poi trova un esponente
pubblico ed uno privato, fa tutti i controlli del caso e vi comunica che
ha generato le vostre chiavi.

A questo punto calcola l'hash della coppia di chiavi (o meglio, di un
parametro particolare appartenente univocamente a QUELLA coppia di
chiavi e non altre, il modulo RSA stesso ad esempio): quello e' il
FINGERPRINT della chiave, ovvero una sequenza di caratteri esadecimali
che identifica in maniera pressoche' univoca la vostra chiave. Se
infatti doveste identificare con precisione un utente tramite la sua
chiave non potreste affidarvi al suo username, perche' potrebbe non
essere univoco (chissa' quanti "Pippo" ci saranno al mondo...).
D'altro canto identificarlo tramite il modulo della chiave potrebbe
essere molto scomodo: un modulo da 2048 bit sono 512 caratteri
esadecimali! Allora una funzione hash ci viene in aiuto per creare un
"distillato" della chiave allo scopo di aiutare l'identificazione -
inutile che vi dica che e' pressoche' impossibile che si verifichi una
collisione.

Poi il PGP piglia il modulo, l'esponente pubblico, lo username e il
fingerprint, ci attacca un header con tutti i dati del caso e lo
schiaffa nel pubring.pgp. Che cos'e'? Non e' altro che un "keyring", un
mazzo di chiavi cioe', ovvero un file in cui il PGP mette tutte le
chiavi pubbliche (il corrispettivo per le chiavi private e' il
"secring.pgp", ed e' salvato in un'area del disco diversa). Analogamente
piglia i due fattori primi che generano il modulo, l'esponente privato
etc. e li schiaffa nel secring.pgp, questo pero' solo DOPO averli
crittati simmetricamente (tramite IDEA ad esempio) con l'hash della
passphrase scelta (in questo modo, anche se il secring cadesse nelle
mani del nemico, egli dovrebbe comunque trovare la passphrase giusta per
ognuna delle chiavi che intende usare). Alla coppia di chiavi generata,
oltre al fingerprint, il PGP associa anche un Key ID, che sarebbe una
specie di "hash dell'hash". E' una stringa composta di pochi caratteri
esadecimali, normalmente si tratta di un solo doubleword. E' piu' corta
e permette una prima identificazione approssimativa della chiave piu'
immediata, ma naturalmente e' molto meno accurata.

A questo punto il PGP salva lo stato interno del generatore random in un
file chiamato RANDSEED (normalmente "randseed.bin" o "randseed.rnd") e
lo critta. La prossima volta che userete il PGP esso si limitera' a
raccogliere pochi dati random (come l'ora del giorno etc.), ed invece
che richiedervi di nuovo di digitare i tasti o muovere il mouse,
decrittera' il randseed e lo usera' per reinizializzare il generatore.

Le chiavi sono pronte, a questo punto il PGP vi chiede se volete
connettervi ad Internet ed inviare la vostra chiave pubblica ad un
keyserver, ovvero un servizio pubblico che contiene un database con
tutte le chiavi pubbliche degli utenti: se cercate la chiave pubblica di
"Pippo", non dovete fare altro che collegarvi ad un keyserver qualsiasi
(i keyserver "dialogano" tra di loro i modo che il database sia in
comune) e fare una ricerca per nome utente (spesso trovate piu'
risultati), oppure per key ID (uno o cmq pochi risultati), oppure per
fingerprint (un solo risultato). Spedire la propria chiave pubblica ad
un keyserver non e' necessario, ed e' a volte meglio evitarlo in alcune
circostanze, ma puo' essere utile se volete permettere che tutti possano
accedere alla vostra chiave pubblica.

E' possibile anche esportare le singole chiavi dai keyring, cioe'
salvarle su un file esterno che puo' essere in formato binario (.bin,
.pgp, .pub, .sec o altro) oppure in formato Base64 (.asc, .b64 etc...),
in quest'ultimo caso potete incollare la vostra chiave pubblica ad
esempio in un'e-mail.


[ Firma di un file ]

Prima di tutto il PGP calcola l'hash del messaggio, questo
principalmente allo scopo di garantirne l'integrita' e la
non-alterazione. Poi piglia questo message digest, ci attacca una
timestamp e tutte le amenita' del caso, e lo firma con la chiave privata
dell'utente (naturalmente questi per farlo dovra' inserire la sua
passphrase), infine aggiunge un header con tutte le informazioni
necessarie (tipo il fingerpint della chiave necessaria alla
verifica etc...). Il blocco di dati costituito da:
- header
- message digest (contiene anche timestamp + varie ed eventuali)
- message digest firmato
e' la FIRMA vera e propria del messaggio. Come tale, essa potra' essere
concatenata al messaggio (che, ricordate, resta in chiaro, noi ora
stiamo analizzando SOLO il procedimento di firma), oppure salvata su un
file a parte (.sig: "detached signature file").

Per verificarne la validita' l'utente legge il messaggio, ne calcola
l'hash e lo controlla con il message digest nella firma. Se questo non
coincide potrebbe significare che il messaggio e' stato alterato. Poi
prende l'hash firmato, lo decritta con la chiave pubblica del firmatario
e lo confronta con quello da lui calcolato. Se non coincide puo'
significare che e' stato qualcun altro a firmare il messaggio, e non il
mittente che l'utente si aspettava. Se invece tutti e tre gli hash
coincidono, allora la firma e' valida.


[ Crittazione simmetrica di un file ]

Innanzitutto il PGP vi chiede di inserire la passphrase che sara'
necessaria per la decrittazione del file. Questa NON e' la passphrase
della vostra chiave pubblica (non e' nemmeno necessario possedere una
propria coppia di chiavi per utilizzare la crittazione simmetrica),
bensi' una frase o password scelta appositamente per proteggere QUEL
messaggio. A questo punto calcola l'hash di quella passphrase: quello
sara' la chiave simmetrica da usare in crittazione.

Poi piglia il messaggio (che puo' essere gia' firmato oppure no) e ne
calcola il message digest, allo scopo di garantirne l'integrita' in fase
di decompressione. Infatti la cosa che fa subito dopo e' di prendere il
message digest, concatenarlo al messaggio stesso e quindi applicare un
algoritmo ZIP al tutto. Questo ha due scopi: innanzitutto ridurre le
dimensioni del messaggio (che, come avevamo gia' visto, una volta
crittato non sara' piu' comprimibile), inoltre inevitabilmente ne
aumenta l'entropia, cosa che sembra conferire maggiore protezione da un
certo tipo di attacchi crittanalitici.

Fatto cio', il PGP usa la chiave generata in precedenza per crittare
simmetricamente, tipo con l'IDEA, il file compresso ottenuto. Come
ultima cosa concatena l'hash della chiave di decrittazione (che in
pratica sarebbe l'hash dell'hash della passphrase, servira' per capire
al volo se la passphrase inserita per la decodifica e' giusta o no) al
messaggio crittato, insieme con un header. Eventualmente, se richiesto,
codifica tutto in Base64.

In fase di decrittazione, il PGP chiede all'utente di inserire la frase
chiave per lo sblocco. Ne calcola l'hash, quindi ne calcola l'hash
dell'hash e lo confronta con quello allegato al cyphertext. Se non
coincidono dice qualcosa del tipo "You have typed an incorrect
passphrase. Ritenta and you will be piu' fortunato"
, altrimenti usa
l'hash come chiave per decrittare il messaggio. Poi decomprime cio' che
ottiene, ne estrae il message digest originale e calcola il message
digest del plaintext decompresso. Se i due message digest non coincidono
e' probabile che ci sia stato un errore di trasmissione, o comunque il
messaggio non e' integro (warning: "PGP bad packet!", evenienza
abbastanza rara anche se a me e' capitato), altrimenti, una volta
decompresso, avete finalmente ottenuto il vostro plaintext (se il
messaggio era anche firmato, ora potete procedere con la verifica della
firma come visto sopra).


[ Crittazione asimmetrica di un file ]

Per prima cosa il PGP vi chiede di selezionare dal vostro pubring la
chiave pubblica, o le chiavi pubbliche (il PGP supporta destinatari
multipli) degli utenti a cui dovra' essere spedito il messaggio. Poi
critta il messaggio in maniera simmetrica come appena visto, solo che
invece che chiedervi una passphrase per generare una chiave IDEA da 128
bit, questa chiave la genera da solo usando il messaggio ZIPpato come
seed per inizializzare il motore random. A questo punto ottiene un
messaggio che assomiglia in tutto e per tutto ad un file crittato
simmetricamente, solo che ora piglia la chiave random usata per la
crittazione, la critta con la chiave pubblica di uno dei destinatari ed
allega quel che ottiene al messaggio. Ripete il procedimento per ognuno
dei destinatari, cioe' allega un blocco di decrittazione per ogni chiave
pubblica coinvolta, quindi aggiunge un header contenente le informazioni
sulle chiavi dei destinatari. Di nuovo, se richiesto codifica il tutto
in Base64.

L'utente che riceve il messaggio legge l'header e va a vedere se
qualcuna delle chiavi private autorizzate a decrittare il messaggio e'
in suo possesso. In caso affermativo sblocca la chiave privata con la
sua passphrase, estrae il blocco di decrittazione corrispondente e lo
decritta con tale chiave privata. Quindi controlla l'hash della chiave
simmetrica ottenuta con quello allegato al cyphertext, se e' corretto
procede come nel caso simmetrico alla decodifica.


[ Altre funzioni PGP ]

La quasi totalita' delle versioni di PGP supporta una quantita' di altre
funzioni basate principalmente sull'infrastruttura di chiavi pubbliche
creata. Il PGP infatti, contrariamente ad altre applicazioni, non fa
affidamento su una qualche autorita' esterna per garantire
l'autenticita' delle chiavi, bensi' su una "rete di fiducia". Ogni
utente, ad esempio, puo' impostare un "livello di fiducia" per una data
chiave, a seconda se e' stato lui stesso a generarla, oppure se l'ha
ricevuta di persona da un amico, o se l'ha ricevuta via internet da una
terza parte di cui puo' o meno fidarsi etc... Se siamo completamente
sicuri della provenienza di una chiave, possiamo firmarla (aggiungere
cioe' un "certificato di fiducia") con la nostra chiave. Se qualcuno che
si fida di noi riceve questa chiave firmata, allora sa che probabilmente
puo' fidarsi anche di quella chiave, etc... Inoltre possiamo impostare
una "data di scadenza" per la nostra chiave, cosi' che non sara'
possibile riutilizzarla dopo quella data, oppure emanare un "certificato
di revoca"
di una delle nostre chiavi, e moltre altre cose. Per la
descrizione dettagliata di queste operazioni vi rimando alla guida
utente del PGP.
Da notare la simpatica cosa della "sicurezza a strati" offerta dal PGP:
l'unica informazione che potreste ricavare da un file crittato e'
l'identificativo della chiave che serve a sbloccarlo: non saprete
nemmeno leggerne l'eventuale firma all'interno, o solo il TIPO di file o
la data e l'ora di crittazione senza avere la chiave adeguata. Anche la
dimensione del file originale e' difficile da stimare a causa della
compressione.


*** PGP: LE VERSIONI ***

Le varie versioni di PGP possono differire da molto a moltissimo l'una
dall'altra. In questo articolo non si pretende di dare una panoramica
completa della storia e delle varie versioni di PGP (la release 1.0
risale al 1991), che tralaltro e' incredibilmente complicata e
frammentaria, anzi, non e' affatto escluso che in questa sede ci possano
essere delle imprecisioni quindi utilizzate queste info con la dovuta
cautela.
PGP e' nato come applicazione MS-DOS e si e' evoluto poi all'ambiente
Windows, ma ovviamente ne esistono versioni anche per Linux e altre
piattaforme. La scelta della versione e' di importanza cruciale ed e'
legata direttamente alla storia del PGP. Il brevetto di questo programma
infatti e' passato di mano in mano diverse volte. Abbiamo gia' detto che
l'esportazione del PGP dagli USA era considerato in origine alla stregua
del contrabbando d'armi, il sito "commerciale" della Pretty Good Privacy
Inc. e' www.pgp.com e si riferisce appunto alle versioni pensate per gli
USA. Ma parallelamente e' nato un altro sito: www.pgpi.com (la "i" sta
per "international"), che e' dedicato alle versioni "modificate" del
PGP, cosa che e' stato possibile fare grazie a delle "falle" nella
legislazione americana e alla disponibilita' del codice sorgente. Le
versioni international sono identificate da una "i" finale (es.:
6.0.1i), di tutte e' disponibile il codice sorgente, sono legali sia in
USA che in Europa e sono non-commercial free. Per ragioni di copyright
in alcuni casi non sono compatibili con le chiavi pubbliche generate da
vecchissime versioni di PGP, ma questo non e' un problema primo perche'
quelle versioni erano usate solo in america e sono molto vecchie, ormai
non le usa piu' quasi nessuno, secondo perche' in genere esiste sempre
un'opzione (attivabile dall'utente, magari dal sorgente) che rende una
versione international compatibile con una USA. La cosa naturalmente non
e' legalissima, ma gli sviluppatori non la inseriscono di default e
mettono un bel disclaimer sull'uso - insomma, loro se ne lavano le mani
;)
Non tutto e' rose e fiori pero', e questo perche' ad un certo punto la
NAI (network Associates Inc., l'azienda proprietaria della Pretty Good
Privacy Inc. dal dicembre 1997) cambia la visione delle cose. Nel
febbraio 1991 Phil R. Zimmermann (PRZ), "padre" di PGP che era rimasto a
lavorare alla NAI come socio, lascia l'azienda emettendo tra l'altro un
comunicato sui newsgroup che spiega le sue ragioni. Questo divorzio era
annunciato: negli ultimi mesi del 2000 nuovi membri del consiglio di
management hanno assunto il controllo della PGP Inc., e la loro politica
era radicalmente diversa da quella di PRZ: si prende la decisione di
limitare il piu' possibile la pubblicazione di codice sorgente di tutte
le versioni future di PGP (all'epoca era stato pubblicato da poco il
sorgente della 6.5.8), inclusa la 7.0.3 allora in lavorazione (da notare
che meno di un anno dopo accade il piu' grave evento terroristico della
storia: forse le cose stavano per cambiare e qualcuno sapeva?...).
Dopo l'11 settembre 2001 la NAI abbandona la PGP Inc. (che torna alla
McAfee, proprietaria della NAI) e il progetto PGP viene definitivamente
chiuso, mentre continuano ad uscire le release e il codice sorgente di
GnuPG, che in questo periodo acquista popolarita'.
Nell'agosto 2002 pero' si forma una nuova societa', la PGP Corporation,
la quale riprende il progetto PGP. Il 3 dicembre 2002 viene rilasciata
la nuova versione freeware di PGP, la 8.0 (la prima perfettamente
compatibile con Windows XP e Mac OS X), completa di sorgenti: PGP
rinasce.

E qui finisce la parte storica, ora arriva la parte delle preferenze
personali, da prendere rigorosamente con le molle proprio in quanto
personali. Scegliere una versione di PGP non e' affatto cosa semplice e
dipende dalle necessita' dell'utente.

Se da un lato la corsa all'ultima release e' pericolosa (perche' non
sempre sono presenti i sorgenti e perche' e' capitato un sacco di volte
di trovare dei bug che sono stati poi corretti), d'altro canto la mania
di molti cypherpunk "old school" di continuare ad usare le vecchissime
versioni per DOS tipo la 2.0 puo' essere parimenti pericolosa. Di sicuro
e' scomoda, perche' ci sono diversi casi di incompatibilita' tra le
chiavi. Ma puo' anche essere pericolosa nel vero senso della parola,
perche' queste versioni usano algoritmi vecchi (come l'MD5 che non e' da
considerarsi una gran sicurezza rispetto allo SHA-1 o al RipeMD-160), e
inoltre sono affette da bug che sono stati corretti solo riprogettando
l'intero software nelle versioni successive (installare patch a
ripetizione in questo campo non e' una gran bella cosa...).

Sicuramente non dovete usare versioni successive alla 6.5.8 (riguardo
alla 8.0, essendo un prodotto post-11 settembre la vedo con sospetto,
consiglio diffidenza e aspettare quantomeno che i sorgenti vengano
analizzati a fondo alla ricerca di bug), quali la 7.x (PRZ affermo' che
a suo avviso la 7.0.3 e' una delle piu' sicure versioni mai uscite, ma
non ha nuove features interessanti rispetto alla 6.5.8 e non ne e'
disponibile il sorgente). Per quanto riguarda le vecchie versioni per
DOS, forse la migliore e' la 2.6.3i, che tra l'altro e' totalmente
compatibile con le chiavi RSA fino a 2048 bit. Tra le versioni per
Windows e Linux, la prima veramente degna di nota e' la 5.5.x, che
tralaltro corregge alcuni bug della 5.0, credo sia un buon compromesso
perche' tralaltro e' abbastanza "leggera". Forse la release migliore e'
la 6.5.8, ma per usarla con un po' di criterio reputo quasi
indispensabile andare ad analizzare di persona il sorgente (o la firma
PGP del pacchetto) e quindi ricompilarsela da soli, cosa non alla
portata di tutti.


*** CONSIGLI PER L'USO ***

Se la parte precedente era da prendere con le molle questa lo e' ancora
di piu'. Io non sono ne' un crittologo professionista, ne' un "esperto"
di PGP nel vero senso della parola, ma voglio illustrare qui alcune
considerazioni che spero servano almeno come spunto di riflessione.

Innanzitutto il primo consiglio e': quando installate il PGP, prima
ancora di generare una propria coppia di chiavi, andate alla voce
"opzioni" per cambiare alcune impostazioni - e non abbiate paura di
andare alla voce "advanced". Le opzioni tipiche che potete trovare sono:

- Cache decryption/signing passphrase for: <time>
Meglio disabilitare questa opzione, a meno che non facciate un uso cosi'
intensivo di PGP tale che vi troviate a decrittare un file ogni due
minuti. L'opzione consiste nel memorizzare temporaneamente in memoria la
passphrase che inserite la prima volta in modo che, se vi doveste
trovare ad averne di nuovo bisogno entro il tempo specificato, non
avrete bisogno di riimmetterla di nuovo. Naturalmente, durante il tempo
in cui la vostra passphrase e' memorizzata il sistema e' del tutto
sprotetto: chiunque puo' accedere al vostro pc puo' anche usare il PGP
al posto vostro.

- Comment block (optional): <testo>
Se lo attivate, il testo apparira' (preceduto dal campo "Comment:") in
tutti i vostri messaggi PGP. Questo puo' aiutare un eavesdropper ad
individuare i VOSTRI messaggi tra molti, quindi non e' saggio usarlo
nella creazione di nuovi messaggi. Puo' essere pero' di valido aiuto per
inserire un commento nelle vostre chiavi pubbliche, commento che
potrebbe comprendere KeyID, data di creazione etc... insomma, quante
piu' informazioni possibili sulle vostre chiavi pubbliche. Specialmente
indicare la data di creazione e la dimensione dei moduli usati puo'
aiutare a proteggere da alcuni attacchi estremamente avanzati che
potrebbero permettere di "falsificare" il fingerprint o il KeyID di una
chiave, nei quali attacchi pero' si perde il controllo sulle dimensioni
della chiave fasulla generata.

- Faster key generation
Visto che nel DH e nel DSA i parametri di dominio possono essere in
comune ad un gruppo di utenti, attivando questa opzione i vostri numeri
primi non verranno generati al momento della creazione di una chiave,
bensi' pescati a caso in un database di numeri "di default" incluso nel
PGP (questa opzione pero' funziona solo se scegliete una dimensione
della chiave "standard", e quindi in genere 768, 1024, 2048 o 3072 bit),
questo naturalmente non vale nel caso che stiate generando una chiave
RSA. Attivando questa opzione guadagnate moltissimo in velocita' (in
computer vecchi potrebbe essere l'unica soluzione per poter generare una
chiave abbastanza grossa) e non perdete praticamente nulla in sicurezza.
Ciononostante, dato che le chiavi si generano una volta sola, se potete
permettervi un po' di tempo in piu' per fare le cose per bene sarebbe
meglio disabilitare questa opzione.

- Number of wipe loops: <numero>
Questo e' il numero di volte che i file verranno sovrascritti quando
cancellati con l'opzione "wipe". Di solito il valore di default e' 3 (la
sicurezza aumenta fino a poco meno di 30 loop), io vi consiglio di
alzare un po' questo valore.

- Lista dei keyserver
Se non avete intenzione di avvalervi dei keyserver, cancellate o
quantomeno disabilitate tutti quelli presenti in questa lista.

- Enabled algorithms
Se ne deselezionate uno di quelli listati, esso non potra' essere
utilizzato. A meno che tra quelli listati non troviate anche il DES,
lasciate tutto com'e'.

- Preferred algorithm
Qui e' questione di preferenze. L'algoritmo preferito e' quello che
verra' poi usato nella vostra chiave o nella crittazione simmetrica.
Alcuni dicono che il piu' sicuro sia il 3DES, anche se la cosa non mi
trova completamente d'accordo (e cmq e' molto lento). Sia l'IDEA che il
CAST sono molto buoni, probabilmente il migliore e' il CAST perche' e'
piu' veloce e non ha subito nemmeno attacchi parziali (contrariamente
all'IDEA, che pero' continua ad essere inattaccabile), pero' l'IDEA si
basa su concetti matematici piu' "profondi" e quindi e' forse piu'
improbabile che esista un modo di crittanalizzarlo. Io sceglierei il
CAST, ma qui e' davvero questione solo di preferenze.

- Warn when encrypting to keys with an ADK
Lasciate selezionata questa opzione. ADK significa "Additional
Decryption Key"
, e riguarda uno dei piu' grossi bug del PGP mai
scoperti. E' abbastanza improbabile che vi imbattiate in una chiave con
un'ADK, nel caso vi apparisse il citato messaggio di warning sappiate
che c'e' il rischio che la chiave sia contraffatta o studiata per
rendere possibile la forgiatura di firme fasulle.

E adesso passiamo al dilemma piu' grande che affligge tutti gli utenti
PGP: Diffie-Hellmann o RSA?
Quasi tutti i forum o newsgroup dedicati al PGP sono pieni di discorsi
su questo argomento. In realta' c'e' poco da dire: abbiamo gia' visto
che, a parita' di modulo, l'accoppiata DH/DSS offre alcuni vantaggi
rispetto all'RSA. Il problema e', in entrambi i casi, relativo alla
firma piu' che alla crittazione, nel caso DH/DSS a causa della
dimensione del modulo DSS limitato a 1024 bit, e nel caso dell'RSA a
causa dell'uso della funzione hash MD5 invece che SHA. Probabilmente le
chiavi RSA sono piu' sicure rispetto alla firma, e quelle
Diffie-Hellmann piu' sicure rispetto alla crittazione vera e propria.
L'alternativa migliore forse (anche se piuttosto estrema pero')
e' quella di avere DUE coppie di chiavi, una RSA (da usare solo per la
firma) ed una DH/DSS (da usare solo per farsi inviare messaggi
crittati). In questo modo tralaltro se si dovesse scoprire che uno dei
due sistemi presenta dei "buchi" si ha sempre la seconda chiave.


<nota a proposito dell'MD5>

Riguardo all'MD5 si sente dire spesso a riguardo che e' una merda
perche' la dimensione dell'hash e' troppo piccola - soli 128 bit. A
differenza dei normali algoritmi di crittografia simmetrici infatti, una
funzione hash deve avere una lunghezza del checksum DOPPIA rispetto alla
lunghezza di una chiave considerata "sicura", perche' la complessita'
computazionale per trovare una collisione in una funzione hash e'
pari a O(2^(n/2)), e non O(2^n) come la complessita' di un attacco
brute-forcing contro una chiave random (dove "n" e' la dimensione del
problema). E quindi, mentre una chiave IDEA da 128 bit puo' essere
considerata sicura, un message digest da 128 bit non lo e', perche'
trovare una collisione richiede O(2^64) operazioni, cioe' quanto un
brute-forcing contro una chiave da 64 bit - relativamente facile.
Ora, tecnicamente parlando questa e' una cazzata.
Infatti la stima della complessita' di O(2^(n/2)) per far collidere
una funzione hash si riferisce al caso piu' "roseo", ovvero
che il nemico non abbia un hash prestabilito a priori da imitare, e
quindi si limiti a creare messaggi random finche' non ne trova due
con lo stesso hash (questo e' il "birthday attack" vero e proprio).
Se invece (come accade di solito) il nemico ha gia' un
messaggio da falsificare, egli deve generare messaggi a randa finche'
non ne trova uno che abbia QUEL PRECISO VALORE HASH, cosa che come
avevamo gia' visto al cap.8 fa letteralmente crollare le possibilita' di
successo, e la complessita' computazionale asintotica del problema
diventa O(2^n).
Cio' comunque non toglie che ormai l'MD5 non rappresenta piu' quella
sicurezza assoluta cercata, e che i tempi richiedono urgentemente nuove
funzioni hashing con checksum di 256 bit o superiore.

</nota a proposito dell'MD5>


Altro punto da discutere: la dimensione della chiave. Per il modulo DSS
non c'e' nulla da fare: sono 1024 bit punto e basta. Per l'RSA si arriva
in genere a 2048, anche se le ultime versioni di PGP arrivano fino a
4096 come per il DH. Purtroppo fattorizzare grandi numeri o risolvere
logaritmi discreti sta diventando piu' facile ogni giorno che passa.
Peggio ancora, sta diventando piu' facile in maniera piu' veloce di quel
che ci si aspettasse. Per questo motivo il mio consiglio e' di essere
conservativi, e di scegliere sempre, in linea di massima, la dimensione
della chiave piu' grande che il programma in uso possa offrire.
Con delle eccezioni pero'. Infatti ho sentito dire di versioni
"modificate" del PGP, cioe' sorgenti ricompilati da qualche
appassionato, che offrono un supporto per una dimensione "maggiorata"
delle chiavi. A parte il fatto che incrementare SOLO la dimensione del
modulo non contribuisce in maniera significante all'aumento di sicurezza
(secondo Bruce Schneier: "la sicurezza e' una catena, e la sua
resistenza e' data da quella dell'anello piu' debole"
), a parte la
lentezza esasperante delle operazioni da effettuare su un modulo da
16384 bit, c'e' da tenere presente la questione che altre versioni del
PGP non riconoscono queste nuove chiavi. E' del tutto inutile avere una
chiave sicurissima se poi non la possiamo usare con nessun altro. In
ogni caso sconsiglio vivamente di superare i 4096 bit per una DH e i
2048 per una RSA.

Altra cosa di cui spesso non si tiene conto e' la possibilita' offerta
da PGP di usare la crittografia tradizionale (cioe' simmetrica) per
crittare i file. Vi ricordo che si sente spesso dire in giro che "la
crittografia a chiave pubblica e' molto piu' sicura di quella a chiave
singola"
, cosa che normalmente viene interpretata come: "algoritmi quali
RSA sono molto piu' sicuri di algoritmi tipo IDEA"
, cosa che, vi ricordo
anche questo, e' come dire "un sistema d'allarme a infrarossi e' piu'
sicuro di un carro corazzato"
, oppure anche "morto Cristo, spenti i
lumi, ma morto un Papa se ne fa un altro"
...
La possibilita' di crittare singoli file con algoritmi quali IDEA, CAST
e similari e' forse la piu' sicura delle features offerte da PGP. Se
tutto cio' di cui avete bisogno e' di proteggere dati personali sul
vostro computer che non volete siano letti da altri, NON USATE LA
CRITTAZIONE ASIMMETRICA! Sarebbe veramente sciocco. Un file crittato con
RSA ad esempio, ha senso solo per uno scambio di dati tra due
interlocutori che non condividono informazioni segrete a priori, cosa
che nel cyberspazio si verifica spesso, ma tenete conto che attaccare un
protocollo a chiave pubblica e' in genere estremamente piu' facile che
attaccare un protocollo basato su chiave segreta. Primo perche' un
crittosistema simmetrico e' (o almeno dovrebbe essere) suscettibile solo
a brute forcing, mentre un crittosistema asimmetrico e' suscettibile,
oltre che al brute forcing, anche ad attacchi crittanalitici basati sia
sul protocollo di implementazione che sul problema matematico di base.
Secondo, perche' nella quasi totalita' dei casi e' impensabile
appoggiarsi esclusivamente ad un crittosistema asimmetrico, e quindi
(come nel PGP) si usano sistemi ibridi che uniscono le debolezze dei
sistemi asimmetrici e di quelli simmetrici a quelle del protocollo di
implementazione: basta attaccare con successo l'anello piu' debole per
compromettere tutto il sistema.
Nel caso del PGP ad esempio, se il nemico intercettasse un messaggio
crittato con una chiave pubblica RSA e volesse decifrarlo avrebbe due
alternative: rompere la chiave RSA (es.: fattorizzando il modulo),
oppure tentare di ricavarsi la chiave IDEA (o affine). Anche se ci fosse
un modulo di 2048 bit, per il nemico sarebbe piu' conveniente di diversi
ordini di grandezza attaccare la chiave RSA con il NFS o simili
piuttosto che tentare un brute-forcing contro una chiave random da 128
bit! E quindi se utilizzaste SOLO una crittazione IDEA, oltre a rendervi
le cose molto piu' facili togliereste al nemico la SCELTA.
Insomma: ogni volta che e' possibile ricorrete alla crittazione
simmetrica piuttosto che a quella a chiave pubblica.

Altro aspetto troppo sottovalutato: la passphrase. Che cos'ha di diverso
una passphrase da una password?
Nell'esempio di prima, il nemico oltre a poter scegliere tra attaccare
il modulo RSA o la session-key IDEA avrebbe un'altra opzione: trafugare
dal vostro computer la vostra chiave segreta e scoprire la passphrase
che serve per sbloccarla.
Ora, questo e' probabilmente l'attacco di gran lunga meno dispendioso in
termini di risorse e di onere computazionale, e quindi proprio quello
che il nemico sceglierebbe.
Trafugare un file da un pc capite da soli che non e' impossibile. Anche
se il vostro computer fosse blindato dentro un edificio protetto dai
migliori sistemi d'allarme sul mercato, sarebbe meno costoso ingaggiare
una banda di ladri professionisti e relative attrezzature piuttosto che
ingaggiare un team di matematici coi controcoglioni e fornirli di una
distesa di Cray-X1 grande come un campo da calcio in stile NSA (per non
parlare della costruzione delle relative centrali elettriche da
centinaia di Megawatt che servirebbero per alimentarli e dei sistemi di
raffreddamento ad azoto liquido). Nella maggior parte dei casi un topo
d'appartamento o un hacker da quattro soldi sarebbero piu' che
sufficienti a procurarvi il secring.pgp. E quindi la meta' del lavoro e'
fatto.
La seconda meta', quella piu' difficile (ammesso che non facciate
cazzate tipo appendere il classico foglietto sul monitor), consiste
nell'indovinare la passphrase per lo sblocco. Se questa fosse una parola
esistente, lunga anche una dozzina di caratteri, la cosa migliore e' il
vocabulary attack. Il nemico ha un dizionario elettronico che comprende
tutte le parole di tutte le lingue conosciute, slang e dialetti inclusi.
Tentarle tutte impegherebbe comunque un tempo relativamente irrisorio,
ammesso che il nemico abbia accesso a una buona dotazione tecnologica
(la rete di un'universita' e' sufficiente). Si possono provare poi le
parole scritte al rovescio, le date di nascita, combinazioni di tutte
queste cose etc. L'onere computazionale aumenta in maniera poco
consistente ai fini crittografici. Insomma una singola password non
servirebbe a nulla.
L'idea della passphrase e' che se la password e' costituita da PIU' di
una singola parola, se queste parole sono arrangiate sensa senso
apparente e se ciascuna di queste parole ha la complessita' di una
normale password di alto livello (e quindi comprende numeri, caratteri
di punteggiatura, maiuscole e minuscole), allora l'onere computazionale
diventa "non ragionevole", ed attaccare la passphrase via vocabulary
attack e' poco piu' facile che attaccarla per brute forcing.
Ideare una passphrase sicura e allo stesso tempo facilmente
memorizzabile e' la piu' grande sfida che viene richiesta all'utente PGP
ma e' molto importante. E' del tutto inutile avere la quasi certezza
matematica dell'inviolabilita' del codice se poi per sbloccare la chiave
privata basta qualcosa come "Apriti sesamo". A proposito del PGP un
consiglio: non fidatevi della progressbar "passphrase quality": e' molto
poco *paranoica*. Una passphrase che non riempia completamente la
progressbar non e' nemmeno da prendersi in considerazione, ma e' molto
meglio inventarla ben piu' complicata. Prendetevi tutto il tempo che vi
occorre per scegliere una buona passphrase. I crittanalisti dell'NSA NON
ve ne saranno grati.

Infine quattro parole a proposito delle versioni "modificate" di PGP.
Non sempre sono da disdegnare - dopotutto i sorgenti vengono distribuiti
anche per questo - pero' e' sempre bene essere diffidenti. In ogni caso
deve SEMPRE essere presente il sorgente modificato, e devono essere
indicate chiaramente le modifiche fatte. Il programma modificato
dovrebbe essere postato sui relativi newsgroup per essere analizzato
dagli esperti. Comunque ripeto che non vale assolutamente la pena
ricompilare un codice solo per generare chiavi piu' grandi, ma puo'
servire ad esempio per disabilitare i vari "legal kludge", cioe' quelle
modifiche che vengono fatte al programma per renderlo incompatibile con
versioni antecedenti di cui era vietata l'esportazione, oppure per
attivare features addizionali che magari non sarebbero presenti nella
versione pubblica pre-compilata, o magari per attivare il supporto per
nuovi algoritmi futuri (tipo curve ellittiche) o per rendere
riconoscibili dal programma le chiavi "maggiorate". In ogni caso in
linea di massima usate un PGP standard, e la ricompilazione dei sorgenti
e' consigliata solo ad un pubblico adulto...



============================================
*** LIBRERIE ARITMETICHE MULTIPRECISIONE ***
============================================

Alcuni tra di voi che si dilettino in programmazione non avranno potuto
fare a meno di notare una cosa: e' dall'inizio dell'articolo che si
continua a parlare di numeri interi da migliaia di bit. Cosa significa
di preciso? Programmatori assembly principianti dovrebbero infatti
sapere che una CPU puo' effettuare calcoli solo su dati che siano
contenuti nei suoi registri, non direttamente in memoria, e un registro
di CPU normalmente si parla di 32 bit o giu' di li'. E' anche vero che
tipi di dato quali il long double (mi riferisco a C & related) arrivano
fino a 80 bit, ma in questi casi parliamo di dati in virgola mobile (e
noi, vi ricordo, lavoriamo con gli interi), molto spesso le istruzioni
floating-point non hanno bisogno di essere emulate via software perche'
vengono implementate nello stesso hardware del processore. E se allora,
diciamo, volessi realizzare un programma di crittografia in C o
addirittura VB, come potrei maneggiare numeri da 1024 bit e oltre?
La risposta e': utilizzando una libreria aritmetica multiprecisione. Si
tratta di un insieme di routine e funzioni (normalmente contenute in un
modulo di programma) le quali effettuano delle operazioni, a partire da
quelle basilari come l'addizione fino alle esponenziazioni modulari, su
dei tipi di dato "costruiti" che rappresentano numeri interi in
multiprecisione. Un intero in multiprecisione e' sostanzialmente un tipo
di dato definito dall'utente che permette di rappresentare numeri interi
appunto di qualsiasi dimensione si voglia. Visto che questi interi sono
normalmente troppo grandi per entrare in un singolo registro, essi
vengono immagazzinati in memoria sotto forma di array di elementi-base,
i quali elementi-base hanno in genere dimensione sufficiente a poter
entrare in un registro di CPU. E cosi' ad esempio, un numero da 1024 bit
puo' venire rappresentato in memoria come un array di 64 word di 16 bit
l'una ovvero qualcosa come:

unsigned short int NUMERO[63]

dove NUMERO[0] rappresenta i 16 bit low-end del numero che vogliamo
rappresentare, NUMERO[1] i bit dal 16 al 31, NUMERO[2] i bit dal 32 al
47, e cosi' via. La libreria aritmetica multiprecisione si comporta un
po' come un dispositivo hardware "virtuale" (una CPU software invece che
in silicio), effettuando i calcoli DIRETTAMENTE su dati che risiedono in
memoria. Per farlo, essa si comporta da "tramite" tra i dati astratti e
la vera CPU, impiegando algoritmi che "spezzettano" l'array e operano
sui singoli elementi-base, ognuno dei quali viene trasportato in un
registro, viene elaborato e quindi riportato in memoria, poi si prende
l'elemento successivo, si elabora e viene riportato in memoria etc...

E' un po' come quando alle elementari si insegna ai bambini a far di
conto utilizzando carta, penna e operazioni "in colonna". Il cervello in
questo caso si comporta come una ipotetica CPU i cui registri possono
immagazzinare solo numeri da 0 a 9. Numeri piu' grandi di 9 vengono
rappresentati "in multiprecisione" sul foglio di carta (che sarebbe la
RAM) come stringhe di cifre decimali arrangiate secondo un sistema
posizionale (ovvero vengono rappresentati come "array" di cifre), spetta
poi al metodo di operazione in colonna (cioe' l'algoritmo) far si' che
si possa operare su questi dati prendendo in considerazione un solo
simbolo per volta. Ad esempio, ecco una elementare procedura in C che
effettua l'addizione di due numeri da 1024 bit, rappresentati come array
da 64 interi positivi di 16 bit l'uno, e restituisce un puntatore
all'array somma. Se si verifica overflow la funzione restituisce NULL:

unsigned short int *addizione
(unsigned short int *a, unsigned short int *b) {
register long int i;
register unsigned short int riporto;
unsigned short int *ptr;
/* alloca memoria */
ptr = (unsigned short int *)
malloc(64 * sizeof(unsigned short int));
if (!ptr) return 0;
/* algoritmo */
riporto = 0;
for (i = 0; i < 64; i++) {
ptr[i] = a[i] + b[i] + riporto;
if (ptr[i] < a[i]) riporto = 1;
else riporto = 0;
};
if (riporto) return 0;
else return ptr;
}

Ragionando un attimo su quel che potrebbe essere la divisione ad
esempio, capite da soli che effettuare un calcolo di qualsiasi tipo su
dei dati disposti in questo modo e' piuttosto masochistico, e in effetti
la realizzazione di una libreria aritmetica multiprecisione non e' cosa
da poco. Innanzitutto bisogna trovare un modo ottimale per rappresentare
gli interi in multiprecisione, ad esempio invece di rappresentarli
direttamente tramite array si potrebbero rappresentare come delle
strutture dati, composte da un puntatore all'array vero e proprio e da
un valore che indichi il numero di elementi di memoria allocati
nell'array (in questo modo si possono gestire degli array di dimensione
variabile sfruttando l'allocazione dinamica della memoria), esempio:

typedef struct {
long int index;
unsigned short int *block;
} BIG_INTEGER;

Lo svantaggio di questo metodo e' che ogni operazione che cambi la
dimensione del BIG_INTEGER richiede un'allocazione costante di memoria,
cosa che rallenta molto il codice (requisito fondamentale di una
libreria di questo tipo e' la velocita', gia' di per se' limitata
intrinsecamente dalla dimensione degli operandi). Un altro sistema
(usato in librerie professionali quali la GnuMP) e' quello di usare un
sovrappiu' di memoria come "buffer" per ogni array, in modo che quando
ci sia bisogno di aumentare la dimensione del BIG_INTEGER si possa prima
sfruttare questo buffer, e solo se questo non sia sufficente si

  
ricorre
ad una nuova allocazione, esempio:

typedef struct {
long int index;
long int allocated;
unsigned short int *block;
} BIG_INTEGER;

Il parametro "allocated" indica la quantita' di memoria effettivamente
allocata all'indirizzo "block", mentre "index" indica quanto, di quello
spazio allocato, viene effettivamente impiegato al momento per
rappresentare i dati (naturalmente si avra' sempre index <= allocated).
Nel momento che la dimensione del BIG_INTEGER passi da 32 a 19 blocchi,
ad esempio, la memoria non verra' riallocata, ma verra' solo alterato il
valore di index da 32 a 19. Se allocated fosse uguale a 40 e si passasse
poi da 19 a 43 blocchi, prima verrebbe effettuata una riallocazione di
43 (o piu') blocchi di memoria all'indirizzo block, poi verrebbe
modificato il valore allocated a indicare questo nuovo spazio, quindi si
modificherebbe index col valore 43 esatto. Questo metodo spreca spazio
in memoria ma aumenta la velocita' del codice perche' riduce il numeri
di allocazioni (grosso collo di bottiglia), spettera' al programmatore
scegliere uno dei due metodi o un compromesso tra i due a seconda del
prodotto finale che intende realizzare.

Altro aspetto da considerare e' la dimensione del singolo elemento-base
dell'array. Io nell'esempio ho usato uno short int, ma visto che la
maggior parte dei processori moderni hanno registri a 32 bit sarebbe
stato meglio usare un long int, per due motivi. Il primo e' che cosi'
facendo si fanno in genere la meta' dei cicli che servirebbero per
scorrere tutti gli elementi di un array, e visto che in questi
processori le operazioni tra interi a 32 bit hanno in genere la stessa
velocita' in termini di cicli di clock delle operazioni a 16 bit si ha
effettivamente quasi un raddoppio della velocita'. Il secondo motivo e'
che in molti processori (i Pentium ad esempio), un address bus a 32 bit
implica che l'accesso alla memoria e' piu' rapido se i dati sono
arrangiati a celle di 32 bit, o comunque si trovano ad un indirizzo di
memoria multiplo di 4. Nel caso di un array di elementi a 16 bit invece
che 32, nella meta' dei casi per accedere ad un singolo elemento
servirebbero infatti alcuni cicli di clock in piu', perdendo qualcosa in
velocita' (questo accade perche' un elemento su due dell'array si trova
ad un indirizzo multiplo di due ma non di 4, se l'indirizzo fosse
dispari sarebbe ancora peggio in termini di prestazioni).
Il fatto di usare valori a 16 bit pero' ha un vantaggio "organizzativo",
soprattutto nel caso di una moltiplicazione. Infatti moltiplicare due
valori a 16 bit nel caso di un processore a 32 bit risulta abbastanza
efficiente, perche' si puo' immagazzinare il risultato temporaneamente
in un singolo registro, ma se i valori fossero entrambi di 32 bit
servirebbero dei "trucchi" per poter maneggiare il risultato che
sarebbe di 64 bit stavolta), trucchi che rendono piu' laboriosa la
scrittura del codice. In generale comunque, soprattutto se si programma
in assembly, e' meglio usare delle fullword invece che word di
dimensione dimezzata rispetto alla lunghezza dei registri, tanto piu'
che in molti processori moderni esistono delle funzioni di
moltiplicazione specializzate per gestire risultati a 64 bit sfruttando
gli altri registri.

Un'altra cosa da tenere in considerazione (la piu' importante) riguarda
gli algoritmi che vengono impiegati per effettuare i calcoli,
specialmente per quel che riguarda operazioni a basso livello come
moltiplicazione e divisione (che sono dei veri e propri colli di
bottiglia per tutte le operazioni piu' complicate). Infatti i classici
algoritmi "da scuola elementare" (di origine araba) hanno due grossi
difetti: primo, lavorano nativamente in base dieci, secondo, hanno
un'efficienza asintotica relativamente bassa.
Cio' significa che per numeri fino ad una certa dimensione potete
usarli tranquillamente, ma non sarebbe efficiente usarli per numeri
di (la sparo cosi' a caso...) 10000 bit ad esempio. Per numeri molto
grandi esistono altri algoritmi (Karatsuba, Montgomery, Toom Cook,
moltiplicazione FFT, etc...), il cui studio e' uno degli argomenti
principali della Teoria della Complessita'. In generale per poter
scrivere una libreria aritmetica multiprecisione DAVVERO efficiente
bisogna avere l'equivalente di una laurea in algebra computazionale...

Tra gli allegati (che dovreste finalmente trovare in questo numero di
OQ) ho deciso di non mettere funzioni_matematiche.bas, poiche' la sua
utilita' pratica sarebbe stata pressoche' nulla e la sua utilita'
didattica limitata. Ho invece deciso di includere un vero gioiellino
(eheheh), che possa risultare sia utile che didattico: un programma di
calcolatrice che usa una libreria multiprecisone scritta da me... in VB!

La scelta puo' apparire bizzarra, me ne rendo conto, ma i motivi ci
sono. Il primo e' che io ho cominciato a programmare dal Basic del
Commodore, e che sono poi "evoluto" (prima di passare ad altra roba) al
Visual Basic, e questa libreria (VBwinLAMP.bas) l'ho scritta parecchio
tempo addietro, all'epoca non me la sarei mai sentita di programmare una
libreria in qualcos'altro. Il secondo e' che il VB e' un ottimo
linguaggio dal punto di vista didattico, anche se e' pessimo per molti
altri aspetti, e credo che un sorgente VB sia accessibile ai piu' - se
questo articolo non fosse stato rivolto ai newbie sarebbe stato molto
piu' corto, perche' mi sarei potuto permettere di saltare la maggior
parte della roba. Il terzo motivo e' che, mentre troverete facilmente
librerie piu' o meno buone in C, quasi nessuno si e' sognato di farne
una in VB, e quindi faccio una cosa alternativa! >B-)
Scherzi a parte, non aspettatevi granche', anzi... La libreria e'
funzionale, ma e' vecchia e non e' stata ricorretta. E' piena di bug, e
di sicuro puo' essere molto meglio ottimizzata e velocizzata, non usa
nessun algoritmo asintoticamente avanzato ed e' incredibilmente lenta
(TROPPO lenta, anche trattandosi di VB). Una cosa ESTREMAMENTE
elementare insomma. Ma e' pur sempre una libreria che chiunque programmi
in VB potra' integrare nei propri programmi, meglio di niente... Il
programma-calcolatrice in questione si chiama CAMP, per le specifiche
del progetto e della libreria vi rimando comunque al readme allegato.



===============================================
*** POSTA ELETTRONICA E ANONYMOUS REMAILERS ***
===============================================

Questo argomento e' piuttosto vasto, per una lettura piu' approfondita
vi rimando allo splendido "Kryptonite", ed.Nautilus, io qui daro' solo
una breve panoramica.

Per "anonymous remailer" si intende in genere una vasta categoria di
servizi web dalle funzionalita' simili ma non uguali. Per fare un
paragone potremmo dire che sono l'analogo dei proxy server rispetto alla
posta elettronica: un proxy in genere "maschera" la connessione di un
utente, frapponendosi come "scudo" tra l'utente ed il suo obiettivo e
rendendone, ad esempio, piu' difficoltoso il tracciamento. Analogamente
un anonymous remailer si frappone fra il mittente di un'e-mail e il suo
destinatario creando un "bounce" del messaggio in maniera tale che, ad
esempio, l'IP e l'indirizzo e-mail del mittente venga eliminato.

Ci sono diversi tipi di remailer con funzionalita' diverse, ma tutti in
genere hanno delle caratteristiche che li accomunano.

Innanzitutto, la cosa piu' importante e' che i remailer sono di solito
"amichevoli", mi spiego meglio: contrariamente ai proxy, i quali sono
nati in principio per offrire un servizio addizionale solo a determinati
utenti (che magari pagano per avere quel servizio), gli anonymous
remailer sono nati direttamente dall'etica cypherpunk. Essi cioe' sono
gestiti da gruppi "crittoanarchici", ed offrono servizio gratuito a
chiunque ne abbia bisogno. Queste organizzazioni sono dotate di un
proprio codice etico che mira innanzitutto a salvaguardare la privacy e
la liberta' d'espressione dell'utente, quindi non collaborano volentieri
con le forze dell'ordine, mantengono segreti e crittati i log (la
maggior parte non tiene affatto log) e stanziano fondi per l'assistenza
legale di gestori di altri remailer che abbiano grane con i governi.
Inoltre collaborano assiduamente, anche a livello accademico, per
migliorare il servizio dal punto di vista della sicurezza e
dell'affidabilita', usano solo prodotti di cui sia disponibile il
sorgente, spesso addirittura open source (tipo GnuPG) e promuovono
campagne per la salvaguardia dei diritti digitali. Proprio per questo
sono piu' convenienti da usare e soprattutto piu' sicuri di un servizio
a pagamento.

Inutile dire che coi tempi che corrono molti di questi remailer sono
assiduamente tenuti sotto controllo dalle forze governative. Sicuramente
una certa percentuale di questi e' anche insicura, cioe' attivamente
monitorata da gente tipo FBI, NSA etc. Stimare tale percentuale e'
pressoche' impossibile, ma probabilmente tale percentuale varia da paese
a paese. In alcuni stati USA tutti i servitori di servizi web sono
obbligati a tenere dei log delle connessioni e a consegnarle quando
richiesto alla polizia. Sempre in USA, i dati personali di un cittadino
non appartengono al cittadino stesso bensi' all'azienda che li
raccoglie, la quale e' libera di creare database contenenti abitudini e
informazioni di qualsiasi genere nonche' di vendere tali informazioni a
chicchesia, e il tutto senza interpellare il cittadino o le forze
dell'ordine.
In Europa la situazione e' un po' migliore, tanto che la normativa sulle
liberta' individuali e' stata in passato causa di diversi attriti tra i
governi europei e USA, il quale da sempre mira ad "americanizzare" il
sistema europeo, spingendo tra l'altro per la compravendita di
informazioni sui consumatori tra aziende USA ed europee. Dopo l'11
settembre le cose in America sono di molto peggiorate (vedi Information
Act e Carnivores), ed anche in Europa sono destinate a peggiorare,
soprattutto a causa della presenza di governi-fantoccio manovrati piu' o
meno direttamente dagli americani (come quelli italiano, spagnolo e
inglese ad esempio).

Ma torniamo a noi. Proprio a causa di questi massicci sistemi di
controllo delle masse, gli anonymous remailer hanno dovuto escogitare
dei trucchi per sopravvivere in maniera efficace. In particolare, da
sempre la filosofia cypherpunk e la crittografia vanno a braccetto...

<lettore> Evvai! Arriva la cavalleria!

Si infatti. Ma l'avere dei buoni algoritmi di crittografia non basta da
solo a risolvere i problemi da cui un sistema complesso come la rete dei
remailer puo' essere affetto. Serve un'infrastruttura ben studiata, con
protocolli sicuri e adattabili alle varie situazioni. L'evoluzione delle
tecnologie crittografiche ha portato di pari passo all'evoluzione dei
remailer per far fronte a nuovi modelli di minaccia. Cerchiamo di
chiarire il concetto dando uno sguardo alle principali categorie di
remailer.


[ Anonymous Remailer type 0: PSEUDONYM ]

Questi sono stati il primo tentativo di anonimato postale. Non erano
affatto sicuri, infatti venivano piu' propriamente chiamati
"pseudoanonimi", ormai non ne esistono quasi piu' e hanno interesse per
lo piu' storico. La piu' grande debolezza di questi sistemi era
l'esistenza sul server di un database che conteneva coppie <nome utente
reale - account anonimo>, al quale era possibile in linea di massima
avere accesso. In pratica questi server non facevano altro che
"strippare" (cioe' togliere) l'intestazione dei messaggi del mittente
originale e sostituirla con una fasulla. Dopo una storica battaglia
legale che coinvolse il remailer anon.penet.fi e la potente setta
californiana nota come "Chiesa di Scientology", battaglia che alla fine
vide costretto il gestore di penet.fi a rivelare il vero indirizzo di un
utente colpevole di aver pubblicato in maniera anonima dei testi
considerati "sacri" dagli adepti scientologhi, questo e altri remailer
dello stesso tipo capirono che era il momento di chiudere i battenti e
di inventarsi qualcosa di molto piu' drastico...


[ Anonymous Remailer type 1: CYPHERPUNK ]

Questi remailer sono tra i piu' diffusi in rete, perche' se usati
correttamente con crittazione a cascata PGP e concatenazione offrono una
sicurezza elevatissima. In questo caso non esiste nessun database di
account. Ogni remailer e' dotato di una propria chiave pubblica, con la
quale decritta i messaggi che riceve e li inoltra all'indirizzo che
appare nell'intestazione del messaggio decrittato. Il bello e' che il
messaggio decrittato potrebbe anche essere un nuovo messaggio crittato,
con la chiave di un altro remailer, e destinato a quel remailer. Una
crittazione "a strati" insomma. Ad ogni passaggio della catena di
remailer, ciascuno toglie uno ed un solo strato di crittazione, e
inoltra il messaggio al prossimo remailer della catena. Ciascun remailer
puo cosi' conoscere solo l'indirizzo di provenienza e quello di
destinazione SOLO del singolo "strato" che gli compete. Se vengono usati
piu' remailer, basta che anche solo uno di questi sia "sicuro" per far
perdere irrimediabilmente le proprie traccie. Questo sistema e' davvero
sicuro e garantisce la non-rintracciabilita' del mittente a partire dal
messaggio finale ricevuto. Ma ha due grossi svantaggi. Il primo e'
l'impossibilita' di rispondere al messaggio che arriva per via anonima,
svantaggio questo a cui si puo' ovviare tramite l'allegamento al
messaggio finale di un REPLY-BLOCK, cioe' un'intestazione crittata PGP
che, se appesa in testa a un qualsiasi messaggio e inviata al remailer
indicato nel reply-block stesso, consente al messaggio di giungere al
proprietario del reply-block passando automaticamente attraverso una
catena di remailer da lui scelta. Il secondo svantaggio e' che, se e'
vero che il mittente non e' rintracciabile a partire dal messaggio
finale, alcuni attacchi possono individuare l'intero percorso del
messaggio - e quindi tracciare il mittente - mediante l'analisi del
traffico. Il sistema di proteggersi da questi attacchi c'e', ma richiede
all'utente una certa esperienza, motivo per cui sono stati inventati i
remailer di tipo mixmaster.


[ Anonymous Remailer type 2: MIXMASTER ]

Questi rappresentano lo stato dell'arte nell'anonimato elettronico. Un
remailer mixmaster e' sostanzialmente un cypherpunk modificato per far
fronte a qualsiasi modello di minaccia. La cosa si ottiene
sostanzialemnte adottando delle contromisure che rendono impossibile
l'analisi del traffico tra i remailer, misure quali: la segmentazione e
reordering automatico dei pacchetti, il tempo di latenza random su ogni
remailer (se non impostato a mano dall'utente), la crittazione
addizionale classica automatica per rendere irriconoscibili pacchetti in
chiaro che transitano su ogni remailer, un pool di pacchetti in latenza
mantenuto sempre parzialmente pieno per impedire attacchi DoS (Denial of
Service), un database con gli hash di tutti i pacchetti transitati nelle
ultime tot ore per impedire attacchi flood, la generazione automatica di
un traffico "fasullo" di pacchetti costante tra tutti i remailer (in
modo da nascondere le vere comunicazioni tra il rumore di fondo della
"corrente" di pacchetti), nonche' la perfetta sincronizzazione di tutti
i mixmaster della Rete. Lo svantaggio piu' grosso di questi remailer in
principio era che per usarli occorreva scaricare per ciascuno un client
di posta apposito, ma oggi questa difficolta' e' stata superata grazie
alla perfetta integrazione tra cypherpunk e mixmaster. In pratica ogni
mixmaster si comporta anche come un cypherpunk: basta inviare un normale
messaggio formattato stile cypherpunk ad un remailer, e se questo
supporta anche il formato mixmaster convertira' automaticamente il
messaggio per farlo transitare attraverso la catena dei mixmaster, con
tutti i vantaggi che ne conseguono. Al momento quasi tutti i remailer
esistenti supportano questo sistema ibrido.


[ Nym Server ]

Questi non sono dei veri e propri anonymous remailer, ma si integrano
spesso con essi. In pratica sono un servizio di account anonimo ideato
sostanzialmente per automatizzare la procedura di risposta dei messaggi,
procedura che potrebbe altrimenti essere implementata solo tramite l'uso
(a volte scomodo) di un reply-block. Un utente che voglia aprire un
Nym-account non deve fare altro che scaricare la chiave pubblica del Nym
e inviare (rigorosamente mediante catena di anonimato cypherpunk o
mixmaster) una richiesta di attivazione comprendente:
- un nickname o un nome utente
- una chiave pubblica PGP appositamente creata per l'uso
su quel nym-server
- un reply-block (o piu' di uno - opzione avanzata) che
punta alla casella reale dell'utente
Per inviare posta attraverso il Nym, il mittente crea il messaggio, lo
firma con la sua chiave privata del nym-account, lo critta con la chiave
pubblica del Nym e glielo invia sempre mediante catena di remailer. Il
Nym decrittera' il messaggio, verifichera' la firma (in modo che nessuno
possa inviare posta a nome di un altro utente) e inviera' il messaggio
alla destinazione (che potrebbe essere una normale casella di posta,
direttamente o tramite un reply-block, oppure un altro Nym-account). Chi
riceve il messaggio (che a sua volta puo' essere in chiaro o crittato)
potra' rispondere al mittente semplicemente inviando la risposta
all'account del Nym. Il Nym firmera' il messaggio con la propria chiave
privata (per assicurarne la provenienza), lo crittera' con la chiave
pubblica dell'utente depositata sul Nym stesso, e inviera' il tutto
attraverso il reply-block depositato dall'utente in fase di attivazione
dell'account (che puo' e dovrebbe essere diverso da quello usato dal
mittente per inviare posta attraverso il Nym). Chi riceve il messaggio
non conosce la vera identita' del mittente. Neanche il Nym la conosce. E
la cosa e' del tutto speculare se anche il destinatario riceve la posta
tramite un suo nym-account: nessuno dei due ha bisogno di conoscere
l'indirizzo dell'altro, e nessun nym conosce il vero indirizzo di
nessuno dei due utenti. Piu' anonimo di cosi'...

<lettore> c'ho il mal di testa...

Beh, comprensibile, l'uso dei remailer puo' apparire abbastanza
intricato (forse perche' lo e' :P ) ma e' abbastanza banale una volta
che ci si ha preso la mano.
Vi ricordo che il capitolo di Kryptonite dedicato all'uso dei remailer e
dei nymserver e' un vero e proprio manuale utente, i lettori interessati
possono cominciare facilmente da li'.



=============================
*** ATTACCHI NON-STANDARD ***
=============================

In questo paragrafo verranno trattati altri tipi di attacco alle
infrastruttre crittografiche molto meno conosciuti, e per questo molto
piu' insidiosi, oltre a particolari considerazioni su attacchi piu'
comuni e gia' trattati.


*** BRUTE FORCING ***

"L'algoritmo X e' suscettibile solo a brute-forcing". Basta questo a
garantire l'effettiva sicurezza dell'algoritmo? Beh, dipende. Il DES ad
esempio e' stato utilizzato cosi' a lungo proprio perche' sembra essere
molto poco sucettibile a qualsiasi attacco diverso dal brute-forcing
(d'ora in avanti "BF"). Eppure dovreste ormai aver capito che non puo'
essere considerato sicuro, a causa della lunghezza della chiave.
Algoritmi quali IDEA e CAST sono stati analizzati a lungo, e anche li'
sembra ormai quasi certo che non esista un modo di crittanalizzarli. La
loro sicurezza deriva dunque dall'uso di una chiave piu' grande. Ma
allora perche' e' stato inventato l'AES?
Il fatto e' che la resistenza al BF e' relativa principalmente alla
potenza delle macchine che vengono impiegate per portare avanti
l'attacco, potenza che, sappiamo, e' in costante e imprevedibile
aumento. Probabilmente negli anni '70 il DES era davvero quella garanzia
di sicurezza che voleva essere, ma allo stesso modo non e' detto che tra
una decina d'anni chiavi da 128 bit saranno considerate
crittograficamente sicure.
La Legge di Moore prevede il raddoppio della potenza degli elaboratori
ogni 18 mesi. Indicando con "P" questa potenza, con "t" il numero di
mesi trascorsi dall'invenzione del primo computer e con "k" la potenza
di calcolo del primo computer, la legge di Moore a voler essere pignoli
si esprime:

P = k * 2^(t/18)

che, vi faccio osservare, ha andamento esponenziale. Dal momento che gli
stessi algoritmi per la risoluzione di classici problemi intrattabili
crittografici quali il NFS hanno gia' un'efficienza euristica
sub-esponenziale, si puo' immaginare che nel futuro questi sistemi
diverranno obsoleti (a meno di non aumentare la dimensione del problema
rispetto al tempo in maniera subesponenziale anch'essa), in quanto la
loro complessita' sara' asintoticamente superata dalla potenza delle
macchine. Per quanto riguarda la crittografia asimmetrica c'e' da
segnalare che al momento esistono solo algoritmi in tempo esponenziale
per la risoluzione di logaritmi discreti sul gruppo finito dei punti di
una curva ellittica.
Vi faccio inoltre notare che, volendo applicare alla lettera la Legge di
Moore, bisognerebbe aumentare la dimensione delle chiavi simmetriche di
un bit ogni diciotto mesi.
Fortunatamente - o sfortunatamente - pare improbabile che la Legge di
Moore resti valida nell'immediato futuro. Da un lato infatti, l'attuale
tecnologia dei chip in silicio sta andando incontro ad un vero e proprio
"collo di bottiglia" perche', come gia' detto addietro, bisognera'
presto scontrarsi con i limiti della miniaturizzazione: effetto Joule ed
effetto Tunnel saranno sempre piu' difficili da combattere man mano che
si scendera' di scala. In secondo luogo pero', ci potrebbero essere
anche svolte nel senso opposto: computer a DNA e computer quantistici
potrebbero ben presto diventare realta', per non parlare della
computazione distribuita, che si e' rivelata il modo piu' efficace per
sviluppare potenza di calcolo.
Nel fare una previsione relativa al BF di una chiave bisogna essere
fantasiosi ed immaginare qualsiasi scenario di attacco possibile. Eccone
alcuni, tradotti in parte da "Applied Cryptography":


[ Computazione distribuita ]

Nel 1994 RSA-129 e' stato rotto utilizzando un software di distribuzione
del calcolo tra partecipanti di tutti i continenti esclusa l'Antartide,
sfruttando secondo le stime lo 0.03% della potenza computazionele
dell'intera Rete. Si pensa che i supercomputer stiano idle (cioe'
inattivi) tra il 70 e il 90% del tempo, i personal computer anche il 99%
del tempo. Questi tempi possono essere sfruttati per far eseguire in
background un programma apposito. Le chiavi simmetriche sembrano essere
fatte apposta per questo scopo, perche' ogni singolo computer puo'
testare un subset delle possibili chiavi, ma anche una parte impegnativa
di algoritmi tipo NFS puo' essere eseguita in questo modo.
Oltre al metodo di distribuire il software apposito, si puo' pensare di
diffondere un virus (magari corrompendo i produttori di antivirus e i
centri di rilevazione virus), il cui unico scopo e' quello di frullare
in background alla ricerca di chiavi, di diffondersi ad altre macchine e
di segnalare in qualche modo al creatore l'eventuale successo, ad
esempio per e-mail. Dopodiche' il virus potrebbe mutare in una nuova
versione il cui unico scopo e' quello di diffondersi ad altre macchine,
cancellare eventuali copie del vecchio e del nuovo virus e quindi
eliminarsi esso stesso.
Un altro sistema assurdo ma possibile e' quello conosciuto col nome di
"Lotteria Cinese". Ipotizziamo che in ogni radio e televisione venduta
viene installato un chip capace di testare un milione di chiavi al
secondo se riceve via radio un particolare codice di attivazione e una
coppia plaintext/cyphertext. Nel momento in cui il Governo Cinese
volesse rompere una chiave non deve fare altro che dire a tutti gli
abitanti di sintonizzarsi su un dato canale ad una certa ora, ed offrire
una ricompensa al primo che, vedendosi un messaggio di successo sullo
schermo, telefoni al numero che comparira' in sovrimpressione e legga
all'operatore le cifre del messaggio. Se il Governo Cinese incentivasse
cosi' la vendita di apparecchiature simili in modo da far avere almeno
una televisione ogni dieci abitanti, le grandi cifre demografiche della
popolazione farebbero il resto: una chiave da 56 bit sarebbe trovata in
una decina di minuti. Gli Stati Uniti da soli attrezzati in questo modo
potrebbero rompere una chiave da 64 bit in poche ore.


[ Biotecnologia ]

In linea teorica e' possibile fare in modo che una qualsiasi cellula si
comporti come un microprocessore. Data la vasta disponibilita' di
organismi viventi in natura, sarebbe possibile fare delle cose veramente
assurde. Esempio 1: il DESosauro.
Il DESosauro e' un ipotetico animale composto da qualcosa come 10^14
cellule. Se si potesse fare in modo che queste cellule si comportino
come un chip che testa un milione di chiavi al secondo (input e output
forniti tramite mezzi ottici o soluzioni disciolte nel sangue della
bestia) rompere una chiave da 64 bit impiegherebbe meno di 0.2 secondi.
Esempio 2: le alghe. Si crea una particolare varieta' di alga
unicellulare modificata che viene allevata in grandi vasche o distese
d'oceano. Il broadcast del messaggio viene effettuato tramite onde
elettromagnetiche (via satellite ad esempio) o tramite soluzioni
acquose. A questo punto le alghe cominciano a ruminare, la prima che
trova la soluzione produce un enzima che causa un cambiamento di colore
nelle alghe circostanti per segnalare il successo. Avendo a disposizione
alghe abbastanza piccole e veloci e abbastanza superficie da coprire,
trovare una chiave da 128 bit potrebbe richiedere relativamente pochi
anni (la mucillagine creata poi e' un problema secondario...).


[ Nanotecnologia ]

Neanche a parlarne.


[ Limitazioni termodinamiche ]

Tutto cio' che e' stato affrontato finora e', naturalmente, fantascienza
(o almeno si spera che lo sia), pero' in queste cose la paranoia non
guasta. Fortunatamente se e' vero che non esistono limiti alla fantasia,
non e' altrettanto vero che non esistono limiti alla fisica.
Una delle conseguenze del secondo principio della termodinamica e' che
per rappresentare informazione e' necessaria energia. Rappresentare un
singolo bit d'informazione mediante il cambiamento di stato di un
sistema richiede un'energia non minore di k*T, dove "T" e' la
temperatura assoluta del sistema e "k" e' la costante di Boltzmann
(k ~= 1.381 * 10^-23 J/K). Dato che la radiazione di fondo del cosmo (la
"temperatura ambiente" dell'universo) e' circa di 3.2 Kelvin, un
computer del tutto ideale che lavori a quella temperatura consumerebbe
non meno di 4.4 * 10^-23 Joule ogni volta che setta a 1 o 0 un bit (far
lavorare il computer a temperature piu' basse della radiazione fossile
richiederebbe una pompa di calore, e quindi in totale servirebbe ancora
piu' energia).
L'energia prodotta in un anno intero dal Sole e' di circa 1.21 * 10^48 J
(energia dei neutrini esclusa). Se si riuscisse in qualche modo a
incanalarla tutta per 32 anni senza la minima perdita si avrebbe
un'energia sufficiente a far passare un contatore da 187 bit attraverso
tutti i suoi possibili stati (naturalmente si avrebbe anche la totale
estinzione della vita sulla Terra, ma questo e' un altro problema).
Pero' non si avrebbe poi energia rimasta per fare nessun calcolo utile
con questo contatore binario.
Ma il Sole e' solo una stella, e anche abbastanza piccola. L'esplosione
di una tipica supernova rilascia qualcosa come 10^59 J (sempre neutrini
esclusi), abbastanza da far scorrere un contatore da 219 bit attraverso
tutti i suoi possibili stati (sempre senza poi nessun'altra utilita').
Queste cifre non hanno nulla a che fare con la potenza e la velocita'
del computer usato: questi sono limiti teorici che la fisica impone, e
implicano strettamente che un attacco BF contro una chiave da 256 bit
sara' insostenibile finche' i computer non saranno fatti di qualcosa
diverso dalla materia e non occuperanno qualcosa di diverso dallo
spazio.


[ DNA computing ]

Questo invece e' uno sviluppo che sembra piu' concreto, ma sara' (forse)
applicabile solo per risolvere problemi NP (quindi per attaccare un
cifrario asimmetrico). Nel 1994 Leonard Adleman dimostro' la
possibilita' di risolvere un classico problema NP in un laboratorio di
biochimica, usando delle molecole di DNA preparate ad hoc. Il problema
in questione era un caso del Problema del Tragitto Hamiltoniano, ovvero
qualcosa del tipo: su una mappa sono segnate "n" citta' ed "m" strade da
una citta' a un'altra, trovare un percorso dalla citta' "A" alla citta'
"Z" tale che passi una ed una sola volta per ognuna delle citta' della
mappa.
Adleman uso' delle stringhe random di 20 basi azotate per rappresentare
ognuna delle citta', e delle altre stringhe di 20 basi per rappresentare
le strade da una citta' all'altra. Queste ultime pero' non erano scelte
a random: erano state "costruite" in modo che ognuna fosse predisposta
ad attaccarsi, alle due estremita', solo e soltanto a due precise
"citta'", in modo che ogni stringa rappresentasse esattamente l'unica
strada tra le due citta'. Adleman sintetizzo' 50 picomoli di "citta'"
(3*10^13 molecole circa) e 50 picomoli di "strade", le mixo' assieme con
un enzima ligase ed attese. A reazione avvenuta, egli uso' delle comuni
tecniche di biologia molecolare (ad esempio l'elettroforesi) per
scartare le sequenze di DNA che non rappresentavano soluzioni valide.
Prima scarto' quelle troppo lunghe o troppo corte, poi quelle che non
includevano la citta' "A" nel tragitto, poi quelle che non includevano
la citta' "B" e cosi' via... Le molecole (o la molecola) rimanenti
rappresentavano la soluzione al problema.
Il fatto e' che, per definizione, qualsiasi istanza di un certo
problema NP puo' essere trasformata, in tempo polinomiale, in una
istanza equivalente di un altro problema NP, e quindi ad esempio si puo'
teoricamente ricondurre un problema di fattorizzazione ad un problema di
tragitto Hamiltoniano.
Anche se il problema risolto da Adleman era abbastanza banale (solo
sette citta', si sarebbe fatto prima a risolverlo a mano...), la tecnica
usata non presenta evidenti problemi di realizzazione pratica
nell'immediato futuro, e quindi e' da tenere in considerazione.


[Computer quantistici ]

Contrariamente ad un computer tradizionale che in un dato momento puo'
assumere uno ed uno solo dei possibili stati, un computer quantistico e'
una macchina che ha una "funzione d'onda" interna, funzione che e' una
sovrapposizione di combinazioni di vari stati possibili. Il suo
funzionamento si basa sul principio quantistico di dualita'
onda-particella. Una qualsiasi computazione effettuata su tale macchina
cambia la funzione d'onda, alterandone in una singola operazione tutto
il set di possibili stati interni, in pratica un quantum computer e' un
sistema continuo invece che discreto. Da questo punto di vista esso
rappresenta un notevole passo in avanti rispetto ai tradizionali automi
a stato singolo, e permetterebbe teoricamente di eseguire algoritmi
"sovrapponendone" le varie operazioni, con la conseguenza di ridurre il
tempo di risoluzione di qualsiasi problema NP a tempo polinomiale.
Al momento si riesce a malapena a pensare a come far eseguire a queste
macchine la somma di un bit, ma la ricerca e' aperta. La realizzazione
teorica ha sulla carta possibilita' non nulle in quanto questa
tecnologia e' concorde a tutte le leggi fisiche conosciute, ma nella
pratica non si sa ancora nemmeno se avra' un futuro.
Come molti sistemi continui infatti, un problema da cui i quantum
computer sono affetti e' la DECOERENZA, ovvero un fenomeno di
attenuazione che fa perdere nitidezza alle funzioni d'onda sovrapposte
causando il blocco del computer dopo pochissimo tempo.
Un altro grosso problema e' che non puo' essere usato nessun clock.
Immaginatevi un clock come un ausiliario del traffico ad un incrocio,
che con la sua paletta smista le auto (i segnali) nelle giuste
direzioni, col compito ad esempio di far passare dieci auto per volta.
Le cose vanno bene anche se al posto delle auto ci sono dei segnali
elettrici che vengono comandati da dei flip-flop (che possono anche
"trattenere" per un certo tempo il segnale prima di trasmetterlo), il
problema si crea nel momento in cui al posto dei segnali elettrici ci
sono dei treni d'onda (fotoni) che viaggiano alla velocita' della luce e
NON HANNO POSSIBILITA' DI FERMARSI se non perdendo energia e causando
decoerenza. Questo implica che QUALSIASI operazione deve essere
"parallelizzata", ad esempio costruendo un immenso circuito quantistico
che possa eseguire un intero algoritmo facendo passare i segnali una e
una sola volta attraverso ogni singola porta logica di cui e' composto.
Per realizzare un intero circuito di esponenziazione modulare
servirebbero milioni, forse miliardi di porte, figurarsi per un circuito
in grado di implemetare il NFS!
Comunque la tecnologia e' troppo recente per fare previsioni anche
nell'immediato futuro. Da segnalare che comunque anche un computer
quantistico sarebbe del tutto inerme contro una chiave simmetrica da 256
bit, per le limitazioni termodinamiche viste prima.


*** MAN IN THE MIDDLE ***

Questo e' uno degli attacchi piu' conosciuti e piu' subdoli che si
conoscano contro i protocolli di crittografia asimmetrica. Difendersi da
esso non e' sempre facile, anzi, e' teoricamente impossibile (anche se
nella pratica spesso se ne riesce ad impedire l'attuazione). E' stato
pensato in origine per essere usato contro i protocolli di scambio delle
chiavi come il DH, ma puo' essere esteso ad uno scambio di messaggi RSA,
una firma digitale etc.
Alice e Bob vogliono comunicare in segreto. Allo scopo si accordano su
dei parametri di dominio DH e si preparano a scambiarsi le potenze
intermedie, comincia Alice inviando la sua potenza a Bob, ma Eve si e'
intromessa nel canale di comunicazione a loro insaputa. Essa intercetta
questa potenza, la memorizza e genera una NUOVA potenza intermedia
usando un parametro scelto arbitrariamente da lei, poi la invia a Bob
spacciandola per quella di Alice. A questo punto Bob riceve la potenza
fasulla e spedisce la sua ad Alice, ma Eve intercetta anche questa. Per
prima cosa la utilizza subito per calcolare la chiave di sessione
segreta del "canale crittografico" che si e' venuto a creare tra lei e
Bob, quindi genera una NUOVA potenza intermedia fasulla (con un nuovo
parametro random da lei scelto) e la invia ad Alice spacciandola per
quella di Bob. Contemporaneamente la utilizza per calcolarsi la chiave
di sessione del SECONDO canale crittografico, quello tra lei ed Alice.
La situazione che si e' venuta a creare e' questa:

Bob <----------------------> Eve <----------------------> Alice
canale 1 canale 2

Ne' Bob ne' Alice si sono accorti dell'intruso e credono di parlare
direttamente l'uno con l'altra, mentra in realta' lo fanno attraverso un
tramite che e' Eve. Quando Alice vorra' mandare un messaggio crittato a
Bob, lo inviera' in realta' ad Eve. Questa decrittera' il messaggio,
potra' leggerlo ed addirittura modificarlo, e poi lo ricrittera' con la
chiave di sessione del primo canale, inviandolo quindi a Bob, il quale
credera' che il messaggio giunga da Alice. Eve e' un "uomo nel mezzo"
(man in the middle appunto, da ora in avanti "MitM"), e una volta che si
e' installato nella comunicazione non c'e' modo di individuarlo se non
facendo in modo che Alice e Bob si possano comunicare qualche dato
identificativo (la session-key usata ad esempio) attraverso un canale
gia' sicuro. La stessa identica strategia si puo' adottare per leggere
un messaggio crittografato RSA (sostituendo le chiavi pubbliche che gli
utenti si scambiano prima della comunicazione) o per alterare la firma
dei documenti in un protocollo di firma digitale.


[ Eludere il MitM ]

Difendersi da questo attacco come gia' detto e' tutt'altro che banale.
Esistono sostanzialmente due modi.
Il primo e' noto come "Interlock Protocol", e' stato inventato da Rivest
e Shamir ed ha una buona probabilita' di eludere il MitM, anche se
purtroppo non e' utilizzabile in molte circostanze. Questo protocollo
puo' essere usato ogni volta che Alice e Bob debbano scambiarsi un
messaggio di senso compiuto (quindi non va bene ad esempio per lo
scambio di una session-key tramite DH) e funziona cosi':

1 - Alice invia a Bob la sua chiave pubblica
2 - Bob fa altrettanto con la sua
3 - Alice critta il suo messaggio con la chiave pubblica di Bob
4 - Bob fa altrettanto con il suo messaggio e la chiave pubblica
di Alice
5 - Alice invia a Bob META' del testo cifrato che ha ottenuto
6 - Bob invia ad Alice META' del suo testo cifrato
7 - Alice invia a Bob la seconda meta' del suo cyphertext
8 - Bob invia ad Alice la seconda meta' del suo cyphertext
9 - Entrambe le parti decrittano i rispettivi messaggi ricevuti

Il punto fondamentale e' che meta' del testo cifrato e' completamente
inutile senza l'altra meta': non puo' essere decrittata separatamente
(questo si puo' ottenere in vari modi, agendo sia sul cifrario
asimmetrico che su quello simmetrico sia tramite funzioni hash). Nessuno
dei due puo' leggere il messaggio dell'altro prima del passo 9.
Un MitM che cercasse di ripetere il giochetto di prima si troverebbe in
difficolta': ricevuta la prima meta' di un messaggio, esso non avrebbe
modo di decrittarlo e di ricrittarlo con la sua chiave pubblica, quindi
dovrebbe inventarsi un messaggio totalmente nuovo per ognuno dei due
partecipanti allo scambio, e questo ha ottime possibilita' di destare
sospetti nei due interlocutori. Naturalmente cio' puo' accadere solo se
il messaggio in questione ha un senso compiuto, se il messaggio fosse
una chiave simmetrica random nessuno dei due potrebbe riconoscere un
senso in quello che riceve, con o senza MitM.
Il secondo sistema si basa essenzialmente sulla presenza di una "terza
parte di fiducia"
, ovvero un terzo partecipante che prende parte al
protocollo ma non ha nessun interesse nell'alterare le comunicazioni tra
Bob e Alice. Un "arbitro" fidato insomma.
E' infatti da notare che il problema del MitM e' dato
dall'impossibilita' di certificare la reale appartenenza delle chiavi
pubbliche ad un certo interlocutore. Se ad esempio scarichiamo una
chiave pubblica da un keyserver, questo si comporta da terza parte di
fiducia e garantisce che quella chiave sia effettivamente di chi dice di
essere. Il solo fatto di poter scaricare una chiave pubblica da Internet
(ad esempio, da un sito oppure tramite ftp) implica che la stessa
"Internet" (qualunque cosa significhi nel caso in questione) sia
chiamata a fare da terza parte di fiducia.
Nel momento in cui le chiavi pubbliche sono certificate, eludere il MitM
diventa banale. Nel caso dello scambio di chiavi DH ad esempio, il
problema consiste nell'assicurarsi che le potenze intermedie che
transitano sul canale appartengano effettivamente ai possessori delle
chiavi. Per far questo basta allegare, insieme alle potenze intermedie,
le FIRME di queste potenze (ciascuna rispetto alla propria chiave), e un
MitM non saprebbe forgiare delle firme valide per le nuove potenze
fasulle.


*** TIMING E POWER ATTACK ***

Questi attacchi sono abbastanza recenti e spesso di difficile
attuazione, ma molto insidiosi nel caso di una rete di computer ad
esempio.
Il Timing Attack si basa sulla misura dettagliata (al millisecondo) dei
tempi di esecuzione delle varie operazioni crittografiche, tempi che in
teoria variano a seconda della chiave usata o del plaintext. Nel caso
dell'RSA ad esempio, quando andiamo a decrittare un messaggio con la
nostra chiave privata dobbiamo eseguire un'esponenziazione modulare.
Abbiamo gia' visto che questa operazione richiede tanto piu' tempo
quanti "1" ci sono nella rappresentazione binaria del modulo (che e' la
chiave privata vera e propria), quindi misurare il tempo di esecuzione
da' indicazioni sulla chiave. Il concetto puo' essere esteso alla
maggior parte delle operazioni crittografiche.
Il Power Attack e' per certi versi analogo, e parte dal presupposto che
un processore consuma tanta piu' energia quanto piu' sono impegnative le
operazioni che esegue. E cosi', monitorando attentamente il consumo di
corrente da parte di un computer, e' in teoria possibilie capire che
cosa sta facendo in un dato momento e come lo sta facendo, raccogliendo
cosi' di conseguenza molti dati interessanti.
Da segnalare comunque che questi attacchi si possono effettuare solo
attraverso un dettagliato monitoraggio, e quindi spesso solo avendo un
livello di accesso molto elevato al sistema, cioe' solo quando si ha
gia' compromessa in parte la sicurezza di quel sistema.


*** ATTACCO TEMPEST ***

Questo attacco, noto anche come "attacco di Van Eck", e' molto
conosciuto in ambito militare, tanto che si spendono molti soldi per
difendersi da esso, cosa tutt'altro che facile. Si basa sul principio
che ogni congegno elettronico emette radiazioni elettromagnetiche, ivi
compresi computer, cavi di rete, tastiere e monitor. Questa radiazione,
nota in gergo come "radiazione di Eck", e' un rumore di fondo spesso
quasi impercettibile, ma e' sempre presente: i cavi di alimentazione
emettono impulsi che variano a seconda del consumo di corrente della
macchina a cui sono collegati (e qui ritorniamo al Power Attack), i cavi
di rete si comportano come antenne, perdendo informazioni, e cosi' via.
I monitor CRT sono i maggiori responsabili della radiazione Eck:
emettono cosi' tanto segnale che utilizzando un'apposita attrezzatura e'
addirittura possibile visualizzarne il contenuto stando al di fuori
dell'edificio in cui si trovano, i rilevatori in questione entrano
agevolmente in un furgoncino che puo' essere parcheggiato
tranquillamente fuori dal vostro ufficio.
Per quanto possa essere debole, la radiazione Eck e' sempre presente, e
un nemico ben attrezzato non ha nessuna difficolta' nel rilevarla. I
costi di rilevamento naturalmente sono molto alti, ma quasi sempre MOLTO
inferiori ai costi di un attacco crittanalitico.
Per difendersi dall'attacco TEMPEST i mezzi ci sono, ma sono a volte
molto costosi. L'esercito ad esempio spesso attrezza delle stanze o
addirittura interi edifici con apparecchiature schermanti o che emettono
rumore di fondo per fare interferenza (SCIF: Secure Compartmented
Information Facilities), tutte misure estremamente efficienti ma non
alla portata del comune utente. Per situazioni normali probabilmente il
metodo piu' sicuro e' il non mostrare sul monitor informazioni riservate
(come avviene nel PGP per la passphrase ad esempio), il problema e' che
cio' e' attuabile fino a un certo punto naturalmente (un messaggio che
vi arriva per e-mail dovrete pur leggerlo prima o poi no?). Allora una
buona soluzione e' quella di dotarsi di un monitor LCD, che non emette
radiazioni come un CRT, oppure visualizzare i dati sullo schermo con un
font speciale studiato apposta per rendere piu' difficile la
visualizzazione remota (quest'ultima e' una trovata recente di alcune
applicazioni tipo PGP).



=============================
*** QUANTUM CRYPTOGRAPHY ***
=============================

Ne voglio accennare i principi perche' mi sembra il caso, ma trattare a
fondo l'argomento richiederebbe un capitolo intero
sull'elettromagnetismo, quindi non aspettatevi informazioni dettagliate.
La crittografia quantistica e' uno degli ultimi ritrovati in fatto di
sicurezza, la sua inviolabilita' e' assicurata, oltre che dalla
matematica, dalle leggi della FISICA. Essa ha un approccio totalmente
nuovo al problema della riservatezza delle informazioni, la cosa
veramente sbalorditiva e' che non solo riesce a nascondere la
comunicazione tra due interlocutori remoti, ma nel momento esatto in cui
un eavesdropper tentasse di sniffare i dati, la linea verrebbe
immediatamente disturbata e i dati in transito distrutti per sempre,
permettendo cosi' tra l'altro ai due interlocutori di accorgersi subito
della presenza del nemico.
Senza entrare nel dettaglio, la cosa si ottiene appoggiandosi a DUE
canali: uno e' un canale pubblico (ad esempio una normale linea
telefonica), dove Alice e Bob si scambiano dati non segreti, l'altro e'
un CANALE QUANTISTICO. In questo canale (tipicamente fibra ottica, ma
anche raggi laser tramite satellite) i bit viaggiano sotto forma di
fotoni. Per il principio di dualita' onda-particella, questi fotoni
possono essere emessi da Alice "pilotandone" lo stato di onda (ad
esempio la polarizzazione o lo spostamento di fase) senza alterarne il
comportamento di particella, cioe' in pratica Alice puo' emettere fotoni
polarizzati o no in un certo modo etc., fotoni che percorreranno
comunque il canale quantistico arrivando a Bob. Ora, Bob non conosce in
anticipo la sequenza di fotoni che Alice gli inviera' e quindi si
preparera' a "misurare" i loro stati con degli strumenti appositi. Ma
questi stati sono fatti in modo che Bob non ne puo' misurare uno
qualsiasi: egli dispone di uno ed un solo "strumento" (ad esempio un
polarizzatore) per ciascuno degli stati possibili, e deve scegliere
QUALE strumento usare per ciascuno dei fotoni in arrivo. Infatti il
problema e' che, a causa delle leggi di Heisenberg, un fotone che
interagisce con uno strumento di misura viene irrimediabilmente
*distrutto*, cioe' ne viene alterato lo stato in maniera imprevedibile,
e quindi perde l'informazione che trasportava. Se Bob sceglie lo
strumento "giusto" per un dato fotone in arrivo, egli sapra' se quel
fotone rappresentava un bit "1" oppure "0", ma se invece sbagliasse lo
strumento non ricaverebbe nessuna informazione da quel fotone. Il canale
e' fatto in modo che, statisticamente, il numero di fotoni correttamente
"misurati" da Bob e' del 50%. Quando la sequenza di fotoni termina, Bob
contatta Alice tramite il canale pubblico, comunicandogli la sequenza di
STRUMENTI che ha usato per le misurazioni (ma NON i risultati delle
misurazioni riuscite). A questo punto Alice sa quali fotoni Bob e'
riuscito a misurare e quali no, quindi scartera' dalla sua sequenza
originale i bit corrispondenti ai fotoni persi (il 50% circa) ed
utilizzera' gli altri per generare una chiave di sessione segreta per
cominciare lo scambio di dati vero e proprio (e Bob fara' altrettanto
usando i bit corrispondenti ai fotoni che e' riuscito a misurare).
Ora consideriamo il caso che Eve intercetti la comunicazione quantistica
(ad esempio tagliando la fibra ottica e imbastendo la sua attrezzatura).
Tutti i fotoni che Alice invia verranno intercettati da Eve, che
riuscira' a misurarne il 50% circa. Sia i fotoni misurati che quelli
mancati andranno comunque persi nel processo di interazione con lo
strumento, e quindi non arriverebbero a Bob. Motivo per cui Eve dovrebbe
riinviare a Bob gli stessi fotoni e fare finta che niente sia successo.
Ma in realta' lei conosce solo il 50% dei fotoni (e quelli li inviera'
correttamente a Bob), mentre il restante 50% dovra' inventarseli di sana
pianta. Bob a questo punto ricevera' il 50% di fotoni corretti e il 50%
di fotoni fasulli. Di quel 50% di fotoni corretti, egli riuscira' a
misurarne a sua volta il 50%, quindi in totale egli dispone stavolta
solo del 25% dei bit inviatigli da Alice, e dal momento che Alice ne
usera' invece il 50% per generare la chiave segreta, i due non
riusciranno a comunicare e si accorgeranno cosi' dell'intruso (che,
detto per inciso, non conosce nemmeno la chiave segreta, la quale
peraltro non gli sarebbe di nessuna utilita' dato che la conversazione
viene chiusa).
Pur essendo nel suo aspetto *rivoluzionaria*, non bisogna pensare alla
crittografia quantistica come alla panacea di tutti i mali, per vari
motivi. Il primo e' che le attrezzature necessarie (in particolare
l'allestimento di un canale quantistico) hanno costi elevatissimi. Il
secondo e' che la quantum cryptography deve per forza poi appoggiarsi
a qualche altro sistema di cifratura simmetrica - fosse anche un OTP -
per il transito dei dati. Il terzo e' che, mentre essa e' adatta al

  
lo
scambio di chiavi, non puo' essere usata per "immagazzinare"
informazione riservata (come su un hard-disk ad esempio).
Da notare anche che nel caso Eve possa intercettare sia il canale
pubblico che quello quantistico, essa potrebbe portare a segno con
successo un attacco MitM prendendo parte attivamente allo scambio di
fotoni, difendersi dal MitM richiedera' altre astuzie come gia' visto.


* * *



+-------------+
CONCLUSIONI
+-------------+

Ebbene si', siamo giunti alla fine. Non so, ma non mi viene in mente
molto da dire qui. Forse non c'e' molto da dire.

Mi sento come se mi fossi tolto un grosso peso di dosso. Io spero che
questo articolo vi sia stato utile in qualche modo, ma per quel che mi
riguarda il mio impegno e' stato mantenuto: ho pagato il mio debito alla
Rete, gli ho restituito cio' che ho preso - forse con gli interessi.

Scrivere un articolo impegnativo come questo e' servito anche a me, mi
ha fatto crescere, a volte mi ha fatto sentire un po' piu' importante. E
tuttavia non credo che lo faro' di nuovo, non nel breve futuro almeno, a
meno che non emergano ulteriori necessita' quale il bisogno di pagare un
nuovo debito...


ottobre 2003 - Italia
Sinceramente vostro,

Zer0 (Massimo De Lirio)



+--------------------------------------------+
BYES & 10X (alias saluti e ringraziamenti)
+--------------------------------------------+

Volente o nolente mi sento in dovere di dilungarmi ancora di qualche
Kilobyte per ringraziare e per salutare alcune entita' - devo chiamarle
cosi' perche' alcune di queste per me sono tuttora nient'altro che un
nickname o un host address. A dire il vero avrei molti piu' insulti da
fare che buone parole da dire, ma per ora non voglio lasciare dell'amaro
in bocca a molti di coloro che lo meriterebbero.

Innanzitutto ringrazio lo staff di OQ che sta facendo secondo me un
ottimo lavoro (tenete duro ragazzi). Un ringraziamento particolare va a
JEYoNE, senza il quale quest'articolo non so se avrebbe visto la luce
(anche se deve ancora imparare a usare bene il PGP :PPP ).

Non posso non menzionare qui lo staff di Isole nella Rete (ecn.org) e di
Autistici.org, che insieme costituiscono forse una delle piu' vive
realta' cypherpunk italiane (gestiscono tra l'altro l'anonymous remailer
di paranoici.org, il Paranoia Remailer). Il loro sforzo per la
salvaguardia dell'anonimato digitale ha un valore inestimabile.

Ringrazio anche lo staff di irc.azzurra.org, i quali canali hanno
contribuito alla mia "formazione", in particolare il chan #hackmaniaci.
Ricordo con nostalgia i tempi in cui era una vera fucina di attivita'
piena di gente in gamba, e non un posto dove la gente vegeta soltanto...
Mi mancherete.

Per quanto riguarda i saluti, meritano per primi i Tre dell'Ave Maria
:) ovvero Liz, Redo e Arcadian (quante ne abbiamo fatte e soprattutto
quante dobbiamo farne ancora!). Se dovessi dire di avere un "amico" nel
vero senso della parola questo non potrebbe essere che Liz, il quale
da anni mi sopporta e rischia la pelle con me per le cazzate ;)
Unica cosa che mi dispiace e' che lavora per una multinazionale USA, ma
visto che lo fa al solo scopo di sabotarla dall'interno lo perdono :PPP

Saluto anche Ippatsu Man, una delle poche entita' abbastanza interessate
in campo di crittografia da poterci imbastire discorsi costruttivi. Fa
un certo effetto sapere che esiste anche qualcun altro che conosce il
significato di certi acronimi tecnici =)

Un saluto, l'ultimo (lo dovevo), all'hacklab della mia citta' natale,
la ZDR, solo perche' mi ha permesso di fare alcuni incontri
interessanti...
Una cosa e' certa: io credo che ormai si sia perso il significato
della parola "hacker". Quello vero temo non lo conosca piu' nessuno
sulla faccia di questa Terra, forse nemmeno coloro che possono vantarsi
di esserlo stati una volta, tanto tempo fa. Vi siete mai fermati,
durante le vostre cene sociali, a rifletterci?


Credo di essermi dimenticato di molte altre entita' ma, perdonate la mia
fretta, voglio lasciare la lista incompleta per poter finire alla
svelta.



*** CONTATTI ***

Mi trovate - di rado - su IRC:

irc.azzurra.org: #hackmaniaci, #ondaquadra
irc.autistici.org: #hackit99, #zdr

Vi allego le mie chiavi PGP e il mio indirizzo di posta elettronica.
Per qualsiasi motivo - possibilmente serio - contattatemi pure, ma
sono molto impegnato e potrei non avere il tempo di rispondervi.


zer0@paranoici.org


-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGP for Personal Privacy 5.5.3
Comment: ID:0x01965550, DH4095/DSS1024/IDEA128, created 19/08/01. Use
to encrypt only.

mQGiBDt/qZ8RBADU2K7btlh2oukC0JTKhXQFMIqcKWGvAWzhy7h+sDBnjMgk9Lrr
USOrer5hM4+Yj6zWH1phQZErzfLIDWgvgAVhQKmaeFNPnVbxY6nfAJhlqYWpQdAF
7A5Jam9hs/o+4Hgi5ppDoduKHvX3E/MuK8hI6Oh7LHCJlQ/jY3ZfHbEC8QCg/6/C
1Zt83Ii7H6Uv/V3hamGkmM0D/1ZIxIw8VlLTIurDCZNDqmbep+L9CnkLUPXn4K7Y
lK0l/RtJu3VwGKItqZcEJIVE2OIqvf370AYpRSIbXIbniwcMV7aQTa7W3l3X3tiY
SUtHvFB2KMLgfbHopRjemSDBNgF4sskj8V7sKNV76VJqf28u2xtJniRhE0uM26y1
K/2bA/43QFgIu20XznMx09M0odvz32mA74oqAbmfTMXNNVM0dfeUqBBeQUr0gct0
w7bWDV8ovcjo8qfGIk4yXI740wUpMB2vnrm+9fEL8kABBcJSRX3eP+mkZkj43QPL
pDyKLBhogXMgu8VYyGLF8nYy89CJ2b4PHZs/AGMc25Fz/xNx/LQzWmVyMCAoTWFz
c2ltbyBEZSBMaXJpbywgSXRhbHkpIDx6ZXIwQHBhcmFub2ljaS5vcmc+iQBKBBAR
AgAKBQI/P3GJAwsBAwAKCRCyLka7AZZVUJGUAKCSxLzp3tXZreIVhdaukncreSp6
7wCcDqJvPBODX7UMSLrQRmV2GNeXsC2JARUDBRA/P3/tTz4cTacR3ckBAUszB/4m
77ISeguzAdsS6rLRPxi++qd/YWnyZ1ImRehpEvIsZ2uER2bzHOnzByl4BZtEq7v9
TxMBSJv1EgmvoPa6Z+9jJ4TP4X+bddsYuGf5tzSkyp7J9GLlcxIT7UrFIdlxjuM2
ndRELYMXBj1zosoiQ0dyC7zh4U0f8YWQ/zs6/3mSxEounGN0fQEKx5f+d+WORfQr
A6j/rQVZREYFoSJgSytIOt/mTwk9youvtb0AGLHrmb7gO89hiZfxYGgbsU3RoG68
6SE+gNfRoCu7f2doddpoFVTHsH2vh0Uyqn2oJDoRFUgfdjhhSQWrWHcJcY01Fzmt
iLYoyw3llllbixk4vrLfuQQNBDt/r3cQD/9s2BJkVZkh36vjcv+zmfjT5hL0cOwG
m1xAOdINtZeCwpsq/q5Jesd2qV4Awty3vH+z6UtR6fVLkOklGx+anREhxT78GwQW
pr4TEzpqTo0ob6RMwLvsQgpijYE73PUQplFJn4Q7XS/wcx74BnVDv0CiBe+W/ZDc
QxR+J6hOOvXGQAY+3v8qs0fonzItTXskySFi9syv+sn5CrvhviR6kjvGhmaYxo7B
FpXCMByDyvkOxA5I5cw4UMtQz5gSOLUg3+pwYYzuldQdXtXDZgQXuefkJI5LQXb/
ext1ub5v+CfBTHCVl/HyyMotp0RiufzD3IuLHKzl84BdyvF6ljIwTpMrUbfCiDrS
6C5c77BwLuH7NKcaacW+iijVhRywf32A9GlQWkyIkWEkN5KE4fSdtp0eTtgTWi6K
U7iw+ppHj6XP4PklRe/YN2JtmR1T6f7rNgPFXa7eP6xIcWDlJp6dy7x0T7CMy78G
R/wwI+vmI8uWLX2bzpIIv6KEVHzvy3CM4A4KqTMF+HvK2T7eGhZwe0bZP5d1D7rP
wskMgY3o0uQm+AuUpWFMg64zlWEUfJrhsA046oTlTLguhxDIiEpGqi7t6ba03bIp
+me3PSJW/ooUdIG21jh+iyV/hWSuK0S/FHHo1IzPCzksJZmUcI6tY62ah3DWOkEY
8UYZU7fofgOM6wACAg/+N1GAIs8BbDhMTcizhi8aLqG0rkxq1zxGTOkEtrD1KUoB
6SwVPX0NylNP62xb1XGc3Ud+wZ7GYAylCsExAQixVCX0SvnV3eyiHySt4MHl54jv
ZCKE+rXsF0V8S2SIsLyYg6qH53DPK5aMfvE8CzPjDfNab+j+QmOukdJVeg0hP/vu
178QfCeVSJUxrO8PWPbvOJJ3XrKKYi+Is0tpsxP6O3Om+HxWMechk7WdIMofoK5p
lexhB+pn4J1w7a6gW0y+suQ3SyAQJQ9rG5Nqu0mWVW5Dp8sgb1SXOlDEgEso8whv
RE06Q95R0GKqgLKy7wmVSq0Z6rPk5w4tFXjtqy8qHaGUSnpA2Dgg8/UBtrH4NeWe
vS5bVJ1k3Daz91GsGbDIVVbdwbZiMhjviL5XjASX5jhV23tWO6UklhXrLbdtXbbX
zOqxi7I2IadFU6/k+MdBhaUTe/FVORDdDmcE5jfGagm3nLN5rniEwnpu2JOYl+2z
ek2qxRhr3IwMAXheQBX+VFWV5aIpftW+XS6jEkUc7laFDJmF2JA5eUaIs+5yhkxE
BMyh5Jw1xvWEKBLb8SplAm8r49LCu2GEaSwmfSDLknVncVMWLgPZy5jh4JunPtg5
RS5925cUZlOEQyWuwwGT9/R8bEKRUNSl5UY1T47uiyqxlwrCHz2c9lt6f+J1/6yJ
AEYEGBECAAYFAjt/r3cACgkQsi5GuwGWVVB2JgCg+YeyeORuMfEc0WD3TUd/zum/
/XUAn3yH3fauP+wZRto4eFWZDGipuR8b
=isqr
-----END PGP PUBLIC KEY BLOCK-----

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGP for Personal Privacy 5.5.3
Comment: ID:0xA711DDC9, RSA2046/IDEA128, created 02/09/01. Use to verify
signature only.

mQENAjuSDYIAAAEH/jESqDJVcHUrh2I+c6ufmNodBQa73XdWa2KKx0+WPRWI2HM4
buIGZe/RNx2FN+07qk206y3NYAF9lYqgMrBfuBxVVSV9BGaC0l9Ymp3LpS5OID6k
dnaByuRofMEkd0xNvkS9rnPOIz5YtlCdoOGVQbwPyzskI8Lpq9qb7dFiiHTevB/m
v2dfZjxXQMUgIWnjNYrCcF8tHEQnhEAaEKHAroVOCZ0hVbaAsxgTnBb4oE7c0BTS
l/z92/U6JPlsJQNld39tHQk8qCJBlpMcZkOEkiwB4zEmDa95r1olvvLaOmw5Nkcd
aJutisehj7uBmeycKCEm+CVh+tcpTz4cTacR3ckABRG0M1plcjAgKE1hc3NpbW8g
RGUgTGlyaW8sIEl0YWx5KSA8emVyMEBwYXJhbm9pY2kub3JnPokBFQMFED8/cWBP
PhxNpxHdyQEBJzoH/R4WMwwBORnz36etBezwmneZW7r3LldnxoAn1L+xFnOcK7eQ
yAttfPkMzAE4JzI+7VdfXK2uXPdnzXLTRIsh2eTPE9FWnqOvOHaQABScnHed2D/C
SdK0PA7R2OeURK7ZU8HvqJUICj01i/rPHI64Fdx+Jt6d48hyORfTlOka2yGwvia8
wNve9u8jofHdN9+LAdGwoEaZoqRda+NEsbyAxDgmx4fd0aCC6NftcmIcKmJnXzp8
bAr0/5EBUa33DlS9XIygkatucWH4PQkIda1mXxMghQ2MoGbq8NoX6suwG+mc+6OG
H7JZsOWK8vICRZEM/5r6O0rbhUp2dhPyS/OQ8MeJAEYEEBECAAYFAj8/f9AACgkQ
si5GuwGWVVC8bQCfeYudmxfTkNWb4QNQ6LaNSNuHqksAn2jrvmmgYCIDFdEb3kn1
UEd6GjCN
=hUCs
-----END PGP PUBLIC KEY BLOCK-----


Concludo infine con una frase forse un po' "piccante", ma lasciatemi
divertire...

"CODiCE iNVERSO, l'articolo di e-zine che ha vantato il maggior
numero di tentativi di imitazione"



+---------------------------+
APPENDICE 1: Bibliografia
+---------------------------+

Bruce Schneier, "Applied Cryptography",
John Wiley & Sons Inc., 1/1/1996

Autori vari, "Kryptonite", Ed.Nautilus

A. Menezes, P. van Oorschot, S. Vanstone: "Handbook of
Applied Cryptography", CRC press, 1996

Andrew M. Odlyzko, "Discrete logarithms: The past and the
future", AT&T Bell Labs, 19/7/1999

Andrew M. Odlyzko, "Discrete logarithms in finite fields and
their cryptographic significance", AT&T Bell Labs

Bruce Schneier, John Kelsey, "Unbalanced Feistel Networks and
Block-Cypher Design", Counterpane Systems

S. Bakhtiari, R. Safavi-Naini, J.Pieprzyk, "Cryptographic Hash
Functions: a Survey", Centre for Computer Security Research,
University of Wollongong, Australia

PGP Inc., "PGP User's Guide"

C. Adams, RFC 2144: "The Cast-128 Encryption Algorithm",
Entrust Tecnologies, maggio 1997

"Instructions for nym.alias.net" (help@nym.alias.net)

RSA Data Security Inc., "CryptoBytes", RSA Data Labs
Newsletter

GnuMP documentation: www.swox.com\gmp



+-------------------------------------------------+
APPENDICE 2: Quattro chiacchiere sugli allegati
+-------------------------------------------------+

Dovreste trovare una sezione "CODiCE iNVERSO Allegati" in questo numero
di OQ. Esso contiene principalmente quattro cose.

Innanzitutto una tabella dei codici ASCII. Beh, non c'e' molto da dire,
in effetti potevo anche non metterla.

Poi una lista di numeri primi, sono tutti i primi 100000 a partire da
2. Li ho trovati su un sito piuttosto "poetico" (matematicamente
parlando): Aesthetics of the Prime Sequence (www.2357.a-tu.net).

Poi c'e' una sezione per la codifica/decodifica dei dati in Base64,
contiene sia un modulo di programma VB che un programma in c (che i piu'
tra di voi di certo apprezzeranno maggiormente ;), vi assicuro che sono
spesso di utilita' mostruosa. Per quanto riguarda il modulo VB,
contrariamente al resto della "politica" di questo articolo e nonostante
si tratti (appunto) di Visual Basic, stavolta su queste routine e' stato
fatto un discreto lavoro di ottimizzazione (nei limiti delle mie
capacita' naturalmente...), proprio in quanto l'utilita' delle stesse
non e' trascurabile in diverse applicazioni. Il risultato e' piuttosto
veloce, soprattutto se confrontato con altre routine VB scaricabili in
rete, e il tutto e' stato studiato per avere un buon compromesso tra
velocita' e spazio occupato in memoria (sia dai dati che dal codice).
Per info dettagliate vi rimando al readme.

E poi il programma Camp.exe (completo di sorgenti VB): e' una
calcolatrice multiprecisione (simile a quella di Windoze), ma gestisce
solo numeri interi non negativi, della dimensione voluta, oltre alla
possibilita' di effettuare operazioni logiche (AND, OR, etc...) ed altre
amenita' quali il test di Rabin-Miller per la primalita'. Usa la
libreria VBwinLAMP.bas, scritta da me tempo addietro, e questa e' la
vera novita' in quanto tutto il codice e' stato fatto SOLO in VB senza
ricorrere all'ausilio di librerie esterne. Inutile che vi dica che il
risultato non e' granche', ma per ulteriori info anche li' c'e' un bel
readme. Naturalmente lo scopo principale e' quello di illustrare un modo
"pratico" di utilizzo delle stesse routine di VBwinLAMP.bas, la quale e'
stata in origine pensata per fornire calcoli in multiprecisioni per le
mie prime prove di crittografia asimmetrica. Credo comunque che, anche
per quelli tra di voi che conoscono bene altri linguaggi ma non si sono
mai interessati di calcoli in multiprecisione, questo sia un ottimo
sistema per capire i concetti e le difficolta' di base nella
programmazione di un tale oggetto.

Poi alcune correzioni. Ho deciso di non inserire il programma
"Analizzatore d'entropia", principalmente perche' in fondo non credo che
sia cosi' necessario vedere l'applicazione "pratica" del sistema di
calcolo dell'entropia, gia' ampliamente trattato al cap.5, l'algoritmo
di calcolo sara' facilissimo per chiunque da scrivere seguendo le
istruzioni. Se poi quel che vi interessa e' un VERO programma che vi
serva a questo scopo - e lo faccia in maniera efficiente - sara' molto
meglio che diate un'occhiata al programma di Ippatsu Man (in Delphi)
apparso su OQ 7, che in quanto a performance e' sicuramente competitivo.
Ho deciso inoltre di non includere il "giochetto a sorpresa" di cui vi
avevo accennato qualche volta fa. Si trattava di un abbozzo di
protocollo di tunnel crittografico per applicazioni client-server, ma
visto che ho poi trattato ampliamente i protocolli di comunicazione,
oltre agli algoritmi, nel resto dell'articolo, ho deciso invece di
includere VBwinLAMP.bas, in modo che il programmatore principiante
interessato avra' qualcosa di concreto su cui iniziare a lavorare per
creare il proprio protocollo di scambio dati o di autenticazione, o
il proprio (elementare) programma di crittografia.


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x09][ViSi0Ni]ESTASi Di UN BLiTTER iMPAZZiT0 [Arkanoid]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

L'agonia superficiale/artificiosa di un mondo
malato di nessun tormento
In estinzione il desiderio precalcolato
da milioni
di anime urlanti un luogo
il tempo beffa il disegno logico arcaico
dell'ondaquadra in un mare di calchi digitali

Sequenze di colori in re minore
ricordi macchiati di rosso e giallo levigato oro
Sinapsi sinestesi sintassi replicanti il delirio
Dei privi di felicita'
le macchine ruggiscono il dolore orrendo e malato del
Destino predefinito

il profeta ridireziona l'angolo di visuale
dell'assenza
di rifiuto
dell'Aplicassie Blu
prima che i controlli di un Dio privo di felicita'
feriscano i sentimenti ionizzati dalla presenza
di cani
liquidamente distinti dall'il-lo-la
matematico/sistematico

il tempo e' come un pezzo di ghiaccio che cade
senza fare rumore
sull'agonia superficiale/artificiosa di un mondo
malato di nessun tormento
afflitto dai mali di tutte le epoche
privo di un vero
CoprocessoreAnimaInfinito

Dove Dei privi di felicita' accecano i sensi con
l'idiota del creato precocemente ringiovanito
E l'idolo allo specchio dipinto dall'artista
vittima della
Passione plastica vive nel quadro
senza invecchiare

di Universo in Universo le stanze si scambiano
posto
sul piano come nel sogno dell'uomo dei cubi
il destino si ricuce e strappa la tela della prima
Ri-Voluzione del Palombaro Paradigma
il destino si ricuce e lacera
il registro atomo/luce

malato di nessun tormento
l'Oltremacchina assalta
l'imbecille respiro di un Dio senza felicita'
Legato alle rovine di un passato-matrice
Prima del tempo
l'epoca del Calcolatore Aristocratico
Distrugge l'abominio dell'ovvio e rivolge gli
sguardi ipersensoriali alle immagini autodimensionate
[LIVELLO12]

l'armonia sublime dell'organo urlo/campione
diverte
i guerrieri della contemplazione del tempo
prearcaico
ma i colori meraviglianti dipinti a mano elettronica
non riportano il sorriso

Dei privi di felicita' percorrono il cammino
A Rebours
Controcorrente zampilla il rimorso
di novecentonovantanulla
voci malate di nessun ricordo

"Dio e' la distanza piu' breve da zero all'infinito"
(Alfred Jarry)

il tormento piccolo/immenso non risponde
all'insulto
E il Tempo fermo alla velocita' dell'Apocalisse Blu
Respinge e respira brandelli di carne
dell'oltremacchina
e piangendo
si dissolve


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.]
.: [O]nda[Q]uadra [0X0A] OQ20031122[0A]
:: [0x0A][SHUTD0WN] L'EL0Gi0 DELLA P0VERTA' [Tritemius]
[.]::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.

La poverta' e' la condizione naturale dell'Uomo, o almeno cosi' dovrebbe
essere.

Prima che la pubblicita' iniziasse a marciare col passo dell'oca nelle
nostre teste, era evidente a tutti che l'uomo saggio non e' colui che
ha tutto, bensi' colui che a tutto puo' rinunciare.
In questa rinuncia vi e' la sostanza della nostra liberta'.

Noi poveri, meravigliosi straccioni!
Anche qui nell'Occidente dorato e moralmente miserabile, perche'
la poverta' non e' solo economica e materiale ma spirituale.
Anche noi, che abbiamo di tutto di piu', in realta' non siamo che poveri
automi narcotizzati e ipnotizzati, vestiti di nulla, al guinzaglio dei
bottegai globali con le mani pulite e la faccia da galera.

E in questa strana dittatura, il cui ambasciatore e' Topolino e il cui
mantra silenzioso e subliminale e' produciconsumacrepa, la poverta'
diventa il crimine piu' orrendo.

La poverta' e' la nostra ricchezza e la nostra liberta', una immane
sfida contro i prepotenti della Terra, una tremenda testimonianza del
disastro dell'ideologia senza volto e senza anima.
Merita quindi un grande elogio la poverta', motore inarrestabile del
riscatto di un'Umanita' senza piu' dignita', senza piu' misura, senza
piu' equilibrio: senza piu' scampo.

Alla banda di bruti che governano i nostri destini, che conoscono il
prezzo di tutto e il valore di nulla, contrapponiamo la nobilta',
la dignita', lo sguardo fiero, invincibile e rivoluzionario della
stirpe dei senza re.


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos from Google Play

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT