Utilisation du chiffrement avec les bases de données SQL

Adobe AIR 1.5 et les versions ultérieures

Toutes les applications Adobe AIR partagent le même moteur de base de données locale. Par conséquent, toute application AIR peut se connecter, lire et écrire dans un fichier d’une base de données non chiffrée. Depuis Adobe AIR 1.5, AIR a la capacité de créer et de se connecter à des fichiers d’une base de données chiffrée. Lorsque vous utilisez une base de données chiffrée, l’application doit fournir la clé de chiffrement appropriée pour se connecter à cette base de données. Si la clé de chiffrement fournie n’est pas correcte (ou s’il n’y a pas de clé), l’application ne peut pas se connecter à la base de données. Elle ne peut donc pas lire les données de la base de données ni y écrire ou modifier des données.

Pour utiliser une base de données chiffrée, vous devez la créer en tant que base de données chiffrée. Avec une base de données chiffrée existante, vous pouvez ouvrir une connexion à la base de données. Vous pouvez également modifier la clé de chiffrement d’une base de données chiffrée. Mises à part la création et la connexion aux bases de données chiffrées, les techniques de travail sont les mêmes que pour une base de données non chiffrée. En particulier, l’exécution des instructions SQL est identique, que la base de données soit chiffrée ou non.

Utilisation d’une base de données chiffrée

Le chiffrement se révèle très utile dès que vous souhaitez limiter l’accès aux informations stockées dans une base de données. Dans Adobe AIR, la fonction de chiffrement de base de données peut être utilisée dans plusieurs objectifs. Voici quelques exemples pour lesquels l’utilisation d’une base de données chiffrée peut être utile :

  • Cache en lecture seule de données d’application privées et téléchargées depuis un serveur

  • Stockage d’application local de données privées synchronisées avec un serveur (données envoyées et chargées depuis le serveur)

  • Fichiers chiffrés utilisés en tant que format de fichier pour les documents créés et modifiés par l’application Les fichiers peuvent être réservés à un utilisateur, donc privés, ou conçus pour être partagés par tous les utilisateurs de l’application.

  • Toute autre utilisation d’un magasin local de données, par exemple les utilisations décrites à la section Cas d’utilisation des bases de données SQL locales, où les données peuvent être réservées aux personnes disposant d’un accès à l’ordinateur ou aux fichiers de la base de données.

Comprendre la raison pour laquelle vous souhaitez utiliser une base de données chiffrée vous permet de choisir l’architecture de votre application. Le chiffrement risque en particulier d’affecter la manière dont l’application crée, obtient et stocke la clé de chiffrement de la base de données. Pour plus d’informations sur ces considérations, voir Considérations relatives à l’utilisation du chiffrement avec une base de données.

Parallèlement à l’utilisation d’une base de données chiffrée, le magasin local chiffré permet également de préserver la confidentialité des données sensibles. Grâce au magasin local chiffré, vous stockez une valeur ByteArray unique avec une clé String. Seule l’application AIR qui stocke la valeur peut y accéder, et ceci uniquement sur l’ordinateur sur lequel elle est stockée. Avec le magasin local chiffré, il n’est pas nécessaire de créer votre propre clé de chiffrement. Pour ces raisons, le magasin local chiffré convient davantage au stockage aisé d’une seule valeur ou d’un ensemble de valeurs facilement encodé dans un objet ByteArray. Une base de données chiffrée convient mieux aux grands ensembles de données où l’interrogation et le stockage de données structurées sont souhaitables. Pour plus d’informations sur l’utilisation du magasin local chiffré, voir Stockage local chiffré.

Création d’une base de données chiffrée

Pour utiliser une base de données chiffrée, son fichier doit être chiffré lors de sa création. Lorsqu’une base de données a été créée sans chiffrement, elle ne peut plus être chiffrée par la suite. De la même façon, une base de données chiffrée ne peut pas devenir non chiffrée. Si nécessaire, vous pouvez modifier la clé de chiffrement d’une base de données chiffrée. Pour plus d’informations, voir la section Modification de la clé de chiffrement d’une base de données. Si votre base de données n’est pas chiffrée et que vous souhaitez utiliser le chiffrement de bases de données, vous pouvez créer une nouvelle base de données chiffrée, puis y copier la structure et les données des tables existantes.

