Uso della crittografia con i database SQL

Adobe AIR 1.5 e versioni successive

Tutte le applicazioni Adobe AIR condividono lo stesso motore di database locale. Di conseguenza, qualsiasi applicazione AIR può connettersi, leggere e scrivere in un file di database non crittografato. A partire da Adobe AIR 1.5, in AIR è inclusa la capacità di creare file di database crittografati e di connettersi a tali file. Quando usate un database crittografato, per connettersi a tale database un'applicazione deve fornire la chiave di crittografia corretta. Se viene fornita la chiave di crittografica errata (o nessuna chiave), l'applicazione non può connettersi al database. Di conseguenza, l'applicazione non è in grado di leggere i dati dal database oppure di scrivere o modificare i dati nel database.

Per usare un database crittografato, dovete crearlo come database crittografato. Per un database crittografato esistente, potete aprire una connessione a tale database. Potete anche modificare la chiave di crittografia di un database crittografato. A parte le operazioni relative alla creazione e connessione a un database crittografato, le tecniche per l'utilizzo di questo tipo di database sono le stesse che usate per un database non crittografato. In particolare, l'esecuzione delle istruzioni SQL è uguale per i database crittografati e non crittografati.

Vari usi di un database crittografato

La crittografia è utile ogni volta che desiderate limitare l'accesso alle informazioni archiviate in un database. La funzionalità di crittografia del database inclusa in Adobe AIR può essere usata per vari scopi. Di seguito sono riportati alcuni esempi di casi in cui è opportuno usare un database crittografato:

  • Cache di sola lettura di dati privati di un'applicazione scaricati da un server

  • Archivio dell'applicazione locale per dati privati che vengono sincronizzati con un server (i dati vengono inviati e caricati sul server)

  • File crittografati usati come formato di file per i documenti creati e modificati dall'applicazione. I file possono essere riservati a un solo utente o progettati per la condivisione tra tutti gli utenti dell'applicazione.

  • Qualsiasi altro uso di un archivio dati locale, come quelli descritti in Vari usi dei database SQL locali , in cui i dati devono essere mantenuti privati e salvaguardati dagli utenti che hanno accesso al computer o ai file di database.

L'individuazione del motivo per cui è opportuno utilizzare un database crittografato vi permetterà di decidere più facilmente come progettare l'architettura di un'applicazione. In particolare, può incidere sul modo in cui l'applicazione crea, ottiene o archivia la chiave di crittografia per il database. Per ulteriori informazioni su tali considerazioni, vedete Considerazioni sull'uso della crittografia con un database .

Oltre a un database crittografato, un meccanismo alternativo per mantenere privati i dati riservati consiste nell'usare un archivio locale crittografato . In un archivio locale crittografato, potete archiviare un singolo valore ByteArray usando una chiave in formato stringa. Solo l'applicazione AIR che memorizza il valore può accedervi e solo nel computer in cui il valore è memorizzato. Con l'archivio locale crittografato non è necessario creare una chiave di crittografia personalizzata. Per questi motivi, un archivio locale crittografato è più adatto per archiviare in modo semplice un singolo valore o un set di valori che possono essere facilmente codificati in un ByteArray. Un database crittografato è invece più adatto per set di dati estesi in cui è preferibile disporre di funzioni strutturate per l'esecuzione di query e la memorizzazione dei dati. Per ulteriori informazioni sull'uso di un archivio locale crittografato, vedete Archiviazione locale crittografata .

Creazione di un database crittografato

Per utilizzare un database crittografato, è necessario crittografare il file di database al momento della creazione. Dopo avere creato un database non crittografato, non è possibile crittografarlo in seguito. In modo analogo, un database crittografato non può essere decrittografato in seguito. Se necessario, potete modificare la chiave di crittografia di un database crittografato. Per ulteriori dettagli, consultate Modifica della chiave di crittografia di un database . Se è presente un database non crittografato e desiderate usare la crittografia in quel database, potete creare un nuovo database crittografato e copiare i dati e la struttura delle tabelle esistenti nel nuovo database.

La creazione di un database crittografato è pressoché identica a quella di un database non crittografato, come descritto in Creazione di un database . Create innanzitutto un'istanza SQLConnection che rappresenta la connessione al database. Create il database chiamando il metodo open() o openAsync() dell'oggetto SQLConnection, specificando come posizione del database un file ancora inesistente. La sola differenza quando create un database crittografato consiste nel fornire un valore per il parametro encryptionKey (il quinto parametro del metodo open() e il sesto parametro del metodo openAsync() ).

Un valore valido per il parametro encryptionKey è un oggetto ByteArray contenente esattamente 16 byte.

Gli esempi seguenti mostrano la creazione di un database crittografato. Per semplicità, in questo esempio la chiave di crittografia è codificato nel codice dell'applicazione. Questa tecnica è tuttavia sconsigliata, non essendo sicura.

var conn:SQLConnection = new SQLConnection(); 
     
var encryptionKey:ByteArray = new ByteArray(); 
encryptionKey.writeUTFBytes("Some16ByteString"); // This technique is not secure! 
     
// Create an encrypted database in asynchronous mode 
conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey); 
     
// Create an encrypted database in synchronous mode 
conn.open(dbFile, SQLMode.CREATE, false, 1024, encryptionKey);

Per un esempio che illustra un metodo consigliato per generare una chiave di crittografia, consultate Esempio: generazione e uso di una chiave di crittografia. .

Connessione a un database crittografato

Come per la creazione di un database crittografato, la procedura per aprire una connessione a un database crittografato è simile a quella per un database non crittografato. Tale procedura è descritta più dettagliatamente in Connessione a un database . Utilizzate il metodo open() per aprire una connessione in modalità di esecuzione sincrona oppure il metodo openAsync() per aprire una connessione in modalità di esecuzione asincrona . La sola differenza è data dal fatto che quando aprite un database crittografato dovete specificare il valore corretto per il parametro encryptionKey (il quinto parametro del metodo open() e il sesto parametro del metodo openAsync() ).

