Använda kryptering med SQL-databaser

Adobe AIR 1.5 och senare

Samma lokala databasmotor används för alla Adobe AIR-program. Det betyder att alla AIR-program kan ansluta till, läsa från och skriva till en okrypterad databasfil. Från och med Adobe AIR 1.5 inkluderar AIR funktioner för att skapa och ansluta till krypterade databasfiler. När du använder en krypterad databas måste programmet som vill ansluta till databasen tillhandahålla korrekt krypteringsnyckel. Om fel krypteringsnyckel (eller ingen nyckel) anges kan programmet inte ansluta till databasen. Det innebär att programmet inte kan läsa data från databasen eller skriva till eller ändra data i databasen.

Om du vill använda en krypterad databas måste du skapa databasen som en krypterad databas. Om du har en befintlig krypterad databas kan du upprätta en anslutning till databasen. Du kan också ändra krypteringsnyckeln för en krypterad databas. Förutom när det gäller att skapa och ansluta till krypterade databaser, arbetar du på samma sätt med krypterade och okrypterade databaser. Exempelvis kör du SQL-uttryck på samma sätt oavsett om databasen är krypterad eller inte.

Användningsområden för en krypterad databas

Kryptering är användbart om du vill begränsa åtkomsten till informationen som lagras i en databas. Databaskrypteringsfunktionen i Adobe AIR kan användas för många ändamål. Här följer några exempel på situationer då det kan vara praktiskt att använda en krypterad databas:

  • En skrivskyddad cache med privata programdata som hämtas från en server.

  • Ett lokalt programarkiv för privata data som synkroniseras med en server (data skickas till och läses in från servern).

  • Krypterade filer som används som filformatet för dokument som skapas och redigeras av programmet. Filerna kan vara privata för en användare, eller delas av alla som använder programmet.

  • Annan användning av en lokal datalagringsplats, t.ex. de som beskrivs i avsnittet Användningsområden för lokala SQL-databaser, där informationen måste skyddas från användare som har åtkomst till datorn eller databasfilerna.

Om du förstår när och varför det kan vara praktiskt att använda en krypterad databas blir det lättare att bestämma hur programmet ska struktureras. Detta kan exempelvis påverka hur krypteringsnyckeln för databasen skapas, hämtas eller lagras av programmet. Mer information finns i avsnittet Att tänka på när du använder kryptering med en databas.

I stället för att använda en krypterad databas kan du skydda känsliga data med hjälp av den krypterade, lokala lagringsplatsen. Med den krypterade, lokala lagringsplatsen lagrar du ett enskilt ByteArray-värde med hjälp av en String-nyckel. Värdet är bara tillgängligt för det AIR-program som skapat värdet, och endast på den dator som det lagras på. Med den krypterade lokala lagringsplatsen behöver du inte skapa din egen krypteringsnyckel. Dessa anledningar gör att den krypterade lokala lagringsplatsen passar särskilt bra för att enkelt lagra ett enskilt värde eller en uppsättning värden som lätt kan kodas i en bytearray. En krypterad databas passar bäst för större datauppsättningar där strukturerad datalagring och datafrågor används. Mer information om hur du använder den krypterade lokala lagringsplatsen finns i avsnittet Krypterad lokal lagringsplats.

Skapa en krypterad databas

Du kan bara använda en krypterad databas om databasen krypterades när den skapades. En okrypterad databas kan inte krypteras senare. På motsvarande sätt kan en krypterad databas inte dekrypteras senare. Om det behövs kan du ändra krypteringsnyckeln för en krypterad databas. Mer information finns i avsnittet Ändra krypteringsnyckeln för en databas. Om du har en befintlig databas som inte är krypterad och du vill använda databaskryptering kan du skapa en ny krypterad databas och kopiera den befintliga tabellstrukturen och data till den nya databasen.

Du skapar en krypterad databas på praktiskt taget samma sätt som en okrypterad databas. Mer information finns i avsnittet Skapa en databas. Först skapar du en SQLConnection-instans som representerar anslutningen till databasen. Du skapar databasen genom att anropa SQLConnection-objektets open()-metod eller openAsync()-metod och anger en fil som inte finns än för databasplatsen. Den enda skillnaden när du skapar en krypterad databas är att du anger ett värde för encryptionKey-parametern (open()-metodens femte parameter och openAsync()-metodens sjätte parameter).