La création d’une base de données chiffrée est très similaire à la création d’une base de données non chiffrée, décrite à la section Création d’une base de données. Vous commencez par créer une occurrence de SQLConnection représentant la connexion à la base de données. Vous créez la base de données en appelant la méthode open() ou openAsync() de l’objet SQLConnection, en spécifiant un fichier qui n’existe pas encore pour l’emplacement de la base de données. La seule différence lors de la création d’une base de données chiffrée est que vous fournissez une valeur au paramètre encryptionKey (cinquième paramètre de la méthode open() et sixième paramètre de la méthode openAsync()).

Un objet ByteArray contenant exactement 16 octets constitue une valeur valide du paramètre encryptionKey.

Les exemples suivants illustrent la création d’une base de données chiffrée. Pour plus de simplicité, la clé de chiffrement est codée en dur dans le code de l’application dans ces exemples. Toutefois, cette technique est fortement déconseillée car elle n’est pas sécurisée.

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

Vous trouverez un exemple décrivant un moyen conseillé de générer une clé de chiffrement à la section Exemple : Génération et utilisation d’une clé de chiffrement.

Connexion à une base de données chiffrée

Comme la création d’une base de données chiffrée, la procédure qui permet d’ouvrir une connexion à une base de données chiffrée est la même que pour une base de données non chiffrée. Cette procédure est détaillée à la section Connexion à une base de données. Vous faites appel à la méthode open() pour ouvrir une connexion en mode d’exécution synchrone, à la méthode openAsync() pour ouvrir une connexion en mode d’exécution asynchrone. La seule différence est que, pour ouvrir une base de données chiffrée, vous spécifiez la valeur correcte du paramètre encryptionKey (cinquième paramètre de la méthode open() et sixième paramètre de la méthode openAsync()).

Si la clé de chiffrement fournie n’est pas correcte, une erreur se produit. Dans le cas de la méthode open(), une exception SQLError est renvoyée. Dans le cas de la méthode openAsync(), l’objet SQLConnection distribue un événement SQLErrorEvent, dont la propriété errorcontient un objet SQLError. Dans les deux cas, l’objet SQLError généré par l’exception a une valeur de propriété errorID 3138. Cet ID d’erreur correspond au message d’erreur « Le fichier ouvert n’est pas un fichier de base de données ».

L’exemple suivant décrit l’ouverture d’une base de données chiffrée en mode d’exécution asynchrone. Pour plus de simplicité, dans cet exemple, la clé de chiffrement est codée en dur dans le code de l’application. Toutefois, cette technique est fortement déconseillée car elle n’est pas sécurisée.

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

L’exemple suivant décrit l’ouverture d’une base de données chiffrée en mode d’exécution synchrone. Pour plus de simplicité, dans cet exemple, la clé de chiffrement est codée en dur dans le code de l’application. Toutefois, cette technique est fortement déconseillée car elle n’est pas sécurisée.

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

Vous trouverez un exemple décrivant un moyen conseillé de générer une clé de chiffrement à la section Exemple : Génération et utilisation d’une clé de chiffrement.

Modification de la clé de chiffrement d’une base de données

Vous pouvez modifier ultérieurement la clé de chiffrement d’une base de données chiffrée. Pour modifier la clé de chiffrement d’une base de données, commencez par ouvrir une connexion à la base de données en créant une occurrence de SQLConnection, puis appelez sa méthode open() ou openAsync(). Lorsque la connexion à la base de données est établie, appelez la méthode reencrypt() en transmettant la nouvelle clé de chiffrement en tant qu’argument.

Comme la plupart des opérations de base de données, le comportement de la méthode reencrypt() varie selon si la connexion à la base de données utilise le mode d’exécution synchrone ou asynchrone. Si vous utilisez la méthode open() pour vous connecter à la base de données, l’opération reencrypt() s’exécute de façon synchrone. Lorsque l’opération est terminée, l’exécution se poursuit avec la ligne suivante du code :

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

À l’inverse, si la connexion à la base de données est établie avec la méthode openAsync(), l’opération reencrypt() est asynchrone. L’appel à la méthode reencrypt() commence le processus de rechiffrement. Lorsque l’opération est terminée, l’objet SQLConnection distribue un événement reencrypt. Vous utilisez un écouteur d’événement pour connaître le moment où le rechiffrement se termine :

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’opération reencrypt() s’exécute dans sa propre transaction. Si l’opération est interrompue ou échoue (par exemple, si l’application est fermée avant la fin de l’opération), la transaction est annulée. Dans ce cas, la clé de chiffrement d’origine demeure en vigueur pour la base de données.

La méthode reencrypt() ne peut pas être utilisée pour supprimer le chiffrage d’une base de données. La transmission d’une valeur null ou d’une clé de chiffrement qui ne correspond pas à un objet ByteArray de 16 octets à la méthode reencrypt() provoque une erreur.

