Uso del cifrado con bases de datos SQL

Adobe AIR 1.5 y posterior

Todas las aplicaciones de Adobe AIR comparten el mismo motor de base de datos local. Por lo tanto, cualquier aplicación de AIR puede conectarse, leer y escribir en un archivo de base de datos no cifrada. Al comenzar con Adobe AIR 1.5, AIR incluye la capacidad de crear y conectarse con archivos de bases de datos cifradas. Cuando se utiliza una base de datos cifrada, con el fin de conectarse a la base de datos una aplicación debe proporcionar la clave de cifrado correcta. Si se indica una clave incorrecta (o no se indica ninguna), la aplicación no podrá conectarse a la base de datos. Por lo tanto, la aplicación no podrá leer información de la base de datos ni escribir o modificar datos.

Para usar una base de datos cifrada, debe crearla como cifrada. Con una base de datos cifrada existente, podrá abrir una conexión a la base de datos. También es posible cambiar la clave de una base de datos cifrada. Además de crear y conectarse a bases de datos cifradas, las técnicas para trabajar con una base de datos cifrada son las mismas que para el trabajo con una base de datos no cifrada. En concreto, la ejecución de declaraciones SQL es la misma independientemente de si una base de datos se encuentra o no cifrada.

Usos de una base de datos cifrada

El cifrado resulta útil cuando se desea restringir el acceso a la información almacenada en una base de datos. La funcionalidad del cifrado de la base de datos de Adobe AIR se puede emplear para varios usos. A continuación se incluyen algunos ejemplos de casos en los que podría resultar adecuado el uso de una base de datos cifrada:

  • Una memoria caché de solo lectura de datos de aplicación privada descargados desde un servidor.

  • Un almacén de aplicación local para datos privados que esté sincronizado con un servidor (los datos se envían al servidor y se descargan desde el mismo).

  • Archivos cifrados utilizados como formato de archivo para documentos creados y editados por la aplicación. Los archivos pueden ser privados para un usuario, o bien, pueden estar diseñados para compartirse entre todos los usuarios de la aplicación.

  • Cualquier otro uso de un almacén de datos local como, por ejemplo, los que se describen en Usos de las bases de datos SQL locales , donde los datos deben ser privados para los usuarios que disponen de acceso al equipo o a los archivos de la base de datos.

Conocer el motivo por el que se desea utilizar una base de datos cifrada ayuda a decidir la forma en que se plantea la arquitectura de la aplicación. En concreto, puede afectar al modo en que la aplicación crea, obtiene o almacena la clave de cifrado para la base de datos. Para obtener más información sobre estos puntos, consulte Consideraciones para el uso del cifrado con una base de datos .

Además de una base de datos cifrada, un mecanismo alternativo para proteger la privacidad de datos importantes es el almacén local cifrado . Con el almacén local cifrado, se almacena un solo valor ByteArray utilizando una clave String. Únicamente la aplicación de AIR que almacenó el valor puede acceder al mismo y solo en el equipo en el que está almacenado. Con el almacén local cifrado, no es necesario crear una clave de cifrado propia. Por estos motivos, el almacén local cifrado resulta más adecuado para almacenar fácilmente un solo valor o conjunto de valores que se pueden codificar con facilidad en un objeto ByteArray. Una base de datos cifrada es más apropiada para conjuntos de datos más grandes donde el almacenamiento de datos estructurados y las consultas resultan más recomendables. Para obtener más información sobre el uso del almacén local cifrado, consulte Almacenamiento local cifrado .

Creación de una base de datos cifrada

Para poder utilizar una base de datos cifrada, el archivo de base de datos debe estar cifrado cuando se cree. Una vez que una base de datos se crea como no cifrada, no puede cifrarse posteriormente. Asimismo, una base de datos cifrada no puede encontrarse sin cifrar más adelante. Si es necesario, puede cambiar la clave de cifrado de una base de datos cifrada. Para obtener más información, consulte Cambio de la clave de cifrado de una base de datos . Si dispone de una base de datos existente que no está cifrada y desea usar el cifrado de base de datos, puede crear una nueva base de datos cifrada y copiar la estructura de tabla existente y la información en la nueva base de datos.