Ett giltigt encryptionKey-parametervärde är ett ByteArray-objekt som innehåller exakt 16 byte.

I följande exempel visas hur en krypterad databas skapas. För enkelhetens skull är krypteringsnyckeln hårdkodad i programkoden i de här exemplen. Den här tekniken rekommenderas emellertid inte eftersom den inte är säker.

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

Ett exempel som illustrerar en rekommenderad metod för genereringen av en krypteringsnyckel finns i avsnittet Exempel: Generera och använda en krypteringsnyckel..

Ansluta till en krypterad databas

Precis som när du skapar en krypterad databas, ansluter du till krypterade och okrypterade databaser på samma sätt. Den här proceduren beskrivs i detalj i avsnittet Ansluta till en databas. Du använder open()-metoden om du vill öppna en anslutning i synkront körningsläge och openAsync()-metoden om du vill öppna en anslutning i asynkront körningsläge. Den enda skillnaden när du öppnar en krypterad databas är att du anger korrekt värde för encryptionKey-parametern (open()-metodens femte parameter och openAsync()-metodens sjätte parameter).

Om en felaktig krypteringsnyckel anges returneras ett fel. Om du använder open()-metoden genereras ett SQLError-undantag. Om du använder openAsync()-metoden skickar SQLConnection-objektet ett SQLErrorEvent-objekt, vars error-egenskap innehåller ett SQLError-objekt. I båda fallen har SQLError-objektet som genereras errorID-egenskapsvärdet 3138. Fel-ID:t hör till felmeddelandet "Filen som öppnats är inte en databasfil".

Följande exempel illustrerar hur du öppnar en krypterad databas i asynkront körningsläge. För enkelhetens skull är krypteringsnyckeln hårdkodad i programkoden i det här exemplet. Den här tekniken rekommenderas emellertid inte eftersom den inte är säker.

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

Följande exempel illustrerar hur du öppnar en krypterad databas i synkront körningsläge. För enkelhetens skull är krypteringsnyckeln hårdkodad i programkoden i det här exemplet. Den här tekniken rekommenderas emellertid inte eftersom den inte är säker.

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

Ett exempel som illustrerar en rekommenderad metod för genereringen av en krypteringsnyckel finns i avsnittet Exempel: Generera och använda en krypteringsnyckel..

Ändra krypteringsnyckeln för en databas

Om en databas är krypterad kan du ändra krypteringsnyckeln för databasen vid ett senare tillfälle. Om du vill ändra databasens krypteringsnyckel öppna du först en anslutning till databasen genom att skapa en SQLConnection-instans och anropa dess open()- eller openAsync()-metod. När databasanslutningen har upprättats anropar du reencrypt()-metoden och anger den nya krypteringsnyckeln som ett argument.

Som med de flesta databasåtgärder varierar reencrypt()-metodens beteende beroende på om synkront eller asynkront körningsläge används för databasanslutningen. Om du använder open()-metoden för att ansluta till databasen körs reencrypt()-åtgärden synkront. När åtgärden har slutförts exekveras nästa rad i koden:

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

Om du i stället upprättar databasanslutningen med openAsync()-metoden körs reencrypt()-åtgärden asynkront. Omkrypteringen startar när reencrypt() anropas. När åtgärden slutförts skickar SQLConnection-objektet en reencrypt-händelse. Du använder en händelseavlyssnare för att kontrollera när omkrypteringen är färdig:

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 
}

reencrypt()-åtgärden körs i en egen transaktion. Om åtgärden avbryts eller misslyckas (till exempel om programmet stängs innan åtgärden slutförts) återställs transaktionen. I så fall används den ursprungliga krypteringsnyckeln för databasen.

reencrypt()-metoden kan inte användas för att ta bort krypteringen från en databas. Om ett null-värde eller en krypteringsnyckel som inte är en bytearray på 16 byte skickas till reencrypt()-metoden returneras ett fel.