Se la chiave di crittografia specificata non è corretta, si verifica un errore. Per il metodo open() , viene generata un'eccezione SQLError . Per il metodo openAsync() , l'oggetto SQLConnection invia un evento SQLErrorEvent , la cui proprietà error contiene un oggetto SQLError . In entrambi i casi, l'oggetto SQLError generato dall'eccezione presenta il valore 3138 della proprietà errorID . Questo ID errore corrisponde al messaggio di errore “File opened is not a database file” (Il file aperto non è un file di database).

Nell'esempio riportato di seguito viene illustrata l'apertura di un database crittografato in modalità di esecuzione asincrona. Per semplicità, in questo esempio la chiave di crittografia è hardcoded nel codice dell'applicazione. Questa tecnica è tuttavia sconsigliata, non essendo sicura.

import flash.data.SQLConnection; 
import flash.data.SQLMode; 
import flash.events.SQLErrorEvent; 
import flash.events.SQLEvent; 
import flash.filesystem.File; 
     
var conn:SQLConnection = new SQLConnection(); 
conn.addEventListener(SQLEvent.OPEN, openHandler); 
conn.addEventListener(SQLErrorEvent.ERROR, errorHandler); 
var dbFile:File = File.applicationStorageDirectory.resolvePath("DBSample.db"); 
     
var encryptionKey:ByteArray = new ByteArray(); 
encryptionKey.writeUTFBytes("Some16ByteString"); // This technique is not secure! 
     
conn.openAsync(dbFile, SQLMode.UPDATE, null, false, 1024, encryptionKey); 
     
function openHandler(event:SQLEvent):void 
{ 
    trace("the database opened successfully"); 
} 
     
function errorHandler(event:SQLErrorEvent):void 
{ 
    if (event.error.errorID == 3138) 
    { 
        trace("Incorrect encryption key"); 
    } 
    else 
    { 
        trace("Error message:", event.error.message); 
        trace("Details:", event.error.details); 
    } 
} 

Nell'esempio riportato di seguito viene illustrata l'apertura di un database crittografato in modalità di esecuzione sincrona. Per semplicità, in questo esempio la chiave di crittografia è hardcoded nel codice dell'applicazione. Questa tecnica è tuttavia sconsigliata, non essendo sicura.

import flash.data.SQLConnection; 
import flash.data.SQLMode; 
import flash.filesystem.File; 
     
var conn:SQLConnection = new SQLConnection(); 
var dbFile:File = File.applicationStorageDirectory.resolvePath("DBSample.db"); 
     
var encryptionKey:ByteArray = new ByteArray(); 
encryptionKey.writeUTFBytes("Some16ByteString"); // This technique is not secure! 
     
try 
{ 
    conn.open(dbFile, SQLMode.UPDATE, false, 1024, encryptionKey); 
    trace("the database was created successfully"); 
} 
catch (error:SQLError) 
{ 
    if (error.errorID == 3138) 
    { 
        trace("Incorrect encryption key"); 
    } 
    else 
    { 
        trace("Error message:", error.message); 
        trace("Details:", error.details); 
    } 
} 

Per un esempio che illustra un metodo consigliato per generare una chiave di crittografia, consultate Esempio: generazione e uso di una chiave di crittografia. .

Modifica della chiave di crittografia di un database

Quando un database è crittografato, potete modificare la relativa chiave di crittografia in un secondo tempo. Per modificare la chiave di crittografia di un database, aprite innanzitutto la connessione al database creando un'istanza SQLConnection e chiamando il relativo metodo open() o openAsync() . Una volta aperta la connessione al database, chiamate il metodo reencrypt() passando la nuova chiave di crittografia come argomento.

Come per tutte le operazioni relative al database, il comportamento del metodo reencrypt() varia a seconda che la connessione al database utilizzi la modalità di esecuzione sincrona o asincrona. Se usate il metodo open() per connettervi al database, l'operazione reencrypt() viene eseguita in modo sincrono. Al termine dell'operazione, l'esecuzione prosegue con la riga successiva del codice:

var newKey:ByteArray = new ByteArray(); 
// ... generate the new key and store it in newKey 
conn.reencrypt(newKey);

D'altra parte, se la connessione al database viene aperta tramite il metodo openAsync() , l'operazione reencrypt() viene eseguita in modo asincrono. Se chiamate il metodo reencrypt() , viene iniziato di nuovo il processo di crittografia. Al termine dell'operazione, l'oggetto SQLConnection invia un evento reencrypt . Per determinare quando termina il processo, potete usare un listener di eventi:

var newKey:ByteArray = new ByteArray(); 
// ... generate the new key and store it in newKey 
     
conn.addEventListener(SQLEvent.REENCRYPT, reencryptHandler); 
     
conn.reencrypt(newKey); 
     
function reencryptHandler(event:SQLEvent):void 
{ 
    // save the fact that the key changed 
}

L'operazione reencrypt() viene eseguita in una transazione specifica. Se l'operazione viene interrotta o non riesce (ad esempio, se l'applicazione viene chiusa prima che termini l'operazione) la transazione viene annullata. In tal caso, la chiave di crittografia originale sarà ancora quella valida per il database.

Non potete usare il metodo reencrypt() per rimuovere la crittografia da un database. Se passate un valore null o una chiave di crittografia che non è un ByteArray di 16 byte, il metodo reencrypt() restituisce un errore.

Considerazioni sull'uso della crittografia con un database