Considérations relatives à l’utilisation du chiffrement avec une base de données

La section Utilisation d’une base de données chiffrée présente plusieurs cas de figure pour lesquels l’utilisation d’une base de données chiffrée peut être nécessaire. À l’évidence, les besoins de confidentialité diffèrent selon les différents cas d’utilisation des applications (les scénarios présentés, ainsi que d’autres). L’architecture du chiffrement dans votre application joue un rôle important sur le contrôle de la confidentialité des données d’une base de données. Par exemple, si vous utilisez une base de données chiffrée pour assurer la confidentialité des données personnelles, même vis-à-vis des autres utilisateurs de l’ordinateur, la base de données de chaque utilisateur a besoin de sa propre clé de chiffrement. Pour une sécurité optimale, votre application peut générer la clé à partir d’un mot de passe saisi par l’utilisateur. Baser la clé de chiffrement sur un mot de passe permet d’être certain que les données demeurent inaccessibles, même si une autre personne utilise le compte de l’utilisateur sur l’ordinateur. À l’extrême opposé, supposons à présent que vous souhaitiez qu’un fichier de bases de données soit lisible par tout utilisateur de votre application, mais pas par les utilisateurs d’autres applications. Dans ce cas, chaque copie installée de l’application doit pouvoir accéder à une clé de chiffrement partagée.

Vous pouvez concevoir votre application, et en particulier la technique utilisée pour générer la clé de chiffrement, en fonction du niveau de confidentialité désiré pour les données de l’application. Voici une liste de quelques suggestions de conception répondant à divers niveaux de confidentialité des données :

  • Pour qu’une base de données soit accessible à tout utilisateur autorisé à accéder à l’application sur n’importe quel ordinateur, utilisez une seule clé disponible pour toutes les occurrences de l’application. Par exemple, lors de sa première exécution, l’application peut télécharger la clé de chiffrement partagée sur un serveur en utilisant un protocole sécurisé de type SSL. Elle peut ensuite enregistrer la clé dans le magasin local chiffré pour l’utiliser ultérieurement. Une autre alternative consiste à chiffrer les données par utilisateur sur l’ordinateur, puis de synchroniser ces données à l’aide d’un magasin de données distant, tel qu’un serveur, pour rendre les données portables.

  • Pour qu’un seul utilisateur puisse accéder à la base de données sur n’importe quel ordinateur, générez la clé de chiffrement en utilisant un secret propre à l’utilisateur (par exemple un mot de passe). En particulier, n’utilisez pas de valeur associée à un ordinateur particulier (par exemple une valeur stockée dans le magasin local chiffré) pour générer la clé. Une autre alternative consiste à chiffrer les données par utilisateur sur l’ordinateur, puis de synchroniser ces données à l’aide d’un magasin de données distant, tel qu’un serveur, pour rendre les données portables.

  • Pour qu’un seul utilisateur puisse accéder à la base de données sur un seul ordinateur, générez la clé à partir d’un mot de passe et d’une valeur générée arbitrairement, appelée salt. Un exemple de cette technique est disponible à la section Exemple : Génération et utilisation d’une clé de chiffrement.

Voici d’autres considérations relatives à la sécurité qu’il est important de ne pas oublier lors de la conception d’une application utilisant une base de données chiffrée :

  • Un système est autant sécurisé que ce que peut l’être son lien le plus faible. Si vous utilisez un mot de passe saisi par l’utilisateur pour générer une clé de chiffrement, pensez à imposer une longueur minimale et des contraintes de complexité pour les mots de passe. Un mot de passe court qui utilise seulement des caractères de base est facile à deviner.

  • Le code source d’une application AIR est stocké sur l’ordinateur de l’utilisateur en texte brut (dans le cas de contenu HTML) ou dans un format binaire facile à décompiler (dans le cas de contenu SWF). Le code source étant accessible, les deux éléments à ne pas oublier sont :

    • Ne jamais coder en dur la clé de chiffrement dans votre code source

    • Toujours partir du principe qu’un attaquant peut aisément découvrir la technique utilisée pour générer une clé de chiffrement (par exemple un générateur de caractères aléatoire ou un algorithme de hachage particulier)

  • Le chiffrement de base de données d’AIR utilise le chiffrement AES (Advanced Encryption Standard) avec le mode CBC-MAC (CCM). Pour être sécurisé, ce chiffrement combine la clé saisie par l’utilisateur avec une valeur salt. Un exemple de cette technique est disponible à la section Exemple : Génération et utilisation d’une clé de chiffrement.

  • Lorsque vous choisissez de chiffrer une base de données, tous les fichiers disque utilisés par le moteur de base de données en combinaison avec celle-ci sont chiffrés. Toutefois, le moteur de base de données stocke certaines données temporaires en mémoire cache pour améliorer les performances en lecture et écriture au cours des transactions. Toutes les données qui résident en mémoire ne sont pas chiffrées. Si un attaquant peut accéder à la mémoire utilisée par l’application AIR, par exemple avec un débogueur, les données de la base de données ouverte et non chiffrée sont disponibles.

