Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Criptazione: algoritmo di Rijndael

In questo articolo parliamo un pò di Crittografia. Non andremo a descrivere nel dettaglio i vari algoritmi ma verranno esposte 2 procedure semplicissime da utilizzare per criptare e decriptare.

1. Breve introduzione sulla criptazione

La criptazione e la successiva decriptazione di una sequenza di byte può essere espressa attraverso i seguenti passaggi:

1) Ho a disposizione una sequenza di byte che voglio andare a criptare: tale sequenza di byte viene detta plaintext

2) Passo il plaintext in input alla procedura di criptazione: la procedura di criptazione usa la chiave di criptazione per criptare il plaintext ed ottenere il cosidetto ciphertext; il ciphertext è la sequenza di byte criptata

3) Per ottenere il plaintext (sequenza di byte originale) a partire dal ciphertext, passo il ciphertext in input alla procedura di decriptazione: la procedura di decriptazione usa la chiave di decriptazione per decriptare il ciphertext ed ottenere il plaintext  

1.1 Algoritmi Simmetrici e Asimmetrici

Gli algoritmi di criptazione si dividono in 2 classi: simmetrici ed asimmetrici; la differenza sta nelle chiavi di criptazione e decriptazione: gli algoritmi simmetrici usano la medesima sequenza di byte per definire sia la chiave di criptazione sia quella di decriptazione mentre gli algoritmi asimmetrici usano valori differenti per le 2 chiavi.

1.2 Algoritmi a Blocchi: metodo BCB e CBC

I più importanti algoritmi di criptazione (sia simmetrici sia asimmetrici) sono algoritmi a Blocchi ossia criptano il messaggio un blocco di byte alla volta: ogni blocco può essere criptato in 2 modi:

1) ECB (Electronic Code Book): blocchi uguali restituiscono lo stesso ciphertext ossia il risultato della criptazione di 2 sequenze di byte identiche è la stessa sequenza di byte per entrambe; dal ciphertext possiamo quindi avere un certo livello di informazione relativamente al plaintext

2) CBC (Cipher Block Chaining): a differenza del metodo ECB, nel metodo CBC la criptazione di un blocco di byte dipende dal risultato della criptazione del blocco precedente (ad esempio il Blocco i viene combinato in XOR col risultato della criptazione del Blocco i-1 ed il risultato viene poi criptato); quindi blocchi identici nel plaintext non restituiscono blocchi identici nel ciphertext: chiaramente c'è il problema che, nel caso in cui si corrompa il contenuto di un blocco del ciphertext, i blocchi successivi non potranno più essere decriptati correttamente (mentre nel metodo ECB questo problema non sussiste in quanto ogni blocco viene criptato in maniera indipendente dal resto). Basandosi sul blocco precedente, si avrà che se ad esempio i primi 13 blocchi del plaintext sono uguali anche i corrispondenti blocchi nel ciphertext saranno uguali; se 2 messaggi sono completamente uguali anche i loro ciphertext saranno identici; 2 messaggi che iniziano con una stessa sequenza di byte produrranno il medesimo ciphertext fino alla prima differenza. Per ovviare a questa problematica, i metodi CBC sono soliti appendere all'inizio del messaggio una sequenza random (quindi diversa tutte le volte) di byte detta comunemente Vettore di Inizializzazione la quale rappresenta il primo blocco del plaintext: in questo modo anche messaggi completamente identici restituiranno ciphertext differenti. 

1.3 Breve elenco di alcuni algoritmi di criptazione

Tra gli algoritmi di criptazione simmetrici abbiamo

  • Blowfish
  • Des
  • Triple-DES
  • Rijndael

Tra quelli asimmetrici ricordiamo

  • RSA

2. Implementazione

Il web è pieno di librerie Open Source in Delphi che implementano tutti gli algoritmi più importanti di criptazione. Un ottimo pacchetto di componenti è TurboPower LockBox: include l'implementazione di tutti gli algoritmi sopra elencati oltre a procedure di hashing e Unit dedicate alla firma digitale. I componenti sono di una semplicità unica e la criptazione di qualsiasi elemento (stringa, stream, file, etc...) è veramente questione di un paio di righe di codice; oltretutto è possibile usare direttamente le procedure di criptazione (o hashing o firma digitale) senza passare per i componenti. Quindi un'ampia gamma di possibilità di utilizzo.

2.1 Algoritmo di Rijndael