Nella sezione Vari usi di un database crittografato vengono illustrati diversi casi in cui è opportuno usare un database crittografato. È evidente che gli scenari di impiego delle diverse applicazioni (inclusi questi e altri) presentano requisiti diversi per quanto riguarda la riservatezza. Il modo in cui progettate l'uso della crittografia nella vostra applicazione svolge un ruolo importante nel controllo del grado di riservatezza dei dati di un database. Se, ad esempio, utilizzate un database crittografato per proteggere i dati personali, anche dagli utenti dello stesso computer, dovete creare una chiave di crittografia per il database di ciascun utente. Per maggiore sicurezza, l'applicazione può generare la chiave dalla password immessa da un utente. La chiave di crittografia basata su una password assicura che qualora un'altra persona fosse in grado di rappresentare l'account di un utente del computer, non potrà comunque accedere ai dati. Da un altro punto di vista della riservatezza, supponete di voler rendere un database leggibile da parte di qualsiasi utente della vostra applicazione, ma non da altre applicazioni. In tal caso, ogni copia installata dell'applicazione deve poter accedere a una chiave di crittografia condivisa.

Potete progettare la vostra applicazione, e in particolare la tecnica utilizzata per generare la chiave di crittografia, in base al livello di riservatezza che desiderate per i dati dell'applicazione. Di seguito sono riportati alcuni suggerimenti per la progettazione dei vari livelli di riservatezza dei dati:

  • Per rendere il database accessibile a tutti gli utenti che accedono all'applicazione su qualsiasi computer, usate un'unica chiave che sia disponibile per tutte le istanze dell'applicazione. La prima volta che l'applicazione viene eseguita, può ad esempio scaricare la chiave di crittografia condivisa da un server usando un protocollo sicuro, come SSL. La chiave può quindi essere salvata nell'archivio locale crittografato per l'uso futuro. In alternativa, crittografate i dati per ogni utente sul computer e sincronizzateli con un archivio dati remoto, ad esempio un server, in modo da consentire la portabilità dei dati.

  • Per rendere un database accessibile a un singolo utente su qualsiasi computer, generate la chiave di crittografia da un dato segreto dell'utente (ad esempio una password). In particolare, per generare la chiave non usate un valore qualsiasi collegato a un computer specifico (ad esempio un calore memorizzato nell'archivio locale crittografato. In alternativa, crittografate i dati per ogni utente sul computer e sincronizzateli con un archivio dati remoto, ad esempio un server, in modo da consentire la portabilità dei dati.

  • Per rendere un database accessibile solo a un utente su un solo computer, generate la chiave di crittografia da una password e da un valore salt generato. Per un esempio di questa tecnica, consultate Esempio: generazione e uso di una chiave di crittografia. .

Di seguito sono riportate alcune importanti considerazioni aggiuntive da tenere presenti durante la progettazione di un'applicazione che utilizza un database crittografato:

  • La sicurezza di un sistema è direttamente proporzionale alla sicurezza suo collegamento più debole. Se usate una password immessa dall'utente per generare una chiave di crittografia, considerate la possibilità di imporre una lunghezza minima e delle restrizioni relative alla complessità delle password. Una password breve che utilizza solo caratteri di base può essere individuata velocemente.

  • Il codice sorgente di un'applicazione AIR viene archiviato sul computer di un utente in formato testo semplice (per il contenuto HTML) o in un formato binario facilmente decompilabile (per il contenuto SWF). Poiché il codice è accessibile, occorre considerare due punti:

    • Non usate mai una chiave hardcoded nel codice sorgente

    • Partite sempre dal presupposto che la tecnica usata per generare una chiave di crittografia (ad esempio un generatore di caratteri casuali o un particolare algoritmo di hash) può essere facilmente individuata da un utente malintenzionato

  • Nella crittografia dei database AIR viene utilizzato AES (Advanced Encryption Standard) in modalità Counter con CCM (CBC-MAC). Questa modalità di crittografia richiede la combinazione di una chiave immessa dall'utente con un valore salt per essere sicura. Per un esempio di questa modalità, consultate Esempio: generazione e uso di una chiave di crittografia. .

  • Quando decidete di crittografare un database, tutti i file su disco utilizzati dal motore di database assieme al database vengono crittografati. Il motore di database, tuttavia, mantiene alcuni dati temporaneamente in una cache in memoria per migliorare le prestazioni dei tempi di lettura e scrittura delle transazioni. Tutti i dati residenti in memoria non sono crittografati. Se un utente malintenzionato riesce ad accedere alla memoria utilizzata da un'applicazione AIR, ad esempio tramite un debugger, i dati in un database attualmente aperto e non crittografato diventano disponibili.

Esempio: generazione e uso di una chiave di crittografia.

Questa applicazione di esempio dimostra una tecnica per la generazione di una chiave di crittografia. Questa applicazione è progettata per fornire il massimo livello di riservatezza e sicurezza per i dati dell'utente. Un aspetto importante relativo alla protezione dei dati privati è rappresentato dalla necessità di chiedere all'utente di immettere una password a ogni connessione dell'applicazione al database. Di conseguenza, come illustrato nell'esempio, un'applicazione che richiede questo livello di privacy non deve mai memorizzare direttamente la chiave di crittografia del database.

L'applicazione è composta da due parti: una classe ActionScript che genera una chiave di crittografia (la classe EncryptionKeyGenerator) e un'interfaccia utente di base che dimostra come utilizzare la classe. Per il codice sorgente completo, consultate Esempio di codice completo per la generazione e l'uso di una chiave di crittografia .

Uso della classe EncryptionKeyGenerator per ottenere una chiave di crittografia sicura

Non è necessario comprendere i dettagli del funzionamento della classe EncryptionKeyGenerator nell'applicazione. Se desiderate maggiori informazioni sulla generazione di una chiave di crittografia per un database in una classe, vedete Nozioni fondamentali sulla classe EncryptionKeyGenerator .