Att tänka på när du använder kryptering med en databas

I avsnittet Användningsområden för en krypterad databas illustreras olika situationer då du bör använda en krypterad databas. Självklart har användningsscenarierna för olika program (även dessa och andra scenarier) olika sekretesskrav. Hur du strukturerar användningen av eventuell kryptering i programmet avgör i hög grad hur privat informationen i en databas är. Om du till exempel använder en krypterad databas för att skydda personliga data, även från andra användare på samma dator, behöver varje användares databas en egen krypteringsnyckel. Högst säkerhet erhålls om programmet genererar nyckeln från ett lösenord som anges av användaren. Om krypteringsnyckeln baseras på ett lösenord är informationen inte åtkomlig även om någon lyckas personifiera användarens konto på datorn. Andra gånger kanske du tvärtom vill att en databasfil ska kunna läsas av alla som använder programmet, men inte av andra program. I så fall behöver alla installerade kopior av programmet åtkomst till en delad krypteringsnyckel.

Du kan utforma programmet, och teknikerna som används för att generera krypteringsnyckeln, i enlighet med den sekretessnivå som krävs för programinformationen. Följande lista innehåller designförslag för olika sekretessnivåer:

  • Om du vill att en databas ska vara tillgänglig för alla användare som har åtkomst till programmet på alla datorer, använder du en enda nyckel som är tillgänglig för alla programinstanser. Första gången ett program körs kan den delade krypteringsnyckeln hämtas från en server med hjälp av ett säkert protokoll, t.ex. SSL. Därefter kan nyckeln sparas på den krypterade lokala lagringsplatsen för framtida användning. Ett annat alternativ är att kryptera informationen för de enskilda användarna på datorn och synkronisera informationen med ett dataarkiv på en fjärrdator, t.ex. en server, så att informationen blir portabel.

  • Om du vill att databasen ska vara tillgänglig för en enskild användare på alla datorer genererar du krypteringsnyckeln från en användarhemlighet, t.ex. ett lösenord. Generera inte nyckeln baserat på ett värde som är kopplat till en särskild dator (t.ex. ett värde som lagras på den krypterade lokala lagringsplatsen). Ett annat alternativ är att kryptera informationen för de enskilda användarna på datorn och synkronisera informationen med ett dataarkiv på en fjärrdator, t.ex. en server, så att informationen blir portabel.

  • Om du vill att en databas bara ska vara tillgänglig för en särskild användare på en enskild dator genererar du nyckeln från ett lösenord och ett genererat salt-värde. Ett exempel på den här tekniken finns i avsnittet Exempel: Generera och använda en krypteringsnyckel..

Nedan följer ytterligare säkerhetsaspekter som är viktiga att ha i åtanke när du utformar ett program som ska använda en krypterad databas:

  • Ett system är bara så säkert som dess svagaste länk. Om krypteringsnyckeln genereras baserat på ett användardefinierat lösenord bör du definiera krav på komplexitet och minsta tillåtna längd för lösenorden. Ett kort lösenord som bara innehåller vanliga tecken är inte svårt att lista ut.

  • Källkoden för ett AIR-program lagras på en användares dator som oformaterad text (för HTML-innehåll) eller som en binärfil som enkelt kan dekompileras (för SWF-innehåll). Eftersom källkoden är åtkomlig bör du tänka på följande två saker:

    • Hårdkoda aldrig en krypteringsnyckel i källkoden.

    • Förutsätt alltid att tekniken som används för att generera en krypteringsnyckel (t.ex. program som genererar slumpmässiga tecken eller en särskild hash-algoritm) inte innebär några större svårigheter för en angripare.

  • Databaskrypteringen i AIR baseras på AES (Advanced Encryption Standard) med CCM-läge (Counter with CBC-MAC). För att det här krypteringschiffret ska vara säkert krävs en användardefinierad nyckel som kombineras med ett salt-värde. Ett exempel finns i avsnittet Exempel: Generera och använda en krypteringsnyckel..

  • När du väljer att kryptera en databas krypteras alla hårddiskfiler som används av databasmotorn i kombination med den databasen. För att förbättra läs- och skrivtiderna i transaktioner sparas emellertid en del data tillfälligt i ett cacheminne. Informationen i cacheminnet är inte krypterad. Om en angripare lyckas få åtkomst till minnet som används av ett AIR-program, t.ex. med hjälp av en felsökare, kan han eller hon komma åt informationen i en öppen databas som inte är krypterad.