L'algoritmo di Rijndael è stato il finalista della competizione Advanced Encryption Standard (AES) istituita dal National Institute of Standards and Technology (NIST) nel 1998. AES va a rimpiazzare il Data Encryption Standard (DES). Gli autori sono i 2 esperti di criptografia Belgi Joan Daemen and Vincent Rijmen. E' un algoritmo simmetrico con chiave di criptazione variabile e dimensione dei blocchi variabile. Nel paper di presentazione, gli autori hanno proposto dimensioni di chiave pari a 128, 192 e 256 bit usando dimensioni di blocco pari a 128, 192 e 256 bit. L'implementazione di TurboPower LockBox supporta tutte e tre le suddette dimensioni di chiave (128, 192, 256 bit) ma consente solo blocchi di dimensione pari a 128 bit. Altra cosa importante da osservare è che, con LockBox, vengono implementati entrambi i metodi di gestione dei blocchi ossia ECB e CBC (vedi paragrafo 1.2 per approfondimenti).

2.2 Mettiamo tutto in una unit

Ho deciso di estrapolare tutto ciò che riguarda l'implementazione dell'algortimo di Rijndael dal pacchetto LockBox: in questo modo ho ottenuto una unica Unit (Rijndael.pas) contenente tutto l'occorrente. A questo punto ho deciso di crearmi una unit in cui andare ad implementare un pò di procedure wrapper. Vediamola:

unit uUtilsCrypto; interface uses Windows, Classes, SysUtils, MMSystem, Rijndael; type TCipherMode = (cmEBC, cmCBC); TProgressProc = procedure(CurPostion, TotalSize: longint); function CryptFile(const FileNameIn: string; const FileNameOut: string; CipherMode: TCipherMode): Boolean; function DecryptFile(const FileNameIn: string; const FileNameOut: string; CipherMode: TCipherMode): Boolean; function EncrytedLog(const LogFileName: string; CipherMode: TCipherMode; val: string): Boolean; function RDLEncryptStreamEBC(InStream, OutStream : TStream; Encrypt : Boolean): Boolean; function RDLEncryptStreamCBC(InStream, OutStream : TStream; Encrypt : Boolean): Boolean; var LbOnProgress : TProgressProc; LbProgressSize: Longint; implementation const Passphrase: array[0..49] of byte = ($E3, $41, $55, $02, $09, $8B, $C1, $FE, $FA, $DE, $E2, $CF, $09, $8B, $C1, $AA, $49, $99, $9F, $50, $8B, $41, $55, $02, $AA, $49, $FE, $28, $8B, $67, $CC, $DD, $4F, $1F, $56, $51, $40, $DD, $E4, $9B, $8B, $C1, $AA, $49, $09, $8B, $C1, $44, $60, $B9); function RDLEncryptStreamEBC(InStream, OutStream : TStream; Encrypt : Boolean): Boolean; var Key : TKey128; I : LongInt; Block : TRDLBlock; Context : TRDLContext; BlockCount : LongInt; begin Result := True; {reset the streams} InStream.Position := 0; OutStream.Position := 0; {create a key} GenerateMD5Key(Key, Passphrase, SizeOf(Passphrase)); {initialize the Rijndael context} InitEncryptRDL(Key, sizeof(Key), Context, Encrypt); {get the number of blocks in the file} BlockCount := (InStream.Size div SizeOf(Block)); {when encrypting, make sure we have a block with at least one free} {byte at the end. used to store the odd byte count value} if Encrypt then Inc(BlockCount); {process all except the last block} for I := 1 to BlockCount - 1 do begin if InStream.Read(Block, SizeOf(Block)) <> SizeOf(Block) then begin Result := False; Exit; end; EncryptRDL(Context, Block); OutStream.Write(Block, SizeOf(Block)); if Assigned(LbOnProgress) then begin if InStream.Position mod LbProgressSize = 0 then begin LbOnProgress(InStream.Position, InStream.Size); end; end; end; if Encrypt then begin FillChar(Block, SizeOf(Block), #0); {set actual number of bytes to read} I := (InStream.Size mod SizeOf(Block)); if InStream.Read(Block, I) <> I then begin Result := False; Exit; end; {store number of bytes as last byte in last block} PByteArray(@Block)^[SizeOf(Block)-1] := I; {encrypt and save full block} EncryptRDL(Context, Block); OutStream.Write(Block, SizeOf(Block)); end else begin {encrypted file is always a multiple of the block size} if InStream.Read(Block, SizeOf(Block)) <> SizeOf(Block) then begin Result := False; Exit; end; EncryptRDL(Context, Block); {get actual number of bytes encoded} I := PByteArray(@Block)^[SizeOf(Block)-1]; {save valid portion of block} OutStream.Write(Block, I); end; if Assigned(LbOnProgress) then begin LbOnProgress (InStream.Position, InStream.Size); end; end; function RDLEncryptStreamCBC(InStream, OutStream : TStream; Encrypt : Boolean): Boolean; var Key : TKey128; I : LongInt; Block : TRDLBlock; IV : TRDLBlock; Work : TRDLBlock; Context : TRDLContext; BlockCount : LongInt; begin Result := True; {reset the streams} InStream.Position := 0; OutStream.Position := 0; {create a key} GenerateMD5Key(Key, Passphrase, SizeOf(Passphrase)); {initialize the Rijndael context} InitEncryptRDL(Key, sizeof(Key), Context, Encrypt); {get the number of blocks in the file} BlockCount := (InStream.Size div SizeOf(Block)); if Encrypt then begin {set up an initialization vector (IV)} Block[0] := timeGetTime; Block[1] := timeGetTime; EncryptRDL(Context, Block); OutStream.Write(Block, SizeOf(Block)); IV := Block; end else begin {read the frist block to prime the IV} InStream.Read(Block, SizeOf(Block)); Dec(BlockCount); IV := Block; end; {when encrypting, make sure we have a block with at least one free} {byte at the end. used to store the odd byte count value} if Encrypt then Inc(BlockCount); {process all except the last block} for I := 1 to BlockCount - 1 do begin if InStream.Read(Block, SizeOf(Block)) <> SizeOf(Block) then begin Result := False; Exit; end; if Encrypt then begin EncryptRDLCBC(Context, IV, Block); IV := Block; end else begin Work := Block; EncryptRDLCBC(Context, IV, Block); IV := Work; end; OutStream.Write(Block, SizeOf(Block)); if Assigned(LbOnProgress) then begin if InStream.Position mod LbProgressSize = 0 then begin LbOnProgress (InStream.Position, InStream.Size); end; end; end; if Encrypt then begin FillChar(Block, SizeOf(Block), #0); {set actual number of bytes to read} I := (InStream.Size mod SizeOf(Block)); if InStream.Read(Block, I) <> I then begin Result := False; Exit; end; {store number of bytes as last byte in last block} PByteArray(@Block)^[SizeOf(Block)-1] := I; {encrypt and save full block} EncryptRDLCBC(Context, IV, Block); OutStream.Write(Block, SizeOf(Block)); end else begin {encrypted file is always a multiple of the block size} if InStream.Read(Block, SizeOf(Block)) <> SizeOf(Block) then begin Result := False; Exit; end; EncryptRDLCBC(Context, IV, Block); {get actual number of bytes encoded} I := PByteArray(@Block)^[SizeOf(Block)-1]; {save valid portion of block} OutStream.Write(Block, I); end; if Assigned(LbOnProgress) then begin LbOnProgress (InStream.Position, InStream.Size); end; end; function CryptFile(const FileNameIn: string; const FileNameOut: string; CipherMode: TCipherMode): Boolean; var InputStream, OutputStream: TFileStream; begin Result := False; try InputStream := TFileStream.Create(FileNameIn, fmOpenRead); OutputStream := TFileStream.Create(FileNameOut, fmCreate or fmOpenWrite); case CipherMode of cmEBC: if not RDLEncryptStreamEBC(InputStream, OutputStream, True) then begin Exit; end; cmCBC: if not RDLEncryptStreamCBC(InputStream, OutputStream, True) then begin Exit; end; end; Result := True; finally InputStream.Free; OutputStream.Free; end; end; function DecryptFile(const FileNameIn: string; const FileNameOut: string; CipherMode: TCipherMode): Boolean; var InputStream, OutputStream: TFileStream; begin Result := False; try InputStream := TFileStream.Create(FileNameIn, fmOpenRead); OutputStream := TFileStream.Create(FileNameOut, fmCreate or fmOpenWrite); case CipherMode of cmEBC: if not RDLEncryptStreamEBC(InputStream, OutputStream, False) then begin Exit; end; cmCBC: if not RDLEncryptStreamCBC(InputStream, OutputStream, False) then begin Exit; end; end; Result := True; finally InputStream.Free; OutputStream.Free; end; end; function EncrytedLog(const LogFileName: string; CipherMode: TCipherMode; val: string): Boolean; var InputStream, OutputStream: TFileStream; TempStream: TMemoryStream; TempStreamSizeBefore, TempStreamSizeAfter: Int64; DataLength: Cardinal; begin Result := True; try TempStream := TMemoryStream.Create(); if FileExists(LogFileName) then begin InputStream := TFileStream.Create(LogFileName, fmOpenRead); case CipherMode of cmEBC: if not RDLEncryptStreamEBC(InputStream, TempStream, False) then begin Result := False; end; cmCBC: if not RDLEncryptStreamCBC(InputStream, TempStream, False) then begin Result := False; end; end; InputStream.Free; if Result = False then begin TempStream.Free; Exit; end; end; DataLength := Length(val); TempStreamSizeBefore := TempStream.Size; TempStreamSizeAfter := TempStreamSizeBefore + DataLength; TempStream.Size := TempStreamSizeAfter; TempStream.Position := TempStreamSizeBefore; TempStream.Write(PAnsiChar(val)^, DataLength); OutputStream := TFileStream.Create(LogFileName, fmCreate); case CipherMode of cmEBC: if not RDLEncryptStreamEBC(TempStream, OutputStream, True) then begin Result := False; end; cmCBC: if not RDLEncryptStreamCBC(TempStream, OutputStream, True) then begin Result := False; end; end; OutputStream.Free; TempStream.Free; finally end; end; end.

Le 2 procedure cardine sono RDLEncryptStreamEBC e RDLEncryptStreamCBC che effettuano la criptazione (parametro encrypt = True) e decriptazione (parametro encrypt = False) di uno stream rispettivamente col metodo EBC e CBC. Poi le 2 funzioni CryptFile e DecryptFile per effettuare rispettivamente la criptazione e la decriptazione di un file (entrambe hanno l'ultimo parametro che definisce il metodo di gestione dei blocchi). La funzione EncryptedLog  consente di salvare dei dati in un file di Log criptato: spesso si vanno a scrivere su file determinate cose durante l'esecuzione del nostro programma (quello che passa comunemente sotto il nome di Log dei dati) e a volte può capitare che questi dati siano informazioni sensibili e che non ci fa piacere che vengano viste da qualche curiosone. Con la funzione EncrytedLog  possono essere scritte sequenze di byte su un file in forma criptatata

In allegato un esempio

crypto.7z

Implementazione con KOL Library e altre modifiche (27/09/2007)

Ho deciso di fare un porting delle procedure definite sopra alla KOL Library e quindi realizzare anche una versione dell'esempio allegato totalmente basata su KOL. Usando la classe TStream definita dalla KOL Library ho notato delle performance scandentissime e così dopo aver riscontrato le stesse scarse performance in un altra implementazione dell'algoritmo di Rijndael sempre KOL oriented (vedi) che usava sempre la classe TStream, ho deciso di riscrivere la unit Classes di Delphi inserendo solo ciò che mi interessava in questo contesto e depurando la classe TStream ivi implementata dei metodi che coinvolgono TComponent etc... Poi ho tolto le parti di codice che contenevano la sollevazione di eccezioni ed aggiunto i requisiti implementati nella unit SysUtils. Il risultato finale è stato un eseguibile di dimensione 52 KB invece di 48 KB (pochissimo in più) in cui le routines di encryption/decryption tornavano finalmente ad essere efficienti come nella originale versione VCL. Di seguito sorgenti ed eseguibile

crypto_kol_src_exe.7z

Data utlimo aggiornamento: 27/09/2007 (aggiunta versione KOL; creata una unit uClasses.pas in cui ho reimplementato le classi derivate da TStream snellendole parecchio: la classe TStream e derivate fornite dalla KOL Library non erano performanti e la unit Classes e SysUtils di Delphi rendono il .exe finale piuttosto grassoccio)

Data aggiornamento: 26/09/2007 (leggere modifiche all' implementazione, ad esempio eliminazione delle eccezioni e trasformazione delle procedure in funzioni; aggiunta della nuova funzione EncryptrdLog che consente di scrivere dati su un File di Log criptato)

 

 
 
Your Ad Here