La creación de una base de datos cifrada es un proceso casi idéntico a la creación de una base de datos no cifrada, tal y como se describe en Creación de una base de datos . En primer lugar se crea una instancia de SQLConnection que representa la conexión con la base de datos. La base de datos se crea llamando al método open() del objeto SQLConnection o al método openAsync() , especificando para la ubicación de la base de datos un archivo que aún no exista. La única diferencia al crear una base de datos cifrada es que se proporciona un valor para el parámetro encryptionKey (el quinto parámetro del método open() y el sexto parámetro del método openAsync() ).

Un valor del parámetro encryptionKey válido es un objeto ByteArray que contiene exactamente 16 bytes. http://help.adobe.com/es_ES/Flash/CS5/AS3LR/flash/utils/ByteArray.html

Los siguientes ejemplos muestran la creación de una base de datos cifrada. Para que resulte más fácil, en estos ejemplos la clave de cifrado está programada en el código de la aplicación. Sin embargo, el uso de esta técnica no es recomendable porque no es segura.

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

Para obtener un ejemplo que muestre un modo recomendado de generar una clave de cifrado, consulte Ejemplo: Generación y uso de claves de cifrado .

Conexión con una base de datos cifrada

Al igual en sucede en la creación de una base de datos cifrada, el procedimiento para abrir una conexión a una base de datos cifrada es como la conexión a una base de datos no cifrada. Dicho procedimiento se describe con más detalle en Conexión a una base de datos . El método open() se utiliza para abrir una conexión en modo de ejecución sincrónico o el método o el método openAsync() para abrir una conexión en modo de ejecución asíncrono . La única diferencia radica en que para abrir una base de datos cifrada, se especifica el valor correcto para el parámetro encryptionKey (el quinto parámetro del método open() y el sexto parámetro del método openAsync() ).

Si la clave de cifrado que se proporciona no es correcta, se generará un error. Para el método open() , se emite una excepción SQLError. http://help.adobe.com/es_ES/Flash/CS5/AS3LR/flash/errors/SQLError.html Para el método openAsync() , el objeto SQLConnection distribuye SQLErrorEvent , cuya propiedad error contiene un objeto SQLError . En cualquier caso, el objeto SQLError generado mediante la excepción cuenta con el valor de propiedad errorID 3138. Este ID de error se corresponde con el mensaje de error “File opened is not a database file” (El archivo abierto no es un archivo de base de datos).

En el siguiente ejemplo se muestra la apertura de una base de datos cifrada en modo de ejecución asíncrono. Para que resulte más fácil, en este ejemplo la clave de cifrado está programada en el código de la aplicación. Sin embargo, el uso de esta técnica no es recomendable porque no es segura.

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

En el siguiente ejemplo se muestra la apertura de una base de datos cifrada en modo de ejecución sincrónico. Para que resulte más fácil, en este ejemplo la clave de cifrado está programada en el código de la aplicación. Sin embargo, el uso de esta técnica no es recomendable porque no es segura.

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

Para obtener un ejemplo que muestre un modo recomendado de generar una clave de cifrado, consulte Ejemplo: Generación y uso de claves de cifrado .

Cambio de la clave de cifrado de una base de datos

Cuando se cifra una base de datos, es posible cambiar su clave de cifrado con posterioridad. Para cambiar una clave de cifrado de la base de datos, en primer lugar abra una conexión con la base de datos, creando una instancia de SQLConnection llamando a su método open() o openAsync() . Una vez que la base de datos esté conectada, llame al método reencrypt() , transmitiendo la nueva clave de cifrado como argumento.

Al igual que sucede en la mayoría de las operaciones de bases de datos, el comportamiento del método reencrypt() varía dependiendo de si la conexión de base de datos utiliza el modo de ejecución sincrónico o asíncrono. Si se utiliza el método open() para realizar la conexión a la base de datos, la operación reencrypt() se ejecuta de forma sincrónica. Cuando se completa la operación, la ejecución continúa con la siguiente línea de código:

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

Por otra parte, si la conexión de base de datos se abre usando el método openAsync() , la operación reencrypt() es asíncrona. Al llamar a reencrypt() , comienza el proceso de nuevo cifrado. Cuando la operación finaliza, el objeto SQLConnection distribuye un evento reencrypt . El detector de eventos se emplea para determinar cuándo finaliza el nuevo cifrado:

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 
}

