Sincronizza Indice |
Scarica il progetto |
Testo dell'articolo |
Stampa l'articolo |
Avrei la necessità (e la curiosità) di sapere come si può calcolare il CRC di un file. Immaginando che la cosa possa interessare anche altre persone (in rete non si trovano esempi documentati in italiano) vi suggerirei, se lo ritenete possibile, di dedicare un nuovo argomento nella sezione Richieste dei lettori. Detto, fatto! La richiesta è alquanto interessante sia come problema pratico (calcolare il CRC32 di un file) sia come argomento didattico (elaborazione di dati binari). Il CRC, Cyclic (o Circular) Redundancy Check ovvero Controllo a ridondanza ciclica, è un algoritmo di calcolo utilizzato per verificare la correttezza dei dati durante la trasmissione o la rilettura degli stessi; tali algoritmi sono due: il CRC16 che utilizza un valore a 16 bit, quindi una precisione di 2^16, ed il CRC32 che utilizza un valore a 32 bit, quindi una precisione di 2^32. L'algoritmo del CRC si basa sull'analisi dei singoli caratteri del testo da analizzare. Ognuno di questi viene analizzato bit per bit fino alla generazione di un codice di hash rappresentativo del testo analizzato. Il risultato del CRC32 è infatti un numero a 32 bit compreso tra 0 e oltre 2 miliardi.
L'utilizzo di Visual Basic per calcoli binari di questo genere non è proprio ottimale e la scelta di un altro linguaggio quale il C produrrebbe sicuramente risultati migliori. Tralasceremo, pertanto, la pura efficienza del codice e ci concentreremo sullo studio dell'applicazione dell'algoritmo. Si raccomanda la consultazione delle informazioni aggiuntive sui sistemi di numerazione e sull'algebra booleana. Questo articolo tratterà l'algoritmo a 32 bit e si snoda lungo quattro sezioni:
Per comodità abbiamo inserito il codice per il calcolo del CRC32 all'interno di un modulo standard e lasciato nel form soltanto il codice per la lettura del file. Calcolare la tabella utilizzata per il calcolo del CRC32 Il primo passo verso il calcolo del CRC32 consiste nel generare una tabella di 256 elementi, ognuno dei quali contenente un valore a 32 bit. Esiste una particolare procedura per generare questi valori, alquanto complessa da spiegare. Vedremo subito il codice che ci chiarirà le idee:
Poiché l'elaborazione della tabella del CRC32 può richiedere
un certo tempo utilizzeremo una variabile di tipo Boolean (riga
3) che informi la funzione che effettua il calcolo del CRC32 che la tabella
è già stata calcolata e pertanto non ha bisogno di essere
elaborata nuovamente.
La funzione di calcolo della tabella è chiamata InizializzaTabella ed è dichiarata Private per renderla inaccessibile alle altre parti del codice. Sarà la funzione che calcola il CRC ad eseguirla in base al valore della variabile blnTabellaCalcolata. Tralasciando per il momento le altre variabili, quella di nome lngValoreCRC conterrà il valore che assumerà ogni elemento della tabella.
Sarà eseguito un calcolo per ogni elemento della matrice (quindi 256 volte). Il valore iniziale di lngValoreCRC corrisponderà all'indice di ogni elemento della tabella (riga 16). Segue un ciclo che verrà ripetuto 8 volte. Nessuna attenzione al valore della variabile intConta: essa non sarà utilizzata in nessuna parte del codice. Merita invece una particolare attenzione la riga successiva (riga 18):
essa indica l'esecuzione di uno shift a destra di 1 bit senza segno, detto
anche ShiftRight. Per una spiegazione sul concetto del BitShifting
si rimanda alla sezione Articoli - News.
Alla riga 19 viene verificato se il primo bit (quello all'estrema destra) del numero lngValoreCRC - che inizialmente viene posto uguale al valore indicato da intBytes - è impostato su 1.
Nel caso esso lo sia, il valore del numero lngTmpCRC ottenuto alla riga 18 viene mischiato mediante l'operatore XOR con il valore seme (salt) standard indicato dalla costante lngSemeCRC. L'algoritmo standard del CRC32 prevede l'uso del valore di seme 3.988.292.384
corrispondente al valore esadecimale EDB8 8320. Tutti gli algoritmi di
calcolo del CRC32 utilizzano tale valore e per questa ragione producono
sempre lo risultato analizzando il medesimo file. In un'implementazione
propria del CRC32 è possibile utilizzare un valore di seme differente
per generare quindi un valore di CRC32 differente. Tornando all'analisi del codice, se invece il primo bit è uguale a 0 il valore del numero lngTmpCRC non viene mischiato con alcun numero. In entrambi i casi il valore risultante viene asseganto alla variabile lngValoreCRC. Fatto questo il ciclo procede fino all'esecuzione di 8 volte. Quel valore lngValoreCRC che alla riga 16 conteneva l'indice della matrice, dopo la prima esecuzione conterrà un numero differente, risultante dall'operazione di RightShift di 1 bit e l'eventuale mix con il valore seme eseguita per 8 volte consecutive prima di essere assegnato come valore della matrice lngTabellaCRC alla riga 25. Terminata l'elaborazione di tutti i valori della tabella, dopo 256 iterazioni, il valore della variabile blnTabellaCalcolata viene impostato su True per impedire una successiva rielaborazione che richiederebbe ulteriore tempo. La tabella verrà infatti elaborata soltanto una volta. Calcolare il CRC32 di un testo Il procedimento per il calcolo del CRC32 vero e proprio è relativamente semplice e si riassume in un un'unico calcolo rappresentabile in linguaggio C come:
Il calcolo rappresenta 3 operazioni:
Beh... forse non è poi così semplice come sembrava.
La funzione che calcola il CRC prende il nome di (ovvio) CalcolaCRC e richiede un parametro obbligatorio corrispondente alla stringa di cui calcolare il CRC. È possibile passare altri due parametri opzionali: CRCPrecedente indica il valore di CRC ottenuto in precedenti elaborazioni, utile per effettuare elaborazioni parziali di testi di grosse dimensioni; se esso non viene fornito viene assunto il valore di default indicato dalla costante lngDefaultCRC. L'altro parametro opzionale è un valore booleano di nome UltimoCRC che indica se il calcolo del codice CRC è terminato e pertanto può essere applicata l'ultima trasformazione; il valore predefinito è False. All'interno della funzione sono dichiarate 4 variabili: la prima, di nome Carattere, identifica il codice ASCII del carattere in esame nella stringa da elaborare; la seconda, di nome Conta, verrà utilizzata soltanto per effettuare un ciclo su tutti i caratteri della stringa da elaborare; le ultime due variabili: Temp ed IndiceTabellaCRC rappresentano i primi due passaggi descritti nella spiegazione del precedente codice in linguaggio C, ovvero il RightShift di 8 bit ed il mix mediante operatore XOR, operazioni che saranno descritte più avanti.
Innanzitutto prima di procedere con l'elaborazione del CRC32 è fondamentale che la tabella lngTabellaCRC sia elaborata. Se il valore della variabile blnTabellaCalcolata è False la funzione di inizializzazione della tabella non è stata richiamata in elaborazioni precedenti e pertanto l'operazione dovrà essere svolta ora, prima di procedere con il codice. Nel caso che la tabella fosse già stata elaborata in precedenza sarebbe superfluo ed impegnativo elaborarla nuovamente. Il valore iniziale del CRC è dato dal parametro CRCPrecedente fornito oppure ottenuto per default. Segue un ciclo eseguito per ogni singolo carattere della stringa Buffer (riga 38). Al suo interno vengono eseguite le quattro operazioni descritte in precedenza:
Tale passaggio è ripetuto per ogni carattere della stringa di cui calcolare il CRC32.
Prima di concludere la funzione viene effettuato un ultimo controllo:
se il valore del parametro UltimoCRC è True
sarà necessario effettuare un'ultimo aggiustamento al valore del
CRC32 invertendone tutti i bit (riga 44).
Come applicare il CRC32 Vediamo un semplice utilizzo del modulo appena sviluppato effettuando il calcolo del codice CRC32 di un file di testo a scelta dell'utente. È importante considerare che il file scelto dall'utente può essere di qualunque dimensione: è pertanto errato provare a caricarlo interamente in memoria e calcolare il CRC32 in un'unica operazione. Il file pertanto sarà letto a blocchi di 2 KB per volta ed ogni volta sarà calcolato il CRC32 di ogni blocco. La funzione CalcolaCRC infatti permette un calcolo segmentato, mediante l'utilizzo del parametro CRCPrecedente. Il
nostro form si comporrà di tre soli e semplici controlli: una Label
descrittiva di nome lblNomeFile, una TextBox in cui l'utente immetterà
il percorso del file di cui calcolare il CRC32, di nome txtNomeFile
ed un semplice pulsante di nome cmdCalcolaCRC32.
Alla riga 6 viene verificata l'esistenza del file specificato nella casella txtNomeFile; vedi anche l'HowTo dedicato alla verifica dell'esistenza di un file ed alla riga 9 viene appurato che non si tratti di una cartella. Con la certezza che il file esiste, alla riga 12 viene effettuata l'apertura in modalità binaria dello stesso. Vedi anche l'HowTo dedicato alla lettura del contenuto di un file.
La prima operazione da effettuare è l'inizializzazione del valore di CRC32, effettuata passando semplicemente alla funzione CalcolaCRC una stringa vuota (riga 13). Come specificato in precedenza, il contenuto del file verrà letto a blocchi di 2 KB ciascuno; a tale scopo, alla riga 14 viene preparata la variabile Buffer. Segue un ciclo che si ripeterà fino a quando non verrà
letto tutto il file, sempre a blocchi di 2 KB ciascuno. L'unico controllo
necessario riguarda la fine del file (riga 17). Ogni pacchetto del file letto viene inviato alla funzione CalcolaCRC, fornendo anche il valore del CRC precedente. Il terzo parametro indicherà che il pacchetto inviato non è l'ultimo ma si richiedono ulteriori elaborazioni. Il valore di ritorno della funzione CalcolaCRC viene memorizzato nella variabile CRC32 per riutilizzarlo nell'elaborazione del blocco successivo. Infatti CRC32 è sia valore di ritorno che parametro inviato alla funzione.
Alla fine del ciclo di lettura ed elaborazione del CRC32 viene chiuso
il file (riga 20) ed infine effettuata l'ultima elaborazione per
richiedere il valore finale del CRC passato come parametro (riga 21). Ulteriori applicazioni del CRC32 Si consideri sempre il CRC32 come un metodo di verifica dei dati e mai come sistema di protezione contro le modifiche dei dati. L'algoritmo infatti è standard nell'elaborazione; quello che può cambiare è il suo campo di applicazione oppure il valore di seme utilizzato. Possiamo infatti memorizzare nei nostri programmi (e dove lo vedremo subito dopo) il CRC32 di ogni singolo file, oppure valore di singoli segmenti del file in analisi. Quello che si rivela utile controllare sono i dati critici alla corretta elaborazione di un processo. Potrebbe essere utile verificare ad esempio che un certo blocco di dati non sia stato modificato poiché in tal caso il programma fornirebbe dati importanti ma fondamentalmente errati, ad esempio in un file di dati di un bilancio aziendale. Resta da sciogliere un ultimo punto: dove e come memorizzare i valori di CRC32 senza che questi vadano a modificare il contenuto del file la cui elaborazione produrrebbe un CRC32 differente? Le soluzioni più semplici consistono in un file di dati di appoggio. Tutti i CRC32 possono essere scritti al suo interno ed il programma che ne richiede la verifica si occuperà di estrarli e confrontali con il valore CRC32 reale ottenuto dall'elaborazione. Un'altra soluzione potrebbe essere quella di scriverli come file di risorse all'interno di un file di libreria (DLL). Un'ultima soluzione, un po' drastica a dire il vero, consiste nell'aggiungere i dati alla fine del file eseguibile stesso, avendo cura che l'elaborazione del CRC32 di questo escluda quei 4 bytes contenenti il CRC32 stesso. In ragione della struttura dell'implementazione adottata l'algoritmo
di calcolo potrà essere utilizzato per calcolare il valore CRC32
non solo dell'intero file, ma anche per elaborazioni parziali di dati
al fine di velocizzare l'esecuzione di calcolo. |
Qualunque sia la soluzione applicata è fondamentale tenere a mente che il CRC32 non può essere considerato un algoritmo di protezione. Se qualcuno vuole utilizzarlo come tale abbia almeno la minima cura di criptare i dati rappresentativi del valore di CRC. Si tenga anche conto che il presente codice è sviluppato in puro Visual Basic e come tale è soggetto ad una pesante lentezza, in parte legata alla soluzione del codice ed in parte legata ai limiti del linguaggio stesso. Fibia
FBI
|
Torna all'introduzione delle Richieste dei lettori |