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
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)
|