La operación reencrypt() se ejecuta en su propia transacción. Si la operación se interrumpe o falla (por ejemplo, si la aplicación se cierra antes de que finalice la operación), la transacción se deshace. En este caso, la clave de cifrado original es aún la clave de cifrado para la base de datos.

El método reencrypt() no se puede utilizar para eliminar el cifrado de una base de datos. La transmisión de un valor null o de una clave de cifrado que no es un objeto ByteArray de 16 bytes al método reencrypt() , genera un error.

Consideraciones para el uso del cifrado con una base de datos

En la sección Usos de una base de datos cifrada se presentan varios casos en los que sería adecuado el uso de una base de datos cifrada. Resulta evidente que los escenarios de uso de diferentes aplicaciones (incluyendo estos y otros escenarios) cuentan con requisitos de privacidad distintos. La forma en que se plantea la arquitectura del uso del cifrado en cada aplicación representa una parte importante en el control del nivel de privacidad de la información de una base de datos. Por ejemplo, si está utilizando una base de datos cifrada parar proteger la privacidad de datos personales, incluso de otros usuarios del mismo equipo, la base de datos de cada usuario necesita su propia clave de cifrado. Para obtener la máxima seguridad, su aplicación puede generar una clave a partir de una contraseña introducida por el usuario. Si la clave de cifrado se basa en una contraseña, se garantiza que aunque otra persona pueda suplantar la cuenta del usuario en el equipo, no sea posible acceder a los datos. En el otro extremo del espectro de la privacidad, supongamos que desea que cualquier usuario de su aplicación, pero no de otras aplicaciones, pueda leer un archivo de la base de datos. En este caso, todas las copias instaladas de la aplicación necesitan acceso a una clave de cifrado compartida.

La aplicación se puede diseñar, y en concreto la técnica utilizada para generar la clave de cifrado, según el nivel de privacidad que se desee para los datos de la aplicación. En la siguiente lista se incluyen sugerencias de diseño para distintos niveles de privacidad de datos:

  • Para que cualquier usuario que tenga acceso a la aplicación pueda acceder a una base de datos en cualquier equipo, utilice una sola clave que esté disponible en todas las instancias de la aplicación. Por ejemplo, la primera vez que se ejecute una aplicación, puede descargar la clave de cifrado compartida de un servidor mediante un protocolo seguro como, por ejemplo, SSL. Después puede guardar la clave en el almacén local cifrado para su uso posterior. Como alternativa, cifre los datos por usuario en el equipo y sincronícelos con un almacén de datos remoto como, por ejemplo, un servidor para hacer que la información sea portátil.

  • Para que un solo usuario pueda acceder a una base de datos en cualquier equipo, genere la clave de cifrado a partir de un secreto de usuario (por ejemplo, una contraseña). En especial, no utilice ningún valor que esté asociado a un equipo concreto (por ejemplo, un valor almacenado en el almacén local cifrado) para generar la clave. Como alternativa, cifre los datos por usuario en el equipo y sincronícelos con un almacén de datos remoto como, por ejemplo, un servidor para hacer que la información sea portátil.

  • Para que únicamente un usuario concreto pueda acceder a una base de datos en un solo equipo, genere la clave a partir de una contraseña y un valor salt generado. Para ver un ejemplo de esta técnica, consulte Ejemplo: Generación y uso de claves de cifrado .

A continuación se incluyen consideraciones sobre seguridad adicionales que son importantes tener en cuenta a la hora de diseñar una aplicación que utilice una base de datos cifrada:

  • Un sistema solo es seguro en su vínculo más débil. Si usa una contraseña introducida por el usuario para generar una clave de cifrado, tenga en cuenta la aplicación de restricciones de complejidad y longitud mínima en las contraseñas. Una contraseña corta que solo utilice caracteres básicos se puede adivinar rápidamente.

  • El código fuente de una aplicación de AIR se almacena en un equipo de usuario en texto sin formato (para contenido HTML), o bien, en un formato binario que se puede descompilar (para contenido SWF). Debido a que es posible acceder al código fuente, se deben recordar dos puntos:

    • No programe nunca una clave de cifrado en el código fuente.

    • Se debe tener siempre en cuenta que la técnica empleada para generar una clave de cifrado (por ejemplo, generador de caracteres aleatorios o un algoritmo hash concreto) puede ser desvelada fácilmente por un atacante.

  • El cifrado de base de datos de AIR utiliza el algoritmo AES (Advanced Encryption Standard, estándar de cifrado avanzado) con modo de contador con CBC-MAC (CCM). Este sistema de cifrado requiere que una clave introducida por el usuario se combine con un valor salt para ser segura. Para ver un ejemplo, consulte Ejemplo: Generación y uso de claves de cifrado .

  • Si opta por cifrar una base de datos, se cifrarán todos los archivos de disco utilizados por el motor de la base de datos junto con dicha base de datos. Sin embargo, el motor de base de datos retiene algunos datos temporalmente en una caché de memoria interna para mejorar el rendimiento de lectura y escritura en las transacciones. Los datos residentes en memoria no están cifrados. Si un atacante puede acceder a la memoria utilizada por una aplicación de AIR (por ejemplo, mediante un depurador), la información de una base de datos que esté abierta y sin cifrar actualmente podría quedar expuesta.