Per utilizzare la classe EncryptionKeyGenerator nell'applicazione, eseguite le operazioni riportate di seguito:

  1. Scaricate la classe EncryptionKeyGenerator come codice sorgente o SWC compilato. La classe EncryptionKeyGenerator è inclusa nel progetto della libreria di base ActionScript 3.0 open source (as3corelib). Potete scaricare il pacchetto as3corelib che comprende il codice sorgente e la documentazione . Potete inoltre scaricare il SWC o i file del codice sorgente dalla pagina del progetto.

  2. Inserite il codice sorgente della classe EncryptionKeyGenerator (o il file SWC di as3corelib) in un percorso accessibile da parte del codice sorgente dell'applicazione.

  3. Nel codice sorgente dell'applicazione aggiungete un'istruzione import per la classe EncryptionKeyGenerator.

    import com.adobe.air.crypto.EncryptionKeyGenerator;
  4. Prima del punto in cui il codice crea il database o apre una connessione ad esso, aggiungete codice per creare un'istanza di EncryptionKeyGenerator chiamando il costruttore EncryptionKeyGenerator() .

    var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator();
  5. Ottenete una password dall'utente:

    var password:String = passwordInput.text; 
     
    if (!keyGenerator.validateStrongPassword(password)) 
    { 
        // display an error message 
        return; 
    }

    L'istanza di EncryptionKeyGenerator utilizza questa password come base per la chiave di crittografia (mostrata nel passaggio successivo). L'istanza di EncryptionKeyGenerator testa la password in base a determinati requisiti di convalida per le password avanzate. Se la convalida non riesce, si verifica un errore. Come mostrato dal codice di esempio, potete verificare la password a priori chiamando il metodo validateStrongPassword() dell'oggetto EncryptionKeyGenerator. In questo modo potete controllare se la password soddisfa i requisiti minimi delle password avanzate, evitando un errore.

  6. Generate la chiave di crittografia dalla password:

    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password);

    Il metodo getEncryptionKey() genera e restituisce la chiave di crittografia (un ByteArray a 16 byte). A questo punto potete utilizzare la chiave di crittografia per creare il nuovo database crittografato o aprire un database esistente.

    Il metodo getEncryptionKey() richiede un parametro obbligatorio, ovvero la password ottenuta al passaggio 5.

    Nota: per garantire il livello massimo di sicurezza e riservatezza dei dati, un'applicazione deve richiedere all'utente di immettere una password per ogni connessione al database. Non memorizzate direttamente la password dell'utente o la chiave di crittografia del database per evitare rischi per la sicurezza. Come dimostrato in questo esempio, è consigliabile che un'applicazione utilizzi la stessa tecnica per derivare la chiave di crittografia dalla password sia per la creazione del database crittografato, che per la successiva connessione ad esso.

    Il metodo getEncryptionKey() accetta anche un secondo parametro facoltativo, ovvero overrideSaltELSKey . EncryptionKeyGenerator crea un valore casuale (detto salt ) che viene utilizzato come parte della chiave di crittografia. Per poter ricreare la chiave di crittografia, il valore salt viene memorizzato nell'archivio locale crittografato (ELS, Encrypted Local Store) dell'applicazione AIR. Per impostazione predefinita, la classe EncryptionKeyGenerator utilizza una determinata stringa come chiave ELS. Sebbene sia improbabile, potrebbe presentarsi un conflitto tra la chiave e un'altra chiave utilizzata dall'applicazione. Anziché utilizzare la chiave predefinita, è consigliabile specificare una chiave ELS personalizzata. Per specificare una chiave personalizzata, passatela come secondo parametro getEncryptionKey() , come illustrato di seguito:

    var customKey:String = "My custom ELS salt key"; 
    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password, customKey);
  7. Create o aprite il database

    Con una chiave di crittografia restituita dal metodo getEncryptionKey() , il codice può creare un nuovo database crittografato o tentare di aprire il database crittografato esistente. In entrambi i casi, utilizzate il metodo open() o openAsync() della classe SQLConnection, come descritto in Creazione di un database crittografato e Connessione a un database crittografato .

    In questo esempio, l'applicazione è progettata per aprire il database in modalità di esecuzione asincrona. Il codice imposta il listener di eventi appropriato e chiama il metodo openAsync() , passando la chiave di crittografia come argomento finale:

    conn.addEventListener(SQLEvent.OPEN, openHandler); 
    conn.addEventListener(SQLErrorEvent.ERROR, openError); 
     
    conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey);

    Nei metodi listener, il codice rimuove le registrazioni del listener di eventi. Visualizza quindi un messaggio di stato per indicare se il database è stato creato o aperto oppure se si è verificato un errore. La parte più significativa di questi gestori di eventi è il metodo openError() . In questo metodo un istruzione if verifica se il database esiste (ovvero se il codice tenta di connettersi a un database esistente) e se l'ID dell'errore corrisponde alla costante EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID . Se si verificano entrambe queste condizioni, la password immessa dall'utente probabilmente non è corretta. In alternativa, il file specificato potrebbe non essere un file di database. Di seguito è riportato il codice che verifica l'ID dell'errore:

    if (!createNewDB && event.error.errorID == EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID) 
    { 
        statusMsg.text = "Incorrect password!"; 
    } 
    else 
    { 
        statusMsg.text = "Error creating or opening database."; 
    }

    Per il codice completo dei listener di eventi di esempio, consultate Esempio di codice completo per la generazione e l'uso di una chiave di crittografia .

Esempio di codice completo per la generazione e l'uso di una chiave di crittografia

Di seguito è riportato il codice completo per l'applicazione di esempio “Generazione e uso di una chiave di crittografia”. Il codice è composto da due parti.