Exemple : Génération et utilisation d’une clé de chiffrement

Cet exemple d’application présente une technique de génération de clé de chiffrement. Cette application est conçue pour assurer le plus haut niveau de confidentialité et de sécurité aux données des utilisateurs. Un point essentiel de la sécurisation des données privées consiste à obliger l’utilisateur à saisir un mot de passe à chaque connexion de l’application à la base de données. Par conséquent, comme nous l’avons vu dans cet exemple, une application exigeant ce niveau de confidentialité ne doit jamais stocker directement la clé de chiffrement de la base de données.

L’application comprend deux parties : une classe ActionScript qui génère une clé de chiffrement (la classe EncryptionKeyGenerator) et une interface utilisateur de base qui décrit l’utilisation de cette classe. Pour obtenir le code source complet, voir la section Exemple de code complet pour la génération et l’utilisation d’une clé de chiffrement.

Utilisation de la classe EncryptionKeyGenerator pour obtenir une clé de chiffrement sécurisée

Il n’est pas nécessaire de maîtriser les détails du fonctionnement de la classe EncryptionKeyGenerator pour l’utiliser dans votre application. Si vous souhaitez savoir comment la classe génère une clé de chiffrement pour une base de données, voir Fonctionnement de la classe EncryptionKeyGenerator.