Ejemplo: Generación y uso de claves de cifrado

Esta aplicación de ejemplo muestra una técnica para generar una clave de cifrado. Esta aplicación está diseñada para proporcionar el nivel más elevado de privacidad y seguridad para los datos de los usuarios. Un aspecto importante a la hora de asegurar los datos privados radica en solicitar al usuario que introduzca una contraseña cada vez que la aplicación se conecta a la base de datos. Por lo tanto, tal y como se muestra en este ejemplo, una aplicación que requiere este nivel de privacidad nunca debe almacenar directamente la clave de cifrado de la base de datos.

La aplicación consta de dos partes: una clase ActionScript que genera una clave de cifrado (clase EncryptionKeyGenerator) y una interfaz de usuario básica que muestra cómo utilizar esa clase. Para obtener el código fuente completo, consulte Código de ejemplo completo para generar y utilizar una clave de cifrado .

Uso de la clase EncryptionKeyGenerator para obtener una clave de cifrado segura

No es necesario comprender los detalles del funcionamiento de la clase EncryptionKeyGenerator para utilizarla en la aplicación. Si está interesado en los detalles del modo en que la clase genera una clave de cifrado para una base de datos, consulte Aspectos básicos de la clase EncryptionKeyGenerator .

Siga los siguientes pasos para usar la clase EncryptionKeyGenerator en su aplicación:

  1. Descargue la clase EncryptionKeyGenerator como código fuente o archivo SWC compilado. La clase EncryptionKeyGenerator se incluye en el proyecto (as) de biblioteca principal de ActionScript 3.0 de código abierto (3corelib). Puede descargar el paquete as3corelib, incluyendo el código fuente y la documentación . También puede descargar el archivo SWC o los archivos de código fuente desde la página del proyecto.

  2. Sitúe el código fuente de la clase (o el archivo SWC as3corelib) en una ubicación donde el código fuente de la aplicación pueda encontrarla.

  3. En el código fuente de la aplicación, añada una declaración import para la clase EncryptionKeyGenerator.

    import com.adobe.air.crypto.EncryptionKeyGenerator;
  4. Antes del punto donde el código crea la base de datos o abre una conexión con la misma, añada código para crear una instancia de EncryptionKeyGenerator, llamando al constructor EncryptionKeyGenerator() .

    var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator();
  5. Obtenga una contraseña del usuario:

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

    La instancia de EncryptionKeyGenerator utiliza esta contraseña como base para la clave de cifrado (se muestra en el siguiente paso). La instancia de EncryptionKeyGenerator comprueba la contraseña con determinados requisitos de validación de contraseña segura. Si la validación falla, se produce un error. Tal y como muestra el código de ejemplo, puede comprobar la contraseña con anterioridad llamando al método validateStrongPassword() del objeto EncryptionKeyGenerator. De este modo, puede determinar si la contraseña cumple con los requisitos mínimos de una contraseña segura y evitar un error.

  6. Genere la clave de cifrado a partir de la contraseña:

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

    El método getEncryptionKey() genera y devuelve la clave de cifrado(ByteArray de 16 bytes). Posteriormente la clave de cifrado se puede utilizar para crear una nueva base de datos cifrada o abrir una existente.

    El método getEncryptionKey() cuenta con un parámetro necesario, que es la contraseña obtenida en el paso 5.

    Nota: para mantener el nivel más alto de seguridad y privacidad de los datos, una aplicación debe solicitar al usuario que indique una contraseña cada vez que la aplicación se conecte a la base de datos. No almacene la contraseña de usuario ni la clave de cifrado de la base de datos directamente. De este modo se expone a riesgos de seguridad. Tal y como se muestra en este ejemplo, una aplicación debe emplear la misma técnica para derivar la clave de cifrado de la contraseña, tanto al crear la base de datos cifrada como al conectarse a ella más adelante.

    El método getEncryptionKey() también acepta un segundo parámetro(opcional); el parámetro overrideSaltELSKey . EncryptionKeyGenerator crea un valor aleatorio (denominado valor salt ) que se usa como parte de la clave de cifrado. Para poder volver a crear la clave de cifrado, el valor salt se almacena en el almacén local cifrado (ELS) de la aplicación de AIR. De forma predeterminada, la clase EncryptionKeyGenerator utiliza una cadena concreta como clave de ELS. Aunque es poco probable, es posible que la clave pueda entrar en conflicto con otra clave que utilice la aplicación. En lugar de utilizar la clave predeterminada, puede que desee especificar su propia clave de ELS. En este caso, especifique una clave personalizada transmitiéndola como segundo parámetro getEncryptionKey() , tal y como se muestra a continuación:

    var customKey:String = "My custom ELS salt key"; 
    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password, customKey);
  7. Creación o apertura de la base de datos

    Con una clave de cifrado devuelta por el método getEncryptionKey() , el código puede crear una nueva base de datos cifrada o intentar abrir la base de datos cifrada existente. En cualquier caso, se utiliza el método open() u openAsync() de la clase SQLConnection, tal y como se describe en Creación de una base de datos cifrada y Conexión con una base de datos cifrada .

    En este ejemplo, la aplicación está diseñada para abrir la base de datos en modo de ejecución asíncrono. El código establece los detectores de eventos apropiados y llama al método openAsync() , transmitiendo la clave de cifrado como argumento final:

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

    En los métodos del detector, el código elimina los registros del detector de eventos. Después muestra un mensaje de estado que indica si se creó o se abrió la base de datos o si se produjo un error. La parte más notable de estos controladores de eventos está en el método openError() . En ese método, una declaración if comprueba si existe la base de datos (lo que significa que el código está intentando conectarse a una base de datos existente) y si el ID de error coincide con la constante EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID . Si ambas condiciones se definen como true, probablemente significa que la contraseña introducida por el usuario no es correcta. (También podría significar que el archivo especificado no es un archivo de base de datos.) A continuación se muestra el código que comprueba el ID de error:

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

    Para obtener el código completo de los detectores de eventos de ejemplo, consulte Código de ejemplo completo para generar y utilizar una clave de cifrado .

