Crittografia in PL/SQL utilizzando DBMS_CRYPTO

Fin dai tempi di Giulio Cesare la crittografia ha giocato un ruolo importantissimo nella vita dell’uomo, decidendo le grandi guerre e muovendo grandi somme di denaro.
Figuriamoci se Oracle si lasciava scappare l’opportunità di implementare i più importanti algoritmi di cifratura!
Già in Oracle8i era presente il package DBMS_OBFUSCATION_TOOLKIT che implementava il DES, principale algoritmo di cifratura dell’epoca e tuttora utilizzatissimo, anche nella variante più complessa da violare: il Triple DES (o 3-DES).
In quest’ultimo decennio, però, ha ricevuto sempre maggiori consensi l’algoritmo AES, adottato come standard dal governo degli Stati Uniti.
Era quindi necessario implementare anche questo nuovo algoritmo in Oracle e con l’occasione è stato deciso di passare ad un nuovo package di funzionalità, DBMS_CRYPTO presentato per la prima volta nella versione 10R1 del DB.
In quest’articolo mi propongo di illustrare velocemente le principali funzionalità del package e mostrare un esempio funzionante di codice che lo utilizza.

Innanzi voglio dettagliare le funzionalità implementate in DBMS_CRYPTO:

Algoritmi di Crittografia:
Oltre ai già citati DES, Triple DES (in questa versione anche a due ed a tre chiavi) ed AES c’è anche l’RC4.

Funzioni di Hashing:
MD4, MD5, SHA-1.

Funzioni per la generazione di MAC code:
MD5 e SHA-1.

Funzioni per la generazione di valori pseudo-casuali:
E’ possibile generare un valore pseudo-casuale di tipo BINARY_INTEGER, NUMBER o RAW.

Prima di passare ad un esempio di procedura per la cifratura/decifratura voglio specificare che il package DBMS_CRYPTO contiene due procedure (ENCRYPT e DECRYPT) che gestiscono cifratura e decifratura di campi di tipo LOB, sia BLOB che CLOB, e due funzioni (sempre ENCRYPT e DECRYPT) che gestiscono cifratura e decifratura di campi RAW. I campi VARCHAR2 devono essere convertiti in RAW e nel set di caratteri AL32UTF8 prima di essere cifrati.

Cominciamo con una funzione per la cifratura di una stringa varchar2 che ritorna la stringa cifrata, ovviamente in un campo RAW, e riceve in input la stringa da cifrare, la chiave di cifratura ed un flag che indica l’algoritmo da utilizzare: A=AES con chiave a 32 byte, D=3DES con chiave a 24 byte.

CREATE OR REPLACE function CIFRA(in_str in VARCHAR2, 
                                 chiave in varchar2, 
                                 Algoritmo in char) 
                  return RAW is
 raw_da_cifrare RAW(2048) := 
     UTL_RAW.CAST_TO_RAW(CONVERT(in_str,'AL32UTF8','WE8MSWIN1252'));
 raw_chiave     RAW(128) := 
     UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
 alg            PLS_INTEGER;
BEGIN
 if Algoritmo = 'D' then
    alg := DBMS_CRYPTO.DES3_CBC_PKCS5;
 elsif Algoritmo = 'A' then
    alg := DBMS_CRYPTO.ENCRYPT_AES256 + 
           DBMS_CRYPTO.CHAIN_CBC + 
           DBMS_CRYPTO.PAD_PKCS5;
 else
    RAISE_APPLICATION_ERROR(-20001,
                 'Algoritmo non previsto, scegliere D o A!');
 end if;
 return dbms_crypto.Encrypt(raw_da_cifrare, alg, raw_chiave);
END;
/