Exempel: Generera och använda en krypteringsnyckel.

Det här exempelprogrammet demonstrerar en teknik som du kan använda för att generera en krypteringsnyckel. Med det här programmet används en hög sekretess- och säkerhetsnivå för att skydda användarnas data. En viktig aspekt vid säkring av privat information är kravet att användaren måste ange ett lösenord varje gång programmet ansluter till databasen. Därför, vilket visas i det här exemplet, ska ett program med den här sekretessnivån aldrig direkt lagra krypteringsnyckeln för databasen.

Programmet består av två delar: en ActionScript-klass som genererar en krypteringsnyckel (klassen EncryptionKeyGenerator), och ett grundläggande användargränssnitt som visar hur den klassen används. Den fullständiga källkoden finns i Fullständig exempelkod för generering och användning av en krypteringsnyckel.

Använda klassen EncryptionKeyGenerator för att få en säker krypteringsnyckel

Det är inte nödvändigt att förstå exakt hur EncryptionKeyGenerator-klassen fungerar för att kunna använda den i programmet. Mer detaljerad information om hur klassen genererar en krypteringsnyckel för databasen finns i EncryptionKeyGenerator-klassen.

Följ de här stegen om du vill använda EncryptionKeyGenerator-klassen i ditt program:

  1. Hämta EncryptionKeyGenerator-klassen som en källkod eller en kompilerad SWC. Klassen EncryptionKeyGenerator ingår i AS3CoreLib-projektet med öppen källkod. Du kan hämta tas3corelib-paketet inklusive källkod och dokumentation. Du kan även hämta SWC-filerna och källkodsfilerna från projektsidan.

  2. Lägg källkoden för EncryptionKeyGenerator-klassen (eller as3corelib-SWC) på en plats som programkällkoden kan hitta.

  3. Lägg till en import-sats i programkällkoden för EncryptionKeyGenerator-klassen.

    import com.adobe.air.crypto.EncryptionKeyGenerator;
  4. Före koden som skapar databasen eller en anslutning till den lägger du till kod för att skapa en EncryptionKeyGenerator-instans genom att anropa konstruktorn EncryptionKeyGenerator().

    var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator();
  5. Hämta ett lösenord från användaren:

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

    EncryptionKeyGenerator-instansen använder det här lösenordet som grund för krypteringsnyckeln (visas i nästa steg). EncryptionKeyGenerator-instansen testar lösenordet mot vissa valideringskrav för starka lösenord. Om valideringen misslyckas inträffar ett fel. Som exempelkoden visar kan du kontrollera lösenordet i förväg genom att anropa EncryptionKeyGenerator-objektets validateStrongPassword()-metod. På det sättet kan du avgöra om lösenordet motsvarar minimikraven för starka lösenord och undvika ett fel.

  6. Generera krypteringsnyckeln från lösenordet:

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

    Metoden getEncryptionKey() genererar och returnerar krypteringsnyckeln (en 16-byte ByteArray). Sedan kan du använda krypteringsnyckeln för att skapa en ny krypterad databas eller öppna en befintlig.

    Lösenordet som hämtas i steg 5 är en obligatorisk parameter i metoden getEncryptionKey().

    Obs! För att få högsta säkerhets- och sekretessnivå för data måste ett program begära att användaren anger ett lösenord varje gång som programmet ansluter till databasen. Lagra inte användarens lösenord eller databasens krypteringsnyckel direkt. Det medför säkerhetsrisker att göra så. I stället, vilket visas i det här exemplet, bör ett program använda samma teknik för att skapa krypteringsnyckeln från lösenordet både när den krypterade databasen skapas och vid senare anslutningar till den.

    Metoden getEncryptionKey() accepterar även en andra (valfri) parameter, parametern overrideSaltELSKey. EncryptionKeyGenerator skapar ett slumpvärde (kallat salt) som används som en del av krypteringsnyckeln. För att kunna återskapa krypteringsnyckeln lagras salt-värdet i ELS-lagret (Encrypted Local Store) i ditt AIR-program. Som standard använder EncryptionKeyGenerator-klassen en speciellt String-värde som ELS-nyckel. Även om det är osannolikt finns en risk för att nyckeln råkar i konflikt med en annan nyckel som används i ditt program. I stället för att använda standardnyckeln kan du ange en egen ELS-nyckel. Om du gör så anger du en egen nyckel genom att skicka den som den andra getEncryptionKey()-parametern, vilket visas här:

    var customKey:String = "My custom ELS salt key"; 
    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password, customKey);
  7. Skapa eller öppna databasen

    Med en krypteringsnyckel som returneras av metoden getEncryptionKey() kan din kod skapa en ny krypterad databas eller försöka att öppna den befintliga krypterade databasen. Oavsett vilket kan du använda SQLConnection-klassens open()- eller openAsync()-metod, vilket beskrivs i Skapa en krypterad databas och Ansluta till en krypterad databas.

    I det här exemplet har programmet utformats att öppna databasen i asynkront körningsläge. Koden aktiverar lämpliga händelseavlyssnare, anropar openAsync()-metoden och anger krypteringsnyckeln som det sista argumentet:

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

    Händelseavlyssnarregistreringarna tas bort i avlyssnarmetoderna. Därefter visas ett statusmeddelande som anger om databasen har skapats, öppnats eller om ett fel har inträffat. Den mest intressanta delen i dessa händelsehanterare finns i openError()-metoden. I den metoden kontrollerar en if-sats om databasen finns (d.v.s. att koden försöker ansluta till en befintlig databas) och om fel-id:t matchar konstanten EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID. Om båda dessa villkor har värdet true betyder det sannolikt att lösenordet som användaren angav är felaktigt. (Det kan även betyda att den angivna filen inte är en databasfil.) Följande kod kontrollerar fel-id:t:

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

    Den fullständiga koden för exemplets händelseavlyssnare finns i Fullständig exempelkod för generering och användning av en krypteringsnyckel.