Nell'esempio viene utilizzata la classe EncryptionKeyGenerator per creare una chiave di crittografia da una password. La classe EncryptionKeyGenerator è inclusa nel progetto della libreria di base ActionScript 3.0 open source (as3corelib). Potete scaricare il pacchetto as3corelib che comprende il codice sorgente e la documentazione . Potete inoltre scaricare il SWC o i file del codice sorgente dalla pagina del progetto.

Esempio Flex

Il file MXML dell'applicazione contiene il codice sorgente di un'applicazione semplice che crea o apre una connessione a un database crittografato:

<?xml version="1.0" encoding="utf-8"?> 
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();"> 
    <mx:Script> 
        <![CDATA[ 
            import com.adobe.air.crypto.EncryptionKeyGenerator; 
             
            private const dbFileName:String = "encryptedDatabase.db"; 
             
            private var dbFile:File; 
            private var createNewDB:Boolean = true; 
            private var conn:SQLConnection; 
             
            // ------- Event handling ------- 
             
            private function init():void 
            { 
                conn = new SQLConnection(); 
                dbFile = File.applicationStorageDirectory.resolvePath(dbFileName); 
                if (dbFile.exists) 
                { 
                    createNewDB = false; 
                    instructions.text = "Enter your database password to open the encrypted database."; 
                    openButton.label = "Open Database"; 
                } 
            } 
             
            private function openConnection():void 
            { 
                var password:String = passwordInput.text; 
                 
                var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator(); 
                 
                if (password == null || password.length <= 0) 
                { 
                    statusMsg.text = "Please specify a password."; 
                    return; 
                } 
                 
                if (!keyGenerator.validateStrongPassword(password)) 
                { 
                    statusMsg.text = "The password must be 8-32 characters long. It must contain at least one lowercase letter, at least one uppercase letter, and at least one number or symbol."; 
                    return; 
                } 
                 
                passwordInput.text = ""; 
                passwordInput.enabled = false; 
                openButton.enabled = false; 
                 
                var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password); 
                 
                conn.addEventListener(SQLEvent.OPEN, openHandler); 
                conn.addEventListener(SQLErrorEvent.ERROR, openError); 
                  
                conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey); 
            } 
             
            private function openHandler(event:SQLEvent):void 
            { 
                conn.removeEventListener(SQLEvent.OPEN, openHandler); 
                conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
                  
                statusMsg.setStyle("color", 0x009900); 
                if (createNewDB) 
                { 
                    statusMsg.text = "The encrypted database was created successfully."; 
                } 
                else 
                { 
                    statusMsg.text = "The encrypted database was opened successfully."; 
                } 
            } 
              
            private function openError(event:SQLErrorEvent):void 
            { 
                conn.removeEventListener(SQLEvent.OPEN, openHandler); 
                conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
     
                if (!createNewDB && event.error.errorID == EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID) 
                { 
                    statusMsg.text = "Incorrect password!"; 
                } 
                else 
                { 
                    statusMsg.text = "Error creating or opening database."; 
                } 
            } 
        ]]> 
    </mx:Script> 
    <mx:Text id="instructions" text="Enter a password to create an encrypted database. The next time you open the application, you will need to re-enter the password to open the database again." width="75%" height="65"/> 
    <mx:HBox> 
        <mx:TextInput id="passwordInput" displayAsPassword="true"/> 
        <mx:Button id="openButton" label="Create Database" click="openConnection();"/> 
    </mx:HBox> 
    <mx:Text id="statusMsg" color="#990000" width="75%"/> 
</mx:WindowedApplication>

Esempio Flash Professional

Il file FLA dell'applicazione contiene il codice sorgente di un'applicazione semplice che crea o apre una connessione a un database crittografato. Per il file FLA sono disponibili quattro componenti sullo stage:

Nome istanza

Tipo di componente

Descrizione

instructions

Label

Contiene le istruzioni fornite all'utente

passwordInput

TextInput

Campo di input in cui l'utente immette la password

openButton

Button

Pulsante su cui l'utente fa clic dopo aver immesso la password

statusMsg

Label

Visualizza messaggi di stato (operazione riuscita o non riuscita).

Il codice dell'applicazione è definito in un fotogramma chiave sul fotogramma 1 della linea temporale principale. Di seguito è riportato il codice dell'applicazione:

import com.adobe.air.crypto.EncryptionKeyGenerator; 
     
const dbFileName:String = "encryptedDatabase.db"; 
     
var dbFile:File; 
var createNewDB:Boolean = true; 
var conn:SQLConnection; 
     
init(); 
     
// ------- Event handling ------- 
     
function init():void 
{ 
    passwordInput.displayAsPassword = true; 
    openButton.addEventListener(MouseEvent.CLICK, openConnection); 
    statusMsg.setStyle("textFormat", new TextFormat(null, null, 0x990000)); 
     
    conn = new SQLConnection(); 
    dbFile = File.applicationStorageDirectory.resolvePath(dbFileName); 
     
    if (dbFile.exists) 
    { 
        createNewDB = false; 
        instructions.text = "Enter your database password to open the encrypted database."; 
        openButton.label = "Open Database"; 
    } 
    else 
    { 
        instructions.text = "Enter a password to create an encrypted database. The next time you open the application, you will need to re-enter the password to open the database again."; 
        openButton.label = "Create Database"; 
    } 
} 
     