La funzione ENCRYPT prende in input i seguenti parametri:

  • Stringa di byte da cifrare
  • Una stringa di massimo 2048 byte da cifrare, prima di essere cifrata viene convertita in RAW e nel set di caratteri AL32UTF8.

  • Algoritmo da utilizzare
  • In realtà non bisogna solo specificare un algoritmo, ma una terna formata da algoritmo, modalità di chaining (ECB, CBC, CFB, OFB) e criterio di riempimento dei blocchi per raggiungere la lunghezza standard (PKCS5, nessun riempimento, riempimento a zeri).
    Ogni algoritmo di cifratura è rappresentato da una costante e lo stesso vale per ogni modalità di chaining e per ogni criterio di padding.
    Il valore del parametro da passare alla funzione di cifratura, dunque, è la somma di tre valori scelti tra i suddetti.
    Nell’esempio per l’AES questo è chiaro, ho scelto ENCRYPT_AES256 come algoritmo di cifratura, CHAIN_CBC come modalità di chaining e PAD_PKCS5 come criterio di padding.
    Per il DES la somma delle tre componenti scelte (ENCRYPT_3DES + CHAIN_CBC + PAD_PKCS5) è sintetizzata con l’unica costante DES3_CBC_PKCS5 che è già predefinita nel package.

  • Chiave di cifratura
  • Anche la chiave di cifratura deve essere convertita in RAW e nel set di caratteri AL32UTF8. Bisogna fare attenzione alla lunghezza della chiave.
    La lunghezza minima dipende dall’algoritmo utilizzato, nel nostro caso è 24 byte per il 3DES e 32 per l’AES.

  • Vettore di inizializzazione per cifrari a blocchi (opzionale)
  • Noi non lo passiamo.

    Ed ecco come cifriamo un testo utilizzando la funzione appena definita:

    
    SQL> select cifra('Questo è un testo molto lungo da
      2  cifrare, contiene anche ritorni a capo etc...',
      3  'MIACHIAVE012345678901234','D') cifrato
      4  from dual;
    
    CIFRATO
    ----------------------------------------
    812DAC49DBEBD0619CEC10E4341FDA9DCC435007
    1EB29B3B0F62AA8D95B1AA5996BA75ABB1E4AB91
    FEA914480892D248A5D92E5642559135CD15DD50
    6D878D5E6B17355800729310C0675B33B7546C12
    
    

    passiamo alla decifratura:

    
    CREATE OR REPLACE function DECIFRA(in_raw in RAW, 
                                     chiave in varchar2, 
                                     Algoritmo in char) 
                      return VARCHAR2 is
     raw_chiave     RAW(128) := 
        UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
     raw_decifrata  RAW(2048);
     alg            PLS_INTEGER;
    BEGIN
     if Algoritmo = 'D' then
        alg := DBMS_CRYPTO.DES3_CBC_PKCS5;
     elsif Algoritmo = 'A' then
        alg := DBMS_CRYPTO.ENCRYPT_AES256 + 
               DBMS_CRYPTO.CHAIN_CBC + 
               DBMS_CRYPTO.PAD_PKCS5;
     else
        RAISE_APPLICATION_ERROR(-20001,
                     'Algoritmo non previsto, scegliere D o A!');
     end if;
    
     raw_decifrata := dbms_crypto.Decrypt(in_raw, alg, raw_chiave);
     return CONVERT(UTL_RAW.CAST_TO_VARCHAR2(raw_decifrata),'WE8MSWIN1252','AL32UTF8');
    END;
    /
    
    

    Dove banalmente rispetto a prima abbiamo chiamato la funzione per decifrare e poi abbiamo riconvertito la RAW ottenuta in VARCHAR2 nel nostro set di caratteri WE8MSWIN1252.

    Facciamo la prova del nove, proviamo a decifrare la stringa che prima avevamo cifrato:

    SQL> r
      1  select decifra(hextoraw('DC84F4C1CA98FC27208C3A501
         3F0A88720DCCE5E5C1E250ADD38352404F3CB73E6C4A97DE64
         E999997B43F5531EAE824D6A2EA7AC5EE78BF5DBC3CCB86CC1
         31B89CBADD7295D71DBE57B7CDC1DFE3787'),
         'MIACHIAVE012345678901234','D') decifrato
      2* from dual
    
    DECIFRATO
    --------------------------------------------------
    Questo è un testo molto lungo da
    cifrare, contiene anche ritorni a capo etc...
    

    Attenzione: ho aggiunto alcuni ritorni a capo nell’esadecimale da decodificare per una questione di leggibilità. Quando ho eseguito l’istruzione sul DB i ritorni a capo non c’erano!
    Faccio notare che nel chiamare la funzione ho dovuto utilizzare la funzione di conversione HEXTORAW per trasformare la stringa esadecimale in una stringa binaria.
    Nell’esempio della cifratura, la conversione opposta RAWTOHEX era stata eseguita automaticamente da SQL*Plus all’atto di visualizzare i risultati.

    Gli esempi precedenti utilizzavano il 3DES, facciamo anche una prova con l’AES:

    
    SQL> select cifra('Testo da cifrare con AES',
      2  'CHIAVEDA32BYTE00CHIAVEDA32BYTE00','A') cifrato
      3  from dual;
    
    CIFRATO
    ----------------------------------------
    84FB4135CD3FAF07AF7B729ED2A0FD7BF306A5AA
    6923B39E81D27CA693D5E343
    
    
    SQL> select decifra(HEXTORAW('84FB4135CD3FAF07AF7B729ED2A0FD
    7BF306A5AA6923B39E81D27CA693D5E343'),
      2  'CHIAVEDA32BYTE00CHIAVEDA32BYTE00','A') decifrato
      3  from dual;
    
    DECIFRATO
    --------------------------------------------------
    Testo da cifrare con AES
    
    

    Come cambiano le cose se devo lavorare su campi BLOB e CLOB? Come sempre quando si maneggiano i LOB si lavora su riferimenti ad oggetti del DB.
    Quindi bisogna prima di tutto disporre di una tabella che contenga i BLOB da cifrare e cifrati. Io ho una tabella MYFILES che contiene una canzone degli U2 ed un episodio di FlashForward:

    SQL> select * from myfiles
    
            ID NAME                 CONTENT
    ---------- -------------------- --------------------
             1 WithOrWithoutYou.mp3 49443303000000000F76
                                    47454F42000006A60000
                                    0062696E617279000052
                                    65616C4A756B65626F78
                                    3A4D6574616461746100
                                    524A4D44000000010000
                                    06800000000000000000
                                    0000001D000000220000
    
             2 FlashForward.avi     5249464632B9DF154156
                                    49204C4953547E220000
                                    6864726C617669683800
                                    0000409C000000000000
                                    000000001001000072ED
                                    00000000000002000000
                                    00000000700200006001
                                    00000000000000000000
    

    Mi propongo di creare una funzione che, ricevendo in input l’ID del file da cifrare, crei un nuovo record con il file cifrato e restituisca il nuovo ID.

    
    create or replace function cifra_file(in_id in number, 
                                          chiave in varchar2, 
                                          Algoritmo in char) 
                      return number is
     blob_da_cifrare BLOB;
     blob_cifrato    BLOB;
     raw_chiave      RAW(128) := 
         UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
     alg             PLS_INTEGER;
     out_id          number;
    begin
        
     if Algoritmo = 'D' then
        alg := DBMS_CRYPTO.DES3_CBC_PKCS5;
     elsif Algoritmo = 'A' then
        alg := DBMS_CRYPTO.ENCRYPT_AES256 + 
               DBMS_CRYPTO.CHAIN_CBC + 
               DBMS_CRYPTO.PAD_PKCS5;
     else
        RAISE_APPLICATION_ERROR(-20001,
                     'Algoritmo non previsto, scegliere D o A!');
     end if;
    
     out_id := fileid.nextval;
    
     -- inserisco un record che conterrà il blob cifrato:
     insert into myfiles (id, name, content)
     select out_id, name||' - cifrato', empty_blob()
       from myfiles
      where id = in_id;
      
    
     select content into blob_da_cifrare 
       from myfiles where id = in_id;
    
     select content into blob_cifrato
       from myfiles where id = out_id;
    
     DBMS_CRYPTO.Encrypt(blob_cifrato,blob_da_cifrare,alg,raw_chiave);
    
     return out_id;
        
    end;
    /
    
    

    Rispetto a prima c’è pochissimo di nuovo, giusto la gestione dei puntatori a BLOB ed il fatto che chiamiamo la procedura ENCRYPT anziché la funzione.

    Ed ecco un esempio di utilizzo, ovviamente non posso chiamare la funzione da SQL perché inserisce un record nel DB, quindi la chiamo con una EXEC e stampo a video l’ID che ritorna.

    
    SQL> exec dbms_output.put_line(cifra_file(1,
              'MIACHIAVE012345678901234','D'))
    3
    
    Procedura PL/SQL completata correttamente.
    
    SQL> select * from myfiles;
    
            ID NAME                 CONTENT
    ---------- -------------------- --------------------
             1 WithOrWithoutYou.mp3 49443303000000000F76
                                    47454F42000006A60000
                                    0062696E617279000052
                                    65616C4A756B65626F78
                                    3A4D6574616461746100
                                    524A4D44000000010000
                                    06800000000000000000
                                    0000001D000000220000
    
             2 FlashForward.avi     5249464632B9DF154156
                                    49204C4953547E220000
                                    6864726C617669683800
                                    0000409C000000000000
                                    000000001001000072ED
                                    00000000000002000000
                                    00000000700200006001
                                    00000000000000000000
    
             3 WithOrWithoutYou.mp3 9243E49958296FFC341B
                - cifrato           1120021A70A6006A285A
                                    CE89D6F62EA04187E59B
                                    6523DC6336B9FDDDC820
                                    37DDCF8FDCA6FF1E0681
                                    507A44F117F94F2DE517
                                    8298B6F39FE6FA710003
                                    8BE3C3F80F43AABF0200
    
    
    

    Ed ora la funzione per decifrare un blob già presente in tabella.
    E’ quasi identica alla precedente…

    
    create or replace function decifra_file(in_id in number, 
                                          chiave in varchar2, 
                                          Algoritmo in char) 
                      return number is
     blob_da_decifrare BLOB;
     blob_decifrato    BLOB;
     raw_chiave      RAW(128) := 
         UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
     alg             PLS_INTEGER;
     out_id          number;
    begin
        
     if Algoritmo = 'D' then
        alg := DBMS_CRYPTO.DES3_CBC_PKCS5;
     elsif Algoritmo = 'A' then
        alg := DBMS_CRYPTO.ENCRYPT_AES256 + 
               DBMS_CRYPTO.CHAIN_CBC + 
               DBMS_CRYPTO.PAD_PKCS5;
     else
        RAISE_APPLICATION_ERROR(-20001,
                     'Algoritmo non previsto, scegliere D o A!');
     end if;
    
     out_id := fileid.nextval;
    
     -- inserisco un record che conterrà il blob decifrato:
     insert into myfiles (id, name, content)
     select out_id, name||' - decifrato', empty_blob()
       from myfiles
      where id = in_id;
      
    
     select content into blob_da_decifrare 
       from myfiles where id = in_id;
    
     select content into blob_decifrato
       from myfiles where id = out_id;
    
     DBMS_CRYPTO.Decrypt(blob_decifrato,blob_da_decifrare,alg,raw_chiave);
    
     return out_id;
        
    end;
    /
    
    

    Ed ecco un test di decifratura:

    
    SQL> exec dbms_output.put_line(decifra_file(3,
              'MIACHIAVE012345678901234','D'))
    4
    
    Procedura PL/SQL completata correttamente.
    
    SQL> select * from myfiles;
    
            ID NAME                 CONTENT
    ---------- -------------------- --------------------
             1 WithOrWithoutYou.mp3 49443303000000000F76
                                    47454F42000006A60000
                                    0062696E617279000052
                                    65616C4A756B65626F78
                                    3A4D6574616461746100
                                    524A4D44000000010000
                                    06800000000000000000
                                    0000001D000000220000
    
             2 FlashForward.avi     5249464632B9DF154156
                                    49204C4953547E220000
                                    6864726C617669683800
                                    0000409C000000000000
                                    000000001001000072ED
                                    00000000000002000000
                                    00000000700200006001
                                    00000000000000000000
    
             3 WithOrWithoutYou.mp3 9243E49958296FFC341B
                - cifrato           1120021A70A6006A285A
                                    CE89D6F62EA04187E59B
                                    6523DC6336B9FDDDC820
                                    37DDCF8FDCA6FF1E0681
                                    507A44F117F94F2DE517
                                    8298B6F39FE6FA710003
                                    8BE3C3F80F43AABF0200
    
             4 WithOrWithoutYou.mp3 49443303000000000F76
                - cifrato - decifra 47454F42000006A60000
               to                   0062696E617279000052
                                    65616C4A756B65626F78
                                    3A4D6574616461746100
                                    524A4D44000000010000
                                    06800000000000000000
                                    0000001D000000220000
    
    
    

    Il record numero 4 è identico all’originale numero 1 ed è stato ottenuto prima cifrando e poi decifrando l’originale.

    Alla prossima,
    Massimo

    Tag: , , ,

    Lascia un commento