Fullständig exempelkod för generering och användning av en krypteringsnyckel

Här nedan är den fullständiga koden för exempelprogrammet "Generera och använda en krypteringsnyckel." Koden består av två delar.

I det här exemplet används klassen EncryptionKeyGenerator för att skapa en krypteringsnyckel från ett lösenord. Klassen EncryptionKeyGenerator ingår i AS3CoreLib-projektet med öppen källkod. Du kan hämta tas3corelib-paketet inklusive källkod och dokumentation. Du kan även hämta SWC-filerna och källkodsfilerna från projektsidan.

Flex-exempel

MXML-filen i programmet innehåller källkoden för ett enkelt program som skapar eller öppnar en anslutning till en krypterad databas:

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

Flash Professional-exempel

FLA-filen i programmet innehåller källkoden för ett enkelt program som skapar eller öppnar en anslutning till en krypterad databas. FLA-filen har fyra komponenter som är placerade på scenen:

Instansnamn

Komponenttyp

Beskrivning

instruktioner

Label

Innehåller de instruktioner som lämnats till användaren

passwordInput

TextInput

Inmatningsfält där användaren anger lösenordet

openButton

Button

Knapp som användaren klickar på efter att lösenordet har angetts

statusMsg

Label

Visar statusmeddelanden (fel eller godkänt)

Programmets kod är angiven på en nyckelbildruta på ruta 1 på huvudtidslinjen. Programmets kod följer nedan:

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

EncryptionKeyGenerator-klassen

Det är inte nödvändigt att förstå EncryptionKeyGenerator-klassen i detalj för att använda den till att skapa en säker krypteringsnyckel till din programdatabas. Sättet du använder klassen på förklaras i Använda klassen EncryptionKeyGenerator för att få en säker krypteringsnyckel. Det kan dock vara värdefullt att förstå teknikerna som klassen använder. Du kanske vill anpassa klassen eller införliva vissa av dess tekniker för tillfällen då en annan sekretessnivå är nödvändig.