Código de ejemplo completo para generar y utilizar una clave de cifrado

A continuación se muestra el código completo para la aplicación de ejemplo “Generación y uso de claves de cifrado.” El código consta de dos partes.

El ejemplo utiliza la clase EncryptionKeyGenerator para crear una clave de cifrado a partir de una contraseña. La clase EncryptionKeyGenerator se incluye en el proyecto (as) de biblioteca principal de ActionScript 3.0 de código abierto (3corelib). Puede descargar el paquete as3corelib, incluyendo el código fuente y la documentación . También puede descargar el archivo SWC o los archivos de código fuente desde la página del proyecto.

Ejemplo de Flex

El archivo MXML de la aplicación contiene el código fuente para una aplicación sencilla que crea o abre una conexión con una base de datos cifrada:

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

Ejemplo de Flash Professional

El archivo FLA de la aplicación contiene el código fuente para una aplicación sencilla que crea o abre una conexión con una base de datos cifrada. El archivo FLA dispone de cuatro componentes situados en el escenario:

Nombre de la instancia

Tipo de componente

Descripción

instructions

Label

Contiene las instrucciones dadas al usuario.

passwordInput

TextInput

Campo de entrada donde el usuario indica la contraseña.

openButton

Button

Botón en el que el usuario hace clic una vez introducida la contraseña.

statusMsg

Label

Muestra mensajes de estado (errores u operaciones correctas).

El código para la aplicación se define en el fotograma clave del fotograma 1 de la línea de tiempo principal. A continuación se incluye el código para la aplicación:

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