function openConnection(event:MouseEvent):void 
{ 
    var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator(); 
     
    var password:String = passwordInput.text; 
     
    if (password == null || password.length <= 0) 
    { 
        statusMsg.text = "Please specify a password."; 
        return; 
    } 
     
    if (!keyGenerator.validateStrongPassword(password)) 
    { 
        statusMsg.text = "The password must be 8-32 characters long. It must contain at least one lowercase letter, at least one uppercase letter, and at least one number or symbol."; 
        return; 
    } 
     
    passwordInput.text = ""; 
    passwordInput.enabled = false; 
    openButton.enabled = false; 
     
    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password); 
     
    conn.addEventListener(SQLEvent.OPEN, openHandler); 
    conn.addEventListener(SQLErrorEvent.ERROR, openError); 
     
    conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey); 
} 
     
function openHandler(event:SQLEvent):void 
{ 
    conn.removeEventListener(SQLEvent.OPEN, openHandler); 
    conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
     
    statusMsg.setStyle("textFormat", new TextFormat(null, null, 0x009900)); 
    if (createNewDB) 
    { 
        statusMsg.text = "The encrypted database was created successfully."; 
    } 
    else 
    { 
        statusMsg.text = "The encrypted database was opened successfully."; 
    } 
} 
 
function openError(event:SQLErrorEvent):void 
{ 
    conn.removeEventListener(SQLEvent.OPEN, openHandler); 
    conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
     
    if (!createNewDB && event.error.errorID == EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID) 
    { 
        statusMsg.text = "Incorrect password!"; 
    } 
    else 
    { 
        statusMsg.text = "Error creating or opening database."; 
    } 
}

Nozioni fondamentali sulla classe EncryptionKeyGenerator

Non è necessario comprendere il funzionamento intrinseco della classe EncryptionKeyGenerator per utilizzarla per la creazione di una chiave di crittografia sicura per il database dell'applicazione. Il processo di utilizzo della classe è illustrato in Uso della classe EncryptionKeyGenerator per ottenere una chiave di crittografia sicura . Questi concetti potrebbero tuttavia risultare utili per comprendere le tecniche utilizzate dalla classe nel caso in cui, ad esempio, desideriate adattare la classe o incorporarne alcune tecniche per situazioni che necessitano un livello diverso di riservatezza dei dati.

La classe EncryptionKeyGenerator è inclusa nel progetto della libreria di base ActionScript 3.0 open source (as3corelib). Potete scaricare il pacchetto as3corelib che include codice sorgente e documentazione . Dal sito del progetto potete inoltre visualizzare il codice sorgente o scaricarlo per seguire direttamente le spiegazioni.

Quando il codice crea un'istanza di EncryptionKeyGenerator e ne chiama il metodo getEncryptionKey() , vengono eseguite diverse operazioni per garantire che l'accesso ai dati possa essere eseguito solo dall'utente che dispone dei diritti appropriati. Il processo per generare una chiave di crittografia da una password immessa dall'utente prima della creazione del database è analogo a quello di ricreazione della chiave di crittografia per l'apertura del database.

Ottenimento e convalida di una password complessa

Quando il codice chiama il metodo getEncryptionKey() , viene passata una password come parametro. La password viene usata come base per la chiave di crittografia. Usando delle informazioni che solo l'utente conosce, questo metodo di progettazione assicura che solo l'utente che conosce la password possa accedere ai dati nel database. Anche se un utente malintenzionato dovesse accedere all'account dell'utente sul computer, non potrà accedere al database senza conoscere la password. Per la massima sicurezza, l'applicazione non memorizza mai la password.

Il codice di un'applicazione crea un'istanza EncryptionKeyGenerator e chiama il suo metodo getEncryptionKey() , passando come argomento una password immessa dall'utente (la variabile password in questo esempio):

var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator(); 
var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password);

La prima operazione eseguita dalla classe EncryptionKeyGenerator quando viene chiamato il metodo getEncryptionKey() è quello di controllare la password immessa dall'utente per assicurare che soddisfi i requisiti di complessità delle password. La classe EncryptionKeyGenerator richiede una password di lunghezza compresa tra 8 e 32 caratteri. Deve contenere sia lettere maiuscole che minuscole e almeno un numero o un simbolo.

L'espressione regolare che controlla questo schema è definita come costante con nome strongPasswordPattern :

private static const STRONG_PASSWORD_PATTERN:RegExp = /(?=^.{8,32}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/;

Il codice che verifica la password si trova nel metodo validateStrongPassword() della classe EncryptionKeyGenerator. Di seguito è riportato il codice:

public function vaidateStrongPassword(password:String):Boolean 
{ 
    if (password == null || password.length <= 0) 
    { 
        return false; 
    } 
     
    return STRONG_PASSWORD_PATTERN.test(password)) 
}

Internamente il metodo getEncryptionKey() chiama il metodo validateStrongPassword() e, se la password non è valida, genera un'eccezione. Il metodo validateStrongPassword() è un metodo pubblico, pertanto il codice dell'applicazione può verificare una password senza chiamare il metodo getEncryptionKey() ed evitare che venga generato un errore.

Espandere la password a 256 bit

In una fase successiva del processo, è richiesto che la lunghezza della password sia di 256 bit. Anziché richiedere a ogni utente di immettere una password della lunghezza esatta di 256 bit (32 caratteri), il codice crea una password più lunga ripetendo i caratteri in essa contenuti.

Il metodo getEncryptionKey() chiama il metodo concatenatePassword() per eseguire l'operazione di creazione della password lunga.

var concatenatedPassword:String = concatenatePassword(password);

Di seguito è riportato il codice del metodo concatenatePassword() :

private function concatenatePassword(pwd:String):String 
{ 
    var len:int = pwd.length; 
    var targetLength:int = 32; 
     
    if (len == targetLength) 
    { 
        return pwd; 
    } 
     
    var repetitions:int = Math.floor(targetLength / len); 
    var excess:int = targetLength % len; 
     
    var result:String = ""; 
     
    for (var i:uint = 0; i < repetitions; i++) 
    { 
        result += pwd; 
    } 
     
    result += pwd.substr(0, excess); 
     
    return result; 
}