Klassen EncryptionKeyGenerator ingår i AS3CoreLib-projektet med öppen källkod. Du kan hämta as3corelib-paketet inklusive källkod och dokumentation.Du kan även se källkoden på projektwebbplatsen eller ladda ned den för att arbeta med vägledning av förklaringarna.

När koden skapar en EncryptionKeyGenerator-instans och anropar dess getEncryptionKey()-metod vidtas flera steg för att säkerställa att bara rätt användare kan komma åt data. Processen är samma vid generering av en krypteringsnyckel från ett användarangivet lösenord innan databasen skapas, samt vid återskapande av krypteringsnyckeln för att öppna databasen.

Begära och validera ett starkt lösenord

När koden anropar getEncryptionKey()-metoden skickas ett lösenord som en parameter. Lösenordet används som bas för krypteringsnyckeln. Genom att använda information som bara användaren känner till försäkrar den här designen att endast användare som har tillgång till lösenordet kan komma åt data i databasen. Även om en angripare lyckas komma åt användarens konto på datorn måste han eller hon känna till lösenordet för att få åtkomst till databasen. Som en extra säkerhetsåtgärd lagras lösenordet aldrig av programmet.

Koden för programmet skapar en EncryptionKeyGenerator-instans och anropar metoden getEncryptionKey(), vilket skickar det användarangivna lösenordet som ett argument (variabeln password i det här exemplet):

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

Första steget som EncryptionKeyGenerator-klassen tar när getEncryptionKey()-metoden anropas är att kontrollera det användarangivna lösenordet för att se till att det motsvarar kraven på starka lösenord. EncryptionKeyGenerator-klassen kräver att ett lösenord är 8–32 tecken långt. Lösenordet måste innehålla både versaler och gemener och minst en siffra eller symbol.

Det reguljära uttrycket som kontrollerar det här mönstret är definierat som en konstant som heter STRONG_PASSWORD_PATTERN:

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

Koden som kontrollerar lösenordet finns i EncryptionKeyGenerator-klassens validateStrongPassword()-metod. Koden är som följer:

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

Metoden getEncryptionKey() anropar internt EncryptionKeyGenerator-klassens validateStrongPassword()-metod och genererar ett undantag om lösenordet är ogiltigt. Metoden validateStrongPassword() är en offentlig metod så att programkod kan kontrollera ett lösenord utan att anropa getEncryptionKey()-metoden för att undvika att ett fel inträffar.

Utöka lösenordet till 256 bitar

Längre fram i processen finns instruktioner som anger att lösenordet måste vara 256 bitar långt. I stället för att tvinga varje användare att ange ett lösenord som är exakt 256 bitar (32 tecken) skapar koden ett längre lösenord genom att upprepa tecknen i lösenordet.

Metoden getEncryptionKey() anropar concatenatePassword()-metoden för att den ska skapa det långa lösenordet.

var concatenatedPassword:String = concatenatePassword(password);

Följande kod gäller för concatenatePassword()-metoden:

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

Om lösenordet är kortare än 256 bitar drar koden samman lösenordet (konkatenerar) så att det blir 256 bitar. Om längden inte blir exakt 256 bitar kortas den sista konkateneringen av så att lösenordet blir exakt 256 bitar.

Generera eller hämta ett 256-bitars salt-värde

Nästa steg är att få fram ett 256-bitars salt-värde som i ett senare steg kombineras med lösenordet. Ett salt-värde är ett slumpmässigt värde som läggs till eller kombineras med ett användardefinierat värde för att generera ett lösenord. Även om en användare väljer ett vanligt ord som lösenord garanterar "lösenord och salt-värde"-kombinationen att ett slumpmässigt värde används av systemet. Denna slumpmässighet hjälper till att skydda mot en ordlisteattack, där en angripare använder en lista med ord för att hitta ett lösenord. Genom att generera salt-värdet och lagra det på den krypterade lokala lagringsplatsen kopplas det dessutom till användarens konto på datorn där databasfilen finns.