Pour utiliser la classe EncryptionKeyGenerator dans votre application, procédez comme suit :

  1. Téléchargez la classe EncryptionKeyGenerator en tant que code source ou SWC compilé. La classe EncryptionKeyGenerator est incluse dans le projet de bibliothèque centrale Open Source ActionScript 3.0 (as3corelib). Vous pouvez télécharger le package as3corelib comprenant le code source et la documentation. Vous pouvez également télécharger des fichiers du code source ou SWC depuis la page du projet.

  2. Placez le code source de la classe EncryptionKeyGenerator (ou le fichier SWC as3corelib) dans un emplacement accessible au code source de votre application.

  3. Dans le code source de votre application, ajoutez une instruction import pour la classe EncryptionKeyGenerator.

    import com.adobe.air.crypto.EncryptionKeyGenerator;
  4. Avant l’endroit où le code crée la base de données ou ouvre une connexion vers celle-ci, ajoutez le code qui crée une occurrence EncryptionKeyGenerator en appelant le constructeur EncryptionKeyGenerator().

    var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator();
  5. Demandez le mot de passe à l’utilisateur :

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

    L’occurrence de EncryptionKeyGenerator utilise ce mot de passe comme base de la clé de chiffrement (décrite à l’étape suivante). L’occurrence de EncryptionKeyGenerator teste le mot de passe par rapport aux exigences de validation de mot de passe renforcé. Si la validation échoue, une erreur survient. Comme le montre l’exemple de code, vous pouvez vérifier le mot de passe en amont en appelant la méthode validateStrongPassword() de l’objet EncryptionKeyGenerator. Cette opération vous permet de déterminer si le mot de passe répond aux exigences minimales d’un mot de passe renforcé et d’éviter une erreur.

  6. Générez la clé de chiffrement à partir du mot de passe :

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

    La méthode getEncryptionKey() génère et renvoie la clé de chiffrement (objet ByteArray à 16 octets). Vous pouvez alors utiliser la clé de chiffrement pour créer votre nouvelle base des données chiffrée ou ouvrir une base de données existante.

    La méthode getEncryptionKey() a un paramètre obligatoire, le mot de passe obtenu à l’étape 5.

    Remarque : pour assurer le plus haut niveau de sécurité et de confidentialité des données, l’application doit obliger l’utilisateur à saisir un mot de passe à chaque connexion de l’application à la base de données. Ne stockez pas directement le mot de passe de l’utilisateur ni la clé de chiffrement de la base de données. Cela entraînerait des risques de sécurité. Au contraire, comme le montre cet exemple, l’application doit utiliser la même technique pour faire dériver la clé de chiffrement du mot de passe lors de la création de la base des données chiffrée et lors des connexions ultérieures à celle-ci.

    La méthode getEncryptionKey() accepte également un second paramètre (facultatif) overrideSaltELSKey. L’occurrence de EncryptionKeyGenerator crée une valeur aléatoire (appelée valeur salt) qui fait partie de la clé de chiffrement. Pour pouvoir recréer la clé de chiffrement, la valeur salt est stockée dans le magasin local chiffré (ELS) de votre application AIR. Par défaut, la classe EncryptionKeyGenerator utilise une chaîne particulière en tant que clé ELS. Bien que cela soit improbable, il est possible que cette clé soit en conflit avec une autre clé utilisée par votre application. Au lieu d’utiliser la clé par défaut, vous pouvez spécifier votre propre clé ELS. Dans ce cas, spécifiez une clé personnalisée en la transmettant en tant que second paramètre getEncryptionKey(), comme illustré ici :

    var customKey:String = "My custom ELS salt key"; 
    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password, customKey);
  7. Création ou ouverture de la base de données

    Avec la clé de chiffrement renvoyée par la méthode getEncryptionKey(), votre code peut créer une nouvelle base de données chiffrée ou tenter d’ouvrir la base de données chiffrée existante. Dans les deux cas, vous utilisez la méthode open() ou openAsync() de la classe SQLConnection, tel que décrit dans les sections Création d’une base de données chiffrée et Connexion à une base de données chiffrée.

    Dans cet exemple, l’application est conçue pour ouvrir la base de données en mode d’exécution asynchrone. Le code configure les écouteurs d’événement appropriés et appelle la méthode openAsync(), en transmettant la clé de chiffrement en tant qu’argument final :

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

    Dans les méthodes des écouteurs, le code supprime les enregistrements des écouteurs d’événement. Il affiche ensuite un message d’état indiquant si la base de données a été créée, ouverte, ou si une erreur s’est produite. La partie la plus importante de ces gestionnaires d’événement se trouve dans la méthode openError(). Dans cette méthode, une instruction if vérifie l’existence de la base de données (c’est-à-dire que le code tente de se connecter à une base de données existante) et si l’ID d’erreur correspond à la constante EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID. Si ces deux conditions sont vraies, il est probable que le mot de passe saisi par l’utilisateur soit incorrect. (Cela peut également signifier que le fichier spécifié n’est pas un fichier de base de données.) Voici le code qui vérifie l’ID d’erreur :

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

    Vous trouverez le code complet des exemples d’écouteurs d’événement à la section Exemple de code complet pour la génération et l’utilisation d’une clé de chiffrement.

Exemple de code complet pour la génération et l’utilisation d’une clé de chiffrement

Voici le code complet d’un exemple d’application « Génération et utilisation d’une clé de chiffrement » : Le code comprend deux parties.

L’exemple utilise la classe EncryptionKeyGenerator pour créer une clé de chiffrement à partir d’un mot de passe. La classe EncryptionKeyGenerator est incluse dans le projet de bibliothèque centrale Open Source ActionScript 3.0 (as3corelib). Vous pouvez télécharger le package as3corelib comprenant le code source et la documentation. Vous pouvez également télécharger des fichiers du code source ou SWC depuis la page du projet.

Exemple Flex

Le fichier MXML de l’application contient le code source d’une application simple qui crée ou ouvre une connexion à une base de données chiffrée :

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

Exemple Flash Professional

Le fichier FLA de l’application contient le code source d’une application simple qui crée ou ouvre une connexion à une base de données chiffrée. Le fichier FLA comprend quatre composants placés sur la scène :

Nom d’occurrence

Type de composant

Description

instructions

Label

Contient les instructions données à l’utilisateur

passwordInput

TextInput

Champ de saisie dans lequel l’utilisateur tape le mot de passe

openButton

Button

Bouton sur lequel l’utilisateur clique après avoir saisi le mot de passe

statusMsg

Label

Affiche des messages d’état (réussite ou échec)

Le code de l’application est défini sur une image-clé de l’image 1 du scénario principal. Voici le code de l’application :

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."; 
    } 
}

Fonctionnement de la classe EncryptionKeyGenerator