Aspectos básicos de la clase EncryptionKeyGenerator

No es necesario comprender el funcionamiento interno de la clase EncryptionKeyGenerator para utilizarla para crear una clave de cifrado segura para la base de datos de la aplicación. El proceso de uso de la clase se explica en Uso de la clase EncryptionKeyGenerator para obtener una clave de cifrado segura . Sin embargo, puede que le resulte útil conocer las técnicas que utiliza esta clase. Por ejemplo, puede desear adaptar la clase o incorporar algunas de sus técnicas para situaciones donde se requiere un nivel diferente de privacidad de datos.

La clase EncryptionKeyGenerator se incluye en el proyecto (as) de biblioteca principal de ActionScript 3.0 de código abierto (3corelib). Puede descargar el paquete as3corelib, incluyendo el código fuente y la documentación . También puede ver el código fuente en el sitio del proyecto o descargarlo junto con las explicaciones.

Si el código crea una instancia de EncryptionKeyGenerator y llama a su método getEncryptionKey() , se llevan a cabo varios pasos para garantizar que únicamente el usuario adecuado pueda acceder a los datos. El proceso es el mismo para generar una clave de cifrado a partir de la contraseña introducida por el usuario antes de que se cree la base de datos, así como para volver a crear la clave de cifrado para abrir la base de datos.

Obtención y validación de una contraseña segura

Cuando el código llama al método getEncryptionKey() , transmite una contraseña como parámetro. La contraseña se debe usar como base para la clave de cifrado. Con el uso de una parte de información que solo conoce el usuario, este diseño garantiza que únicamente el usuario que conoce la contraseña pueda acceder a la información de la base de datos. Aunque un atacante acceda a la cuenta del usuario en el equipo, el atacante no podrá tener acceso a la base de datos sin conocer la contraseña. Para obtener la máxima seguridad, la aplicación nunca almacena la contraseña.

El código de una aplicación crea una instancia de EncryptionKeyGenerator y llama a su método getEncryptionKey() , pasando una contraseña introducida por el usuario como argumento (variable password en este ejemplo):

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

El primer paso que adopta la clase EncryptionKeyGenerator cuando se llama al método getEncryptionKey() consiste en comprobar la contraseña introducida por el usuario para garantizar que cumple con los requisitos de longitud de contraseña. La clase EncryptionKeyGenerator requiere que una contraseña incluya de 8 a 32 caracteres. La contraseña debe incluir una mezcla de letras mayúsculas y minúsculas y al menos un número o carácter de símbolos.

La expresión regular que comprueba este modelo se define como una constante denominada STRONG_PASSWORD_PATTERN :

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

El código que comprueba la contraseña se encuentra en el método validateStrongPassword() de la clase EncryptionKeyGenerator. El código se presenta del siguiente modo:

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

Internamente, el método getEncryptionKey() llama a validateStrongPassword() de la clase EncryptionKeyGenerator y, si la contraseña no es válida, emite una excepción. validateStrongPassword() es un método público, por lo que el código de la aplicación puede comprobar una contraseña sin llamar al método getEncryptionKey() para evitar que se produzca un error.

Ampliación de la contraseña a 256 bits

Más adelante en el proceso, es necesario que la contraseña tenga una longitud de 256 bits. En lugar de requerir que cada usuario indique una contraseña que sea exactamente de una longitud de 256 bits (32 caracteres), el código crea una contraseña más larga mediante la repetición de sus caracteres.

El método getEncryptionKey() llama al método concatenatePassword() para llevar a cabo la tarea de crear la contraseña larga.

var concatenatedPassword:String = concatenatePassword(password);

A continuación se incluye el código para el método 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 la contraseña es inferior a 256 bits, el código concatena la contraseña con sí misma para que tenga 256. Si la longitud no es la deseada exactamente, la última repetición se reduce para que se obtengan justo 256 bits.

Generación o recuperación de un valor salt de 256 bits

El siguiente paso consiste en obtener un valor salt de 256 bits que en un paso posterior se combina con la contraseña. Un valor salt es un valor aleatorio que se añade o se combina con un valor introducido por el usuario para formar una contraseña. El uso de un valor salt con una contraseña asegura que aunque un usuario elija una palabra real o un término común como contraseña, la combinación de contraseña y valor salt que utiliza el sistema sea un valor aleatorio. Esto ayuda de forma aleatoria en la protección frente a un ataque de diccionario, donde el atacante utiliza una lista de palabras para intentar adivinar una contraseña. Asimismo, con la generación del valor salt y su almacenamiento en el almacén local cifrado, se asocia a la cuenta del usuario en el equipo en el que se ubica el archivo de base de datos.