Om programmet anropar getEncryptionKey()-metoden för första gången skapar koden ett slumpmässigt 256-bitars salt-värde. Annars läser koden in salt-värdet från den krypterade lokala lagringsplatsen.

Salt-värdet lagras i en variabel som heter salt. Koden avgör om ett salt-värde redan har skapats genom att försöka att läsa in salt-värdet från den krypterade lokala lagringsplatsen:

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

Om koden skapar ett nytt salt-värde genererar makeSalt()-metoden ett slumpmässigt 256-bitars värde. Eftersom värdet lagras på den krypterade lokala lagringsplatsen genereras det som ett ByteArray-objekt. makeSalt()-metoden använder Math.random()-metoden för att generera det slumpmässiga värdet. Math.random()-metoden kan inte generera 256 bitar på en gång. I stället används en slinga som anropar Math.random() åtta gånger. Varje gång genereras ett slumpmässigt uint-värde mellan 0 och 4294967295 (det högsta uint-värdet). Ett uint-värde används av praktiska skäl eftersom det innehåller exakt 32 bitar. Ett 256-bitars värde genereras genom att åtta uint-värden skrivs till bytearrayen. Här följer koden för makeSalt()-metoden:

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

När koden sparar salt-värdet till den krypterade lokala lagringsplatsen (ELS) eller hämtar salt-värdet från ELS, behövs en String-nyckel som salt-värdet sparas under. Salt-värdet kan inte hämtas om nyckeln är okänd. Om det är fallet kan inte krypteringsnyckeln återskapas varje gång för att öppna om databasen. Som standard använder EncryptionKeyGenerator en fördefinierad ELS-nyckel som definieras i konstanten SALT_ELS_KEY. I stället för att använda standardnyckeln kan programkoden ange en ELS-nyckel som används i anropet till getEncryptionKey()-metoden. Standardnyckeln eller den programanpassade salt-ELS-nyckeln lagras i en variabel som heter saltKey. Variabeln används i anropen till EncryptedLocalStore.setItem() och EncryptedLocalStore.getItem(), som visades tidigare.

Kombinera 256-bitars lösenordet och salt-värdet med hjälp av XOR-operatorn

Koden har nu ett 256-bitars lösenord och ett 256-bitars salt-värde. Sedan använder det en XOR-åtgärd i bitform för att kombinera salt-värdet och det konkatenerade lösenordet till ett enda värde. Den här tekniken skapar ett 256-bitars lösenord som består av tecken från hela spektrat av möjliga tecken. Den här regeln har värdet true även om det faktiska lösenordet mest sannolikt består av alfanumeriska tecken till största delen. Den här ökade slumpmässigheten har fördelen att uppsättningen möjliga lösenord blir stor utan att användaren måste ange ett långt och komplext lösenord.

Resultatet av XOR-åtgärden lagras i variabeln unhashedKey. Själva processen med att kombinera de båda värdena med hjälp av en XOR-åtgärd sker i xorBytes()-metoden:

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

Den bitvisa XOR-operatorn (^) tar två uint-värden och returnerar ett uint-värde. (Ett uint-värde består av 32 bitar.) Indatavärdena som skickas som argument till xorBytes()-metoden är en sträng (lösenordet) och en bytearray (salt-värdet). En slinga används för att extrahera 32 bitar från varje indatavärde som ska kombineras med hjälp av XOR-operatorn.

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

Inom loopen extraheras de första 32 bitarna (4 byte) från passwordString-parametern. Dessa bitar extraheras och konverteras till ett uint-värde (o1) i en tvåstegsprocess. Först hämtar charCodeAt()-metoden varje teckens numeriska värde. Därefter flyttas värdet till rätt position i uint med hjälp av operatorn för flyttning till vänster (<<) och det flyttade värdet läggs till o1. Det första tecknet (i) blir exempelvis de första 8 bitarna genom att bitarna flyttas 24 bitar åt vänster med hjälp av operatorn för flyttning till vänster (<<) och genom att värdet sedan associeras med o1. Det andra tecknet (i + 1) blir de efterföljande 8 bitarna genom att tillhörande värde flyttas 16 bitar åt vänster och genom att resultatet sedan läggs till o1. Värdena för det tredje och fjärde tecknet läggs till på samma sätt.

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