Il n’est pas nécessaire de comprendre le fonctionnement intrinsèque de la classe EncryptionKeyGenerator pour l’utiliser afin de créer une clé de chiffrement sécurisée pour la base de données de votre application. Le processus d’utilisation de la classe est décrit à la section Utilisation de la classe EncryptionKeyGenerator pour obtenir une clé de chiffrement sécurisée. Il peut cependant se révéler précieux pour comprendre les techniques utilisées par la classe. Par exemple, vous pourriez vouloir adapter la classe ou intégrer certaines de ses techniques lorsqu’un niveau différent de confidentialité des données est nécessaire.

La classe EncryptionKeyGenerator est incluse dans le projet de bibliothèque centrale Open Source ActionScript 3.0 (as3corelib). Vous pouvez télécharger le package as3corelib comprenant le code source et la documentation. Vous pouvez également afficher le code source dans le site du projet ou le télécharger pour suivre les explications.

Lorsque le code crée une occurrence de EncryptionKeyGenerator et appelle sa méthode getEncryptionKey(), plusieurs mesures permettent de s’assurer que seul l’utilisateur autorisé peut accéder aux données. Le processus correspond à la génération d’une clé de chiffrement à partir d’un mot de passe saisi par l’utilisateur avant la création de la base de données ou à la recréation de la clé de chiffrement pour ouvrir la base de données.

Obtention et validation d’un mot de passe renforcé

Lorsque le code appelle la méthode getEncryptionKey(), il transmet un mot de passe sous forme de paramètre. Ce mot de passe est utilisé comme base de la clé de chiffrement. En utilisant des informations que seul l’utilisateur connaît, cette technique permet de s’assurer que seul l’utilisateur qui connaît le mot de passe peut accéder au contenu de la base de données. Même s’il accède au compte de l’utilisateur sur l’ordinateur, l’attaquant ne peut pas accéder à la base de données sans connaître le mot de passe. Pour une sécurité maximale, l’application ne stocke jamais le mot de passe.

Le code d’une application crée une occurrence d’EncryptionKeyGenerator et appelle la méthode getEncryptionKey() correspondante en transmettant un mot de passe saisi par l’utilisateur sous forme d’argument (soit la variable password dans cet exemple) :

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

La première étape effectuée par la classe EncryptionKeyGenerator lors de l’appel à la méthode getEncryptionKey() consiste à vérifier que le mot de passe saisi par l’utilisateur respecte les exigences définies en matière de sécurité. La classe EncryptionKeyGenerator requiert que le mot de passe contienne entre 8 et 32 caractères. Le mot de passe doit combiner des lettres majuscules et minuscules et comprendre au moins un chiffre ou un symbole.

L’expression régulière qui vérifie ce modèle est définie en tant que constante nommée STRONG_PASSWORD_PATTERN :

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

Le code qui vérifie le mot de passe est dans la méthode validateStrongPassword() de la classe EncryptionKeyGenerator. Le code se présente comme suit :

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

La méthode getEncryptionKey() appelle en interne la méthode validateStrongPassword() de la classe EncryptionKeyGenerator et renvoie une exception si le mot de passe n’est pas valide. La méthode validateStrongPassword() étant publique, le code de l’application peut vérifier le mot de passe sans appeler la méthode getEncryptionKey() pour éviter la production d’erreur.

Extension du mot de passe à 256 bits

Dans la suite du processus, le mot de passe doit avoir une longueur de 256 bits. Plutôt que de demander à chaque utilisateur de saisir un mot de passe faisant exactement 256 bits (32 caractères), le code crée un mot de passe plus long en répétant ses caractères.

Pour exécuter la tâche de création d’un mot de passe long, la méthode getEncryptionKey() appelle la méthode concatenatePassword().

var concatenatedPassword:String = concatenatePassword(password);

Voici le code de la méthode 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; 
}

Si le mot de passe n’atteint pas 256 bits, le code le concatène pour obtenir une longueur de 256 bits. Si la longueur ne fonctionne pas exactement, la dernière répétition est raccourcie jusqu’à correspondre à 256 bits.

Génération ou récupération d’une valeur salt de 256 bits

L’étape suivante consiste à obtenir une valeur salt de 256 bits qui sera ensuite combinée au mot de passe dans une étape ultérieure. Une valeur salt est une valeur aléatoire ajoutée ou combinée à la valeur saisie par l’utilisateur pour composer le mot de passe. L’utilisation d’une valeur salt avec un mot de passe permet de s’assurer que, même si l’utilisateur choisit un mot du dictionnaire ou un terme courant comme mot de passe, la combinaison mot de passe-plus-salt utilisée par le système est une valeur aléatoire. Cet aspect aléatoire assure une protection contre les attaques de type dictionnaire, dans lesquelles l’attaquant utilise une liste de mots pour tenter de deviner le mot de passe. De plus, la génération de cette valeur salt et son stockage dans le magasin local chiffré permet de l’associer au compte de l’utilisateur sur l’ordinateur dans lequel le fichier de base de données est situé.