Se la password è inferiore a 256 bit, il codice concatena più istanze della password per raggiungere 256 bit. Se la lunghezza non corrisponde esattamente, l'ultima parte ripetuta viene abbreviata per ottenere esattamente 256 bit.

Generare o recuperare un valore salt di 256 bit

Il prossimo passaggio consiste nell'ottenere un valore salt di 256 bit che in un passaggio successivo viene combinato con la password. Un valore salt è un valore casuale aggiunto o combinato con un valore immesso dall'utente per formare una password. L'uso di un valore salt con una password assicura che, anche se un utente sceglie una parola reale o un termine comune, la combinazione di password più salt usata dal sistema sia un valore casuale. Questo metodo casuale aiuta a proteggere dagli attacchi con dizionario, in cui un utente malintenzionato utilizza un elenco di parole per tentare di individuare la password. Inoltre, quando si genera il valore salt e lo si memorizza nell'archivio locale crittografato, questo viene collegato all'account dell'utente nel computer in cui si trova il file di database.

Se l'applicazione chiama il metodo getEncryptionKey() per la prima volta, il codice crea un valore salt casuale di 256 bit. In caso contrario, il codice carica il valore salt dall'archivio locale crittografato.

Il valore salt viene memorizzato in una variabile denominata salt . Il codice determina se il valore salt è già stato creato tentando di caricarlo dall'archivio locale crittografato:

var salt:ByteArray = EncryptedLocalStore.getItem(saltKey); 
if (salt == null) 
{ 
    salt = makeSalt(); 
    EncryptedLocalStore.setItem(saltKey, salt); 
}

Se il codice crea un nuovo valore salt, il metodo makeSalt() genera un valore casuale a 256 bit. Poiché il valore viene memorizzato nell'archivio locale crittografato, viene generato come un oggetto ByteArray. Il metodo makeSalt() usa il metodo Math.random() per generare il valore in modo casuale. Il metodo Math.random() non può generare 256 bit contemporaneamente. Il codice usa invece un ciclo per chiamare Math.random() otto volte. Ogni volta, viene generato un valore uint casuale compreso tra 0 e 4294967295 (il valore uint massimo). Il valore uint viene usato per praticità, in quanto utilizza esattamente 32 bit. Mediante la scrittura di otto valori uint nell'oggetto ByteArray, viene generato un valore di 256 bit. Il codice seguente si riferisce al metodo makeSalt() :

private function makeSalt():ByteArray 
{ 
    var result:ByteArray = new ByteArray; 
     
    for (var i:uint = 0; i < 8; i++) 
    { 
        result.writeUnsignedInt(Math.round(Math.random() * uint.MAX_VALUE)); 
    } 
     
    return result; 
}

Quando il codice salva il valore salt nell'archivio locale crittografato o lo recupera da esso, necessità di una chiave String in cui salvare il valore. Il valore salt non può essere recuperato se la chiave non è nota. Il tal caso, la chiave di crittografia non può essere ricreata ogni volta per riaprire il database. Per impostazione predefinita, EncryptionKeyGenerator utilizza una chiave dell'archivio locale crittografato predefinita che viene definita nella costante SALT_ELS_KEY . Anziché utilizzare la chiave predefinita, il codice dell'applicazione può anche specificare una chiave dell'archivio locale crittografato da utilizzare nella chiamata al metodo getEncryptionKey() . La chiave dell'archivio locale crittografato salt predefinita o specificata dall'applicazione viene memorizzata in una variabile denominata saltKey . Tale variabile viene utilizzata nelle chiamate a EncryptedLocalStore.setItem() e EncryptedLocalStore.getItem() , come mostrato in precedenza.

Combinazione della password a 256 bit e del valore salt mediante l'operatore XOR

Il codice a questo punto dispone di una password a 256 bit e di un valore salt a 256 bit. Utilizza quindi un'operazione XOR bit a bit per combinare il valore salt e la password concatenata in un solo valore. In realtà questa tecnica consente di creare una password a 256 bit composta da caratteri compresi nell'intera gamma dei caratteri possibili. Questa situazione si verifica anche se la password effettiva immessa è in genere composta principalmente da caratteri alfanumerici. La maggiore casualità offre il vantaggio di ampliare la gamma delle password possibili senza che l'utente debba immettere una password complessa lunga.

Il risultato dell'operazione XOR è memorizzato nella variabile unhashedKey . Il processo effettivo di esecuzione di un'operazione XOR bit a bit sui due valori viene seguito nel metodo xorBytes() :

var unhashedKey:ByteArray = xorBytes(concatenatedPassword, salt);

L'operatore XOR bit a bit ( ^ ) accetta due valori uint e restituisce un valore uint (un valore uint contiene 32 bit). I valori di input passati come argomenti al metodo xorBytes() sono un valore String (la password) e un ByteArray (il salt). Il codice usa quindi un ciclo per estrarre 32 bit alla volta da ogni input da combinare tramite l'operatore XOR.

private function xorBytes(passwordString:String, salt:ByteArray):ByteArray 
{ 
    var result:ByteArray = new ByteArray(); 
     
    for (var i:uint = 0; i < 32; i += 4) 
    { 
        // ... 
    } 
     
    return result; 
}