Si la aplicación llama al método getEncryptionKey() por primera vez, el código crea un valor salt aleatorio de 256 bits. De lo contrario, el código carga el valor salt desde el almacén local cifrado.

El valor salt se almacena en una variable denominada salt . El código determina si ya se ha creado un valor salt, intentando cargar el valor desde el almacén local cifrado:

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

Si el código crea un nuevo valor salt, el método makeSalt() genera un valor aleatorio de 256 bits. Debido a que el valor se almacena finalmente en el almacén local cifrado, se genera como un objeto ByteArray. El método makeSalt() utiliza el método Math.random() para generar el valor de forma aleatoria. El método Math.random() no puede generar 256 bits de una sola vez. El código utiliza un bucle para llamar a Math.random() ocho veces. Cada vez, se genera un valor uint aleatorio entre 0 y 4294967295 (valor uint máximo). El valor uint se utiliza para mayor comodidad, ya que un valor de este tipo usa exactamente 32 bits. Al escribir ocho valores uint en ByteArray, se genera un valor de 256 bits. A continuación se incluye el código para el método 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; 
}

Cuando el código guarda el valor salt en el almacén local cifrado (ELS) o lo recupera desde ELS, necesita una clave String bajo la que se guarda el valor salt. Si no se conoce la clave, el valor salt no podrá recuperarse. En este caso, la clave de cifrado no puede volver a crearse cada vez que se vuelva a abrir la base de datos. De forma predeterminada, EncryptionKeyGenerator utiliza una clave de ELS predefinida que se define en la constante SALT_ELS_KEY . En lugar de emplear la clave predeterminada, el código de la aplicación también puede especificar una clave de ELS para utilizarla en la llamada al método getEncryptionKey() . Tanto la clave predeterminada como la clave de ELS del valor salt especificada por la aplicación se almacenan en una variable denominada saltKey . Esa variable se utiliza en las llamadas a EncryptedLocalStore.setItem() y EncryptedLocalStore.getItem() , tal y como se ha mostrado anteriormente.

Combinación de la contraseña de 256 bits y el valor salt utilizando el operador XOR

El código cuenta ahora con una contraseña de 256 bits y un valor salt de 256 bits. A continuación utiliza una operación XOR en modo bit para combinar el valor salt y la contraseña concatenada en un solo valor. Realmente, esta técnica crea una contraseña de 256 bits que consta de caracteres procedentes del intervalo completo de caracteres posibles. Este principio es verdadero, aunque es más probable que la entrada de contraseña real conste de caracteres alfanuméricos principalmente. Con este grado aumentado de aleatoriedad se obtiene la ventaja de crear el conjunto de posibles contraseñas largas sin que el usuario deba introducir una contraseña extensa y compleja.

El resultado de la operación XOR se almacena en la variable unhashedKey . El proceso real de realizar una operación XOR en modo bit en los dos valores se produce en el método xorBytes() .

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

El operador XOR en modo bit ( ^ ) adopta dos valores uint y devuelve un valor de este tipo. (Un valor uint contiene 32 bits.) Los valores input transmitidos como argumentos al método xorBytes() son un elemento String (contraseña) y un objeto ByteArray (valor salt). Por lo tanto, el código utiliza un bucle para extraer 32 bits cada vez de cada entrada para combinar utilizando el operador 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; 
}

En el bucle, en primer lugar se extraen 32 bits (4 bytes) del parámetro passwordString . Estos bits se extraen y se convierten en un valor uint ( o1 ) en un proceso de dos partes. En primer lugar, el método charCodeAt() obtiene el valor numérico de cada carácter. A continuación, el valor se desplaza a la posición adecuada en el valor uint utilizando el operador de desplazamiento a la izquierda en modo bit ( << ) y el valor desplazado se añade a o1 . Por ejemplo, el primer carácter ( i ) pasa a ser los primeros 8 bits, utilizando el operador de desplazamiento a la izquierda en modo bit ( << ) para desplazar los bits a la izquierda 24 bits y asignado este valor a o1 . El segundo carácter (i + 1 ) pasa a ser los segundos 8 bits, desplazando su valor a la izquierda 16 bits y añadiendo el resultado a o1 . Los valores de los caracteres tercero y cuarto se agregan del mismo modo.

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