Si l’application appelle la méthode getEncryptionKey() pour la première fois, le code crée une valeur salt aléatoire de 256 bits. Sinon, le code récupère la valeur salt dans le magasin local chiffré.

La valeur salt est stockée dans une variable nommée salt. Le code détermine si une valeur salt a déjà été créée en tentant de la récupérer dans le magasin local chiffré :

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

Si le code crée une nouvelle valeur salt, la méthode makeSalt() génère une valeur aléatoire de 256 bits. Comme la valeur est ensuite stockée dans le magasin local chiffré, elle est générée sous forme d’objet ByteArray. La méthode makeSalt() utilise la méthode Math.random() pour générer la valeur de façon aléatoire. La méthode Math.random() ne peut pas générer 256 bits en une seule opération. À la place, le code utilise une boucle pour appeler Math.random() à huit reprises. Chaque fois, une valeur uint aléatoire comprise entre 0 et 4294967295 (valeur uint maximale) est générée. La valeur uint est utilisée pour plus de commodité car elle comprend exactement 32 bits. L’écriture de huit valeurs uint dans l’objet ByteArray permet de générer une valeur de 256 bits. Voici le code de la méthode 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; 
}

Lorsque le code enregistre la valeur salt dans le magasin local chiffré ou tente de l’y récupérer, il a besoin d’une clé String dans laquelle la valeur salt est enregistrée. Sans cette clé, la valeur salt ne peut pas être récupérée. Dans ce cas, la clé de chiffrement ne peut pas être recréée à chaque nouvelle ouverture de la base de données. Par défaut, la classe EncryptionKeyGenerator utilise une clé prédéfinie du magasin local chiffré qui est définie dans la constante SALT_ELS_KEY. Au lieu d’utiliser la clé par défaut, le code de l’application peut également spécifier la clé de magasin local chiffré à utiliser dans l’appel à la méthode getEncryptionKey(). La clé de magasin local chiffré de la valeur salt par défaut ou spécifiée par l’application est stockée dans une variable nommée saltKey. Cette variable est utilisée dans les appels de EncryptedLocalStore.setItem() et EncryptedLocalStore.getItem(), comme indiqué précédemment.

Combinaison du mot de passe 256 bits et de la valeur salt via l’opérateur XOR

Le code dispose à présent d’un mot de passe de 256 bits et d’une valeur salt de 256 bits. Il utilise ensuite une opération XOR au niveau du bit pour combiner la valeur salt avec le mot de passe concaténé dans une valeur unique. Cette technique crée un mot de passe de 256 bits à partir de tous les caractères possibles de la plage complète. Ce principe reste vrai même si la saisie du véritable mot de passe est plus probablement constituée des principaux caractères alphanumériques. Cet aspect encore plus aléatoire a l’avantage de créer le plus vaste ensemble possible de mots de passe sans que l’utilisateur ne doive lui-même saisir un mot de passe long et complexe.

Le résultat de l’opération XOR est stocké dans la variable unhashedKey. La véritable opération XOR au niveau du bit sur les deux valeurs se produit dans la méthode xorBytes() :

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

L’opérateur XOR au niveau du bit (^) prend deux valeurs uint et n’en renvoie qu’une. (Une valeur uint contient 32 bits.) Les valeurs d’entrée transmises en tant qu’arguments à la méthode xorBytes() sont une valeur String (le mot de passe) et une valeur ByteArray (la valeur salt). Par conséquent, le code utilise une boucle pour extraire 32 bits de chaque entrée à combiner à l’aide de l’opérateur 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; 
}

Dans la boucle, les premiers 32 bits (4 octets) sont extraits du paramètre passwordString. Ces bits sont extraits et convertis en valeur uint (o1) dans un processus à deux parties. D’abord, la méthode charCodeAt() récupère la valeur numérique de chaque caractère. Cette valeur est ensuite décalée jusqu’à la position appropriée dans la valeur uint par l’opérateur de décalage gauche au niveau du bit (<<), puis la valeur décalée est ajoutée à o1. Par exemple, le premier caractère (i) devient les premiers 8 bits grâce à l’opérateur de décalage gauche au niveau du bit (<<) qui décale les bits de 24 bits à gauche et affecte cette valeur à o1. Le second caractère (i + 1) prend la place des seconds 8 bits en décalant sa valeur vers la gauche de 16 bits et en ajoutant le résultat à o1. Les valeurs des troisième et quatrième caractères sont ajoutées de la même manière.

        // ... 
         
        // 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 variable o1 contient à présent 32 bits provenant du paramètre passwordString. Ensuite, 32 bits sont extraits du paramètre salt via un appel à sa méthode readUnsignedInt(). Les 32 bits sont stockés dans la variable uint o2.

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