Variabeln o1 innehåller nu 32 bitar från parametern passwordString. Därefter extraheras 32 bitar från parametern salt genom att dess readUnsignedInt()-metod anropas. De 32 bitarna lagras i uint-variabeln o2.

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

Slutligen kombineras de två 32-bitarsvärdena (uint) med XOR-operatorn och resultatet skrivs till en ByteArray som heter result.

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

När slingan slutförts returneras den bytearray som innehåller XOR-resultatet.

        // ... 
    } 
     
    return result; 
}

Hash-koda nyckeln

När det konkatenerade lösenordet och salt-värdet har kombinerats är nästa steg att ytterligare säkra det här värdet genom att hash-koda det med hash-algoritmen SHA-256. Hash-kodningen försvårar reverse engineering av en potentiell angripare.

I det här skedet har koden en ByteArray som heter unhashedKey och innehåller det konkatenerade lösenordet kombinerat med salt-värdet. Core-biblioteksprojektet för ActionScript 3.0 (as3corelib) innehåller en SHA256-klass i paketet com.adobe.crypto. Metoden SHA256.hashBytes() utför en SHA-256-hash-kodning på en ByteArray och returnerar ett String-värde med hash-kodningsresultatet på 256 bitar som ett hexadecimalt tal. Klassen EncryptionKeyGenerator använder SHA256-klassen för att hash-koda nyckeln:

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

Extrahera krypteringsnyckeln från hashen

Krypteringsnyckeln måste vara en bytearray som är exakt 16 byte (128 bitar) lång. Resultatet av SHA-256-hash-algoritmen är alltid 256 bitar långt. Följaktligen är det sista steget att välja 128 bitar från det hash-kodade resultatet som ska användas som den faktiska krypteringsnyckeln.

I EncryptionKeyGenerator-klassen reducerar koden nyckeln till 128 bitar genom att anropa generateEncryptionKey()-metoden. Sedan returneras metodens resultat som resultatet för getEncryptionKey()-metoden:

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

Du måste inte använda de första 128 bitarna som krypteringsnyckeln. Du kan välja från ett urval bitar med godtycklig startplats, du kan välja alla bitar eller använda ett annat sätt att välja bitar. Det viktiga är att 128 olika bitar väljs, och att samma 128 bitar används varje gång.

I det här fallet använder generateEncryptionKey()-metoden urvalet av bitar med start vid 18:e byte som krypteringsnyckeln. Som vi nämnt tidigare returnerar SHA256-klassen en sträng som innehåller en 256-bitars hash i form av ett hexadecimalt tal. Ett enskilt block med 128 bitar har för många byte för att alla ska kunna läggas till i en bytearray på en gång. Av den anledningen används en for-slinga för att extrahera tecken från den hexadecimala strängen, konvertera dem till numeriska värden och lägga till dem i bytearrayen. Det resulterande String-värdet för SHA-256 är 64 tecken långt. Ett intervall med 128 bitar är lika med 32 tecken i String-värdet och varje tecken representerar 4 bitar. Den minsta dataökningen som du kan lägga till i en bytearray är en byte (8 bitar), vilket motsvarar två tecken i hash-strängen. Slingan räknar följaktligen från 0 till 31 (32 tecken) i steg om 2 tecken.

I slingan börjar koden med att fastställa startpunkten för det aktuella teckenparet. Eftersom vi vill börja intervallet vid tecknet på indexposition 17 (den 18:e byten) tilldelas position-variabeln det aktuella iteratorvärdet (i) plus 17. De två tecknen på den aktuella positionen extraheras med hjälp av strängobjektets substr()-metod. Tecknen lagras i variabeln hex. Därefter används parseInt()-metoden för att konvertera hex-strängen till ett decimalt heltal. Det lagrar det värdet i int-variabeln byte. Till sist lägger koden till värdet i byte till ByteArrayen result genom att använda writeByte()-metoden. När slingan slutförts innehåller bytearrayen result 16 byte och kan användas som krypteringsnyckel för en databas.

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