All'interno del ciclo vengono estratti i primi 32 bit (4 byte) dal parametro passwordString . Questi bit vengono estratti e convertiti in un valore uint ( o1 ) mediante un processo composto da due parti. Il metodo charCodeAt() ottiene in primo luogo il valore numerico di ogni carattere. Tale valore viene quindi spostato nella posizione corretta del valore uint mediante l'operatore di spostamento a sinistra bit a bit ( << ) e quindi il valore spostato viene aggiunto a o1 . Il primo carattere ( i ), ad esempio, viene convertito nei primi 8 bit usando l'operatore di spostamento a sinistra bit a bit ( << ) per spostare i bit verso sinistra di 24 bit e assegnando tale valore a o1 . Il secondo carattere (i + 1 ) viene convertito nei secondi 8 bit spostando il relativo valore verso sinistra di 16 bit e aggiungendo il risultato a o1 . I valori del terzo e quarto carattere vengono aggiunti nello stesso modo.

        // ... 
         
        // Extract 4 bytes from the password string and convert to a uint 
        var o1:uint = passwordString.charCodeAt(i) << 24; 
        o1 += passwordString.charCodeAt(i + 1) << 16; 
        o1 += passwordString.charCodeAt(i + 2) << 8; 
        o1 += passwordString.charCodeAt(i + 3); 
         
        // ...

La variabile o1 contiene ora 32 bit del parametro passwordString . A questo punto, vengono estratti 32 bit dal parametro salt mediante la chiamata del relativo metodo readUnsignedInt() . I 32 bit vengono memorizzati nella variabile uint o2 .

        // ... 
         
        salt.position = i; 
        var o2:uint = salt.readUnsignedInt(); 
         
        // ...

Infine, i due valori a 32 bit (uint) vengono combinati mediante l'operatore XOR e il risultato viene scritto in un ByteArray denominato result .

        // ... 
         
        var xor:uint = o1 ^ o2; 
        result.writeUnsignedInt(xor); 
        // ...

Una volta completato il ciclo, viene restituito l'oggetto ByteArray contenente il risultato di XOR.

        // ... 
    } 
     
    return result; 
}

Hash della chiave

In seguito alla combinazione della password concatenata e del valore salt, il passaggio successivo consiste nel garantire maggiore sicurezza per il valore mediante hashing, usando l'algoritmo di hash SHA-256. L'hash del valore ne rende più difficile la decodificazione da parte di un utente malintenzionato.

Il codice, a questo punto, dispone di un ByteArray denominato unhashedKey che include la password concatenata combinata con il valore salt. Il progetto della libreria chiave di ActionScript 3.0 (as3corelib) comprende una classe SHA256 nel pacchetto com.adobe.crypto. Il metodo SHA256.hashBytes() esegue quindi un hash SHA-256 su un ByteArray e restituisce un valore String contenente il risultato hash a 256 bit come numero esadecimale. La classe EncryptionKeyGenerator utilizza la classe SHA256 per l'hash della chiave:

var hashedKey:String = SHA256.hashBytes(unhashedKey);

Estrarre la chiave di crittografia dall'hash

La chiave di crittografia deve essere un ByteArray con una lunghezza esattamente di 16 byte (128 bit). Il risultato dell'algoritmo di hash SHA-256 ha sempre una lunghezza di 256 bit. Di conseguenza, il passaggio finale consiste nel selezionare 128 bit dal risultato hash da utilizzare come chiave di crittografia effettiva.

Nella classe EncryptionKeyGenerator il codice riduce la chiave a 128 bit chiamando il metodo generateEncryptionKey() e restituisce il risultato di questo metodo come risultato del metodo getEncryptionKey() :

var encryptionKey:ByteArray = generateEncryptionKey(hashedKey); 
return encryptionKey;

Non è necessario usare i primi 128 bit come chiave di crittografia. Potete selezionare un intervallo di bit a partire da un punto arbitrario, selezionare un bit ogni due oppure eseguire la selezione in modo diverso. L'aspetto importante è che il codice selezioni 128 bit distinti e che vengano usati ogni volta gli stessi 128 bit.

In questo caso il metodo generateEncryptionKey() usa come chiave di crittografia l'intervallo di bit a partire dal diciottesimo byte. Come accennato in precedenza, la classe SHA256 restituisce un valore String contenente un hash di 256 bit come numero esadecimale. Un singolo blocco di 128 bit contiene troppi byte da aggiungere contemporaneamente a un ByteArray. Di conseguenza, il codice usa un ciclo for per estrarre i caratteri da un valore String esadecimale, convertirli in valori numerici effettivi e aggiungerli al ByteArray. La stringa risultante SHA-256 è lunga 64 caratteri. Un intervallo a 128 bit equivale a 32 caratteri nel valore String, quindi ogni carattere rappresenta 4 bit. L'incremento di dati più piccolo che potete aggiungere a un ByteArray è un byte (8 bit), che equivale a due caratteri nell'oggetto String hash . Di conseguenza, il ciclo effettua il conteggio da 0 a 31 (32 caratteri) in incrementi di 2 caratteri.

All'interno del ciclo, il codice determina innanzitutto la posizione iniziale per la coppia di caratteri corrente. Poiché l'intervallo desiderato inizia in corrispondenza del carattere nella posizione di indice 17 (il 18esimo byte), la variabile position viene assegnata al valore iterator corrente ( i ) più 17. Il codice usa il metodo substr() dell'oggetto String per estrarre i due caratteri nella posizione corrente. Tali caratteri vengono archiviati nella variabile hex . Il codice usa quindi il metodo parseInt() per convertire l'oggetto String hex in un valore integer decimale e memorizza tale valore nella variabile int byte . Infine, aggiunge il valore presente in byte al ByteArray result usando il relativo metodo writeByte() . Al termine del ciclo, il ByteArray result contiene 16 byte ed è pronto per essere usato come chiave di crittografia di un database.

private function generateEncryptionKey(hash:String):ByteArray 
{ 
    var result:ByteArray = new ByteArray(); 
     
    for (var i:uint = 0; i < 32; i += 2) 
    { 
        var position:uint = i + 17; 
        var hex:String = hash.substr(position, 2); 
        var byte:int = parseInt(hex, 16); 
        result.writeByte(byte); 
    } 
     
    return result; 
}