Enfin, les deux valeurs de 32 bits (uint) sont combinées par l’opérateur XOR et le résultat est écrit dans un objet ByteArray nommé result.

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

Lorsque la boucle est terminée, l’objet ByteArray contenant le résultat XOR est renvoyé.

        // ... 
    } 
     
    return result; 
}

Hachage de la clé

Une fois le mot de passe concaténé et la valeur salt combinée, l’étape suivante consiste à sécuriser encore davantage cette valeur en la hachant avec un algorithme SHA-256. Le hachage de la valeur rend encore plus difficile sa rétro-conception par un attaquant.

À ce stade, le code possède un objet ByteArray nommé unhashedKey contenant le mot de passe concaténé combiné à la valeur salt. Le projet de bibliothèque ActionScript 3.0 (as3corelib) comprend une classe SHA256 dans le package com.adobe.crypto. La méthode SHA256.hashBytes() qui exécute un hachage SHA-256 sur l’objet ByteArray et renvoie une valeur String contenant le résultat du hachage 256 bits sous forme de nombre hexadécimal. La classe EncryptionKeyGenerator utilise la classe SHA256 pour hacher la clé :

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

Extraction de la clé de chiffrement du hachage

La clé de chiffrement doit être un objet ByteArray faisant exactement 16 octets (128 bits). Le résultat de l’algorithme de hachage SHA-256 fait toujours 256 bits. Par conséquent, l’étape finale consiste à sélectionner 128 bits dans le résultat haché et à les utiliser comme véritable clé de chiffrement.

Dans la classe EncryptionKeyGenerator, le code réduit la clé à 128 bits en appelant la méthode generateEncryptionKey(). Il renvoie ensuite le résultat de cette méthode en tant que résultat de la méthode getEncryptionKey() :

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

Il n’est pas nécessaire d’utiliser les premiers 128 bits comme clé de chiffrement. Vous pouvez sélectionner une plage de bits à partir d’un point arbitraire, sélectionner tous les autres bits ou sélectionner les bits d’une autre manière. L’important est que le code sélectionne 128 bits distincts, et que ces mêmes 128 bits soient utilisés chaque fois.

Dans ce cas, la méthode generateEncryptionKey() utilise la plage de bits commençant au 18ème octet en tant que clé de chiffrement. Comme nous l’avons déjà mentionné, la classe SHA256 renvoie une valeur String contenant un hachage 256 bits sous forme de nombre hexadécimal. Un unique bloc de 128 bits comprend trop d’octets pour qu’ils soient ajoutés en une seule fois à un objet ByteArray. Par conséquent, le code utilise une boucle for pour extraire les caractères de la valeur String hexadécimale, les convertit en valeurs numériques réelles et les ajoute à l’objet ByteArray. La valeur String SHA-256 résultante comprend 64 caractères. Une plage de 128 bits correspond à 32 caractères dans la valeur String et chaque caractère représente 4 bits. Le plus petit incrément de données que vous pouvez ajouter à un objet ByteArray correspond à un seul octet (8 bits), ce qui équivaut à deux caractères dans la valeur String hash. De ce fait, la boucle compte de 0 à 31 (32 caractères) par incréments de 2 caractères.

Dans la boucle, le code commence par déterminer la position de départ de la paire de caractères en cours. Comme la plage désirée commence au niveau du caractère situé à l’index 17 (18ème octet), la variable position est affectée à la valeur de l’itérateur actuel (i) plus 17. Le code utilise la méthode substr() de l’objet String pour extraire les deux caractères situés au niveau de la position en cours. Ces caractères sont stockés dans la variable hex. Ensuite, le code utilise la méthode parseInt() pour convertir la valeur String hex en une valeur décimale entière. Il stocke cette valeur dans la variable int byte. Enfin, le code ajoute la valeur de byte à l’objet ByteArray result à l’aide de sa méthode writeByte(). Lorsque la boucle se termine, l’objet ByteArray result contient 16 octets et peut être utilisé comme clé de chiffrement pour la base de données.

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; 
}