La variable o1 contiene ahora 32 bits a partir del parámetro passwordString . Posteriormente, se extraen 32 bits del parámetro salt llamando a su método readUnsignedInt() . Los 32 bits se almacenan en la variable uint o2 .

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

Finalmente, los dos valores (uint) de 32 bits se combinan mediante el operador XOR y el resultado se escribe en un objeto ByteArray llamado result .

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

Una vez completado el bucle, se devuelve el objeto ByteArray que contiene el resultado XOR.

        // ... 
    } 
     
    return result; 
}

Operaciones hash de la clave

Una vez combinados el valor salt y la contraseña concatenada, el siguiente paso consiste en asegurar este valor incluyéndolo en hash mediante el algoritmo hash SHA-256. Al incluir en hash el valor, resulta más difícil para un atacante aplicarle una ingeniería inversa.

En este momento el código cuenta con un elemento ByteArray denominado unhashedKey que contiene la contraseña concatenada combinada con el valor salt. El proyecto de biblioteca principal (as3corelib) de ActionScript 3.0 incluye una clase SHA256 en el paquete com.adobe.crypto. El método SHA256.hashBytes() que realiza un algoritmo hash SHA-256 en ByteArray y devuelve una cadena que contiene el resultado hash de 256 bits como número hexadecimal. El código utiliza la clase EncryptionKeyGenerator para realizar la operación de hash de la clave:

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

Extracción de la clave de cifrado del hash

La clave de cifrado debe ser un objeto ByteArray que cuente exactamente con 16 bytes (128 bits). El resultado del algoritmo hash SHA-256 siempre es 256 bits. Por lo tanto, en el paso final se seleccionan 128 bits del resultado de la operación de hash para utilizar como clave de cifrado real.

En la clase EncryptionKeyGenerator, el código reduce la clave a 128 bits llamando al método generateEncryptionKey() : Después devuelve el resultado de ese método como resultado de getEncryptionKey() :

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

No es necesario utilizar los primeros 128 bits como clave de cifrado. Puede seleccionar un intervalo de bits comenzando en un punto arbitrario, puede optar por cualquier otro bit, o bien, usar otro modo de seleccionar bits. Lo más importante es que el código seleccione 128 bits distintos y que los mismos 128 bits se utilicen cada vez.

En este caso, el método generateEncryptionKey() usa el intervalo de bits que comienza en el byte en posición 18 como clave de cifrado. Tal y como se mencionó anteriormente, la clase SHA256 devuelve una cadena que contiene un hash de 256 bits como número hexadecimal. Un solo bloque de 128 bits tiene demasiados bytes para añadir a un elemento ByteArray a la vez. Por ello, el código utiliza un bucle for para extraer caracteres de la cadena hexadecimal, convertirlos en valores numéricos reales y agregarlos a ByteArray. La cadena de resultado de SHA-256 tiene 64 caracteres. Un intervalo de 128 bits es igual a 32 caracteres en la cadena, por lo que cada carácter representa 4 bits. El incremento más bajo de datos que se puede agregar a ByteArray es un byte (8 bits), lo que equivale a dos caracteres en la cadena hash . Por lo tanto, el bucle cuenta de 0 a 31 (32 caracteres) en incrementos de 2 caracteres.

En el bucle, el código determina en primer lugar la posición de inicio del par actual de caracteres. Debido a que el intervalo deseado comienza en el carácter en la posición de índice 17 (byte 18), a la variable position se le asigna el valor del iterador actual ( i ) más 17. El código utiliza el método substr() del objeto String para extraer los dos caracteres en la posición actual. Estos caracteres se almacenan en la variable hex . A continuación, el código utiliza el método parseInt() para convertir la cadena hex en un valor entero decimal. Almacena este valor en la variable int byte . Finalmente, el código agrega el valor en byte al objeto ByteArray result utilizando su método writeByte() . Cuando finaliza el bucle, el objeto ByteArray result contiene 16 bytes y está listo para utilizarse como clave de cifrado de la base de datos.

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