Adobe AIR offers several ways to store data locally. You have
the legacy option of storing data as a shared object, you can also access
the local file system to read and write data, and finally you can write to
an embedded SQLite database. When it comes to securing pieces of data like
passwords or access keys, your best option is to use the built-in encrypted
data store, defined by the EncryptedLocalStore
class.
If you need to secure sets of structured data, your best option is to use the embedded SQLite database encryption support covered in Creating an Encrypted Database and Encrypting a Database with a Password.
The data within this store is housed within DPAPI on Windows and within Keychain on Mac OS X using AES-CBC 128-bit encryption.
Once data is written to the encrypted local store, it is accessible
only by the application that wrote the data. In addition, the data is stored
in a user-specific directory, so each user on the operating system will have
a different encrypted local store for the application. For even more
restrictive security on data, the encrypted local store also offers an
option that ensures that any application attempting to get data from
EncryptedLocalStore
not only has
the correct publisher ID but also has had no changes made to the application
directory.
The recipes in this chapter will demonstrate how to store and retrieve
data from EncryptedLocalStore
.
You would like your application to store a serial number that will be used to verify the user with your remote server for data retrieval. It is critical that this information be private and secure.
Use the EncryptedLocalStore
class, which is the built-in storage solution within AIR for persisting
secure information.
The EncryptedLocalStore
class
includes methods to save, retrieve, and remove data from the secure
local store. To store data to the encrypted local store, you must first
convert it to binary. To convert the data to binary, you can use the
ByteArray
class. The ByteArray
class
is located within the flash.utils
package and contains methods to read and write data of many different
data types to the byte stream.
To convert a simple piece of String
data to a ByteArray
, you can use the writeUTFBytes
method. The following simple
example takes a variable serialNumber
and converts it to a ByteArray
using the writeUTFBytes
method of the ByteArray
class. For example:
var serialNumber:String = "0000-1234-7777-9876" var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(serialNumber);
Now that you have the data in the format needed to save to the encrypted local store, you can complete this simple example by including a fourth line of code, which sets the bytes into the encrypted local store:
EncryptedLocalStore.setItem("serialNumber", bytes);
The following full example shows a simple form where a user can enter a serial number and persist it to the encrypted local store:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <![CDATA[ private function saveToLocalStore():void{ var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(serialNumberTxt.text); EncryptedLocalStore.setItem("serialNumber", bytes); } ]]> </mx:Script> <mx:HBox horizontalCenter="0" y="134"> <mx:Label text="Serial Number"/> <mx:TextInput id="serialNumberTxt"/> <mx:Button label="Save to the encrypted local store" click="saveToLocalStore()"/> </mx:HBox> </mx:WindowedApplication>
To convert a simple piece of String
data to a ByteArray
, you can use the writeUTFBytes
method. The following simple
example takes a variable serialNumber
and converts it to a ByteArray
using the writeUTFBytes
method of the ByteArray
class:
var serialNumber = "0000-1234-7777-9876" var bytes = new air.ByteArray(); bytes.writeUTFBytes(serialNumber);
Now that you have the data in the format needed to save to the encrypted local store, you can complete this simple example by including a fourth line of code, which sets the bytes into the encrypted local store:
air.EncryptedLocalStore.setItem("serialNumber ", bytes);
The following full example shows a simple form where a user can enter a serial number and persist it to the encrypted local store:
<html> <head> <script src="AIRAliases.js" /> <script> function saveToLocalStore(){ var bytes = new air.ByteArray(); bytes.writeUTFBytes(document.theform.serialNumberTxt.value); air.EncryptedLocalStore.setItem("serialNumber", bytes); } </script> </head> <body> <form name="theform"> Serial Number: <input type="text" size="20" name="serialNumberTxt"/> <input type="button" value="Save to the encrypted local store" onclick="saveToLocalStore()"/> </form> </body> </html>
Use the getItem
function of the
EncryptedLocalStore
class to retrieve
your data from the local store.
As mentioned in Storing Data in the Encrypted Local Store, all data stored
within the encrypted local store is stored in binary format as a
flash.utils.ByteArray
and assigned a
String
identifier. To retrieve data
from the encrypted local store, you need to know the identifier that it
was stored with and also be working within the same AIR application that
originally stored the
data.
In the following example, which extends Storing Data in the Encrypted Local Store, the getItem
method is called with an identifier
of serialNumber
passed in:
var data:ByteArray = EncryptedLocalStore.getItem("serialNumber");
Now that you have retrieved the data from the encrypted local
store, the only thing left to do is to use the correct ByteArray
retrieving data in encrypted local
stores function to parse the data from binary back to a
human-readable format.
The following example adds one line of code, which uses the
readUTFBytes
method to parse the
data object into a String
. This
method also requires a single argument that passes the length of the
data object.
var data:ByteArray = EncryptedLocalStore.getItem("serialNumber"); var serialNumber:String = data.readUTFBytes(data.length);
Starting with the code from Storing Data in the Encrypted Local Store, the following
example adds the functionality to retrieve the data from the encrypted
local store and display it within a TextInput
control:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <![CDATA[ private function saveToLocalStore():void{ var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(serialNumberTxt.text); EncryptedLocalStore.setItem("serialNumber", bytes); } private function retrieveFromLocalStore():void { var data:ByteArray = EncryptedLocalStore.getItem("serialNumber"); serial.text = data.readUTFBytes(data.length); } ]]> </mx:Script> <mx:HBox horizontalCenter="0" y="134"> <mx:Label text="Serial Number"/> <mx:TextInput id="serialNumberTxt"/> <mx:Button label="Save to the encrypted local store" click="saveToLocalStore()"/> </mx:HBox> <mx:Button label="Retrieve from the encrypted local store" click="retrieveFromLocalStore()" x="263" y="181"/> <mx:TextInput id="serial" x="41" y="181" width="214"/> </mx:WindowedApplication>
In the following example, which extends Storing Data in the Encrypted Local Store, the getItem
method is called with an identifier
of serialNumber
passed in:
var data = air.EncryptedLocalStore.getItem("serialNumber");
Now that you have retrieved the data from the encrypted local
store, the only step left to take is to use the correct ByteArray
function to parse the data from
binary back to a human-readable format.
The following example adds one line of code, which uses the
readUTFBytes
method to parse the
data object into a String
. This
method also requires a single argument that passes the length of the
data object.
var data = air.EncryptedLocalStore.getItem("serialNumber"); var serialNumber = data.readUTFBytes(data.length);
Starting with the code from Storing Data in the Encrypted Local Store, the following
example adds the functionality to retrieve the data from the encrypted
local store and display it within an Input
form field:
<html> <head> <script src="AIRAliases.js" /> <script> function saveToLocalStore(){ var bytes = new air.ByteArray(); bytes.writeUTFBytes(document.theform.serialNumberTxt.value); air.EncryptedLocalStore.setItem("serialNumber", bytes); } function retrieveFromLocalStore(){ var data = air.EncryptedLocalStore.getItem("serialNumber"); document.theform.serialNumber.value = data.readUTFBytes(data.length); } </script> </head> <body> <form name="theform"> Serial Number: <input type="text" size="20" name="serialNumberTxt"/> <input type="button" value="Save to the encrypted local store" onclick="saveToLocalStore()"/> <br/> <input type="text" size="20" name="serialNumber"/> <input type="button" value="Retrieve from the encrypted local store" onclick="retrieveFromLocalStore()"/> </form> </body> </html>
Use either the removeItem
method (which will remove the single item) or the reset
method (which will clear the entire
store) of the EncryptedLocalStore
class to remove data from the encrypted local store.
Data stored in encrypted local store persists until either the application that created it removes it by identifier or the encrypted local store is reset. Even uninstalling the application will not remove the data that was persisted, so it is up to the developer to remove any data that is no longer necessary.
To remove a single item from the local store, you use the removeItem
method. This method accepts a
single argument of type String
that
is the identifier that the data was associated with when
persisted.
The following example removes an item under the
identifier serialNumber
from the
encrypted local store:
EncryptedLocalStore.removeItem("serialNumber");
The second way to remove data from the encrypted local store is
to use the reset
method. The
reset
method will remove all data
that is stored within the EncryptedLocalStore
that is being stored for
the accompanying application.
The following example utilizes the reset
method to clear all data from the
EncryptedLocalStore
:
EncryptedLocalStore.reset();
The following full example includes code to save data to the encrypted local store from Storing Data in the Encrypted Local Store and includes code to retrieve data from the encrypted local store from Retrieving Data from the Encrypted Local Store. It also adds the functions to remove data or reset the entire encrypted local store data set associated with the application.
Note that if you attempt to retrieve data with an identifier that has been removed or not yet saved, you will get a runtime error (specifically, error #1009: “Cannot access a property or method of a null object reference”).
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <![CDATA[ private function saveToLocalStore():void{ var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(serialNumberTxt.text); EncryptedLocalStore.setItem("serialNumber", bytes); } private function retrieveFromLocalStore():void { var data:ByteArray = EncryptedLocalStore.getItem("serialNumber"); serial.text = data.readUTFBytes(data.length); } private function removeFromLocalStore():void{ EncryptedLocalStore.removeItem("serialNumber"); } private function resetLocalStore():void{ EncryptedLocalStore.reset(); } ]]> </mx:Script> <mx:HBox horizontalCenter="0" y="134"> <mx:Label text="Serial Number"/> <mx:TextInput id="serialNumberTxt"/> <mx:Button label="Save to the encrypted local store" click="saveToLocalStore()"/> </mx:HBox> <mx:Button label="Retrieve from the encrypted local store" click="retrieveFromLocalStore()" x="263" y="181"/> <mx:TextInput id="serial" x="41" y="181" width="214"/> <mx:Button x="153" y="220" label="Remove from the encrypted local store" click="removeFromLocalStore()"/> <mx:Button x="164.5" y="250" label="Reset the encrypted local store" click="resetLocalStore()"/> </mx:WindowedApplication>
The following example removes an item under the
identifier serialNumber
from the
encrypted local store:
air.EncryptedLocalStore.removeItem("serialNumber");
The second way to remove data from the encrypted local store is
to use the reset
method. The
reset
method will remove all data
that is stored within the EncryptedLocalStore
that is being stored for the accompanying application.
The following example utilizes the reset
method to clear all data from the
EncryptedLocalStore
:
air.EncryptedLocalStore.reset();
The following full example includes code to save data to the
encrypted local store from Storing Data in the Encrypted Local Store and includes code
to retrieve data from the encrypted local store from Retrieving Data from the Encrypted Local Store. It
also adds the functions to remove data or reset the entire EncryptedLocalStore
.
<html> <head> <script src="AIRAliases.js" /> <script> function saveToLocalStore(){ var bytes = new air.ByteArray(); bytes.writeUTFBytes(document.theform.serialNumberTxt.value); air.EncryptedLocalStore.setItem("serialNumber", bytes); } function retrieveFromLocalStore(){ var data = air.EncryptedLocalStore.getItem("serialNumber"); document.theform.serialNumber.value = data.readUTFBytes(data.length); } function removeFromLocalStore(){ air.EncryptedLocalStore.removeItem("serialNumber"); } function resetLocalStore(){ air.EncryptedLocalStore.reset(); } </script> </head> <body> <form name="theform"> Serial Number: <input type="text" size="20" name="serialNumberTxt"/> <input type="button" value="Save to the encrypted local store" onclick="saveToLocalStore()"/> <br/> <input type="text" size="20" name="serialNumber"/> <input type="button" value="Retrieve from the encrypted local store" onclick="retrieveFromLocalStore()"/> <br/> <input type="button" value="Remove from the encrypted local store" onclick="removeFromLocalStore()"/> <br/> <input type="button" value="Reset the encrypted local store" onclick="resetLocalStore()"/> </form> </body> </html>
Your client requires more data security than the standard encrypted local store settings enable.
When adding data to the encrypted local store, set stronglyBound
, the optional third argument of the setItem
method, to true
.
Data stored within the encrypted local store is accessible to only
the AIR application that originally saved the data because it is bound
to the application’s publisher ID. However, for an even higher level of
security, you can set the stronglyBound
argument of the setItem
method to true
. Setting this property to true
within the setItem
method binds the data to the actual
bits of the application. This ensures that any application that attempts
to get data from the encrypted local store not only has the correct
publisher ID but also has had no changes made to its application
directory.
Be aware, however, that this extra layer of protection comes with
some trade-off. Because no changes can be made to the application
directory, you will lose any stronglyBound
data stored within the encrypted
local store when your application is updated to a newer version. To
avoid runtime errors, you must account for this when coding your
application with stronglyBound
data.
The following example is identical to the one used
within Storing Data in the Encrypted Local Store with
the only change being the optional third argument of the setItem
method set to true
. This alters the data being stored
within the encrypted local store to be stronglyBound
.
var serialNumber:String = "0000-1234-7777-9876" var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(serialNumber); EncryptedLocalStore.setItem("serialNumber ", bytes, true);
Here is a full example showing a simple form where a user can
enter a serial number and persist it to the encrypted local store
where the data is stronglyBound
to
the application:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <![CDATA[ private function saveToLocalStore():void{ var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(serialNumberTxt.text); EncryptedLocalStore.setItem("serialNumber", bytes, true); } ]]> </mx:Script> <mx:HBox horizontalCenter="0" y="134"> <mx:Label text="Serial Number"/> <mx:TextInput id="serialNumberTxt"/> <mx:Button label="Save to the encrypted local store" click="saveToLocalStore()"/> </mx:HBox> </mx:WindowedApplication>
The following example is identical to the one used
within Storing Data in the Encrypted Local Store with
the only change being the optional third argument of the setItem
method set to true
. This alters the data being stored
within the encrypted local store to be stronglyBound
.
var serialNumber = "0000-1234-7777-9876" var bytes = new air.ByteArray(); bytes.writeUTFBytes(serialNumber); air.EncryptedLocalStore.setItem("serialNumber", bytes, true);
Here is a full example showing a simple form where a user can
enter a serial number and persist it to the encrypted local store
where the data is stronglyBound
to
the application:
<html> <head> <script src="AIRAliases.js" /> <script> function saveToLocalStore(){ var bytes = new air.ByteArray(); bytes.writeUTFBytes(document.theform.serialNumberTxt.value); air.EncryptedLocalStore.setItem("serialNumber", bytes, true); } </script> </head> <body> <form name="theform"> Serial Number: <input type="text" size="20" name="serialNumberTxt"/> <input type="button" value="Save to the encrypted local store" onclick="saveToLocalStore()"/> </form> </body> </html>
Contributed by Ryan Stewart (http://blog.digitalbackcountry.com/)
Using the EncryptedLocalStore
and the File APIs you can put a file in a safe and secure
location.
Most people associate the encrypted local store with storing
usernames and passwords or other bits of text. But thanks to the fact
that EncryptedLocalStore
uses
instances of the ByteArray
class,
it’s easy to throw all kinds of things in there. This recipe offers two
examples for saving data into the encrypted local store and loading it
back out.
There are couple of items to keep in mind when working with entire
files in EncryptedLocalStore
. First,
the EncryptedLocalStore
is supported
only up to 10MB. It can go higher, but you may see performance problems.
In addition, the encrypted local store isn’t cleared out when the
application is uninstalled. You may have to manually clear it out using
the reset
method.
In this example, two methods handle the interaction with the
EncryptedLocalStore
: saveFile
and loadFile
.
The saveFile
function takes a
File
object (which could be from a
drag-and-drop operation or a file open dialog box), and it also contains
start a ByteArray
that will store the
file data. Just like any other File API, there needs to be a stream to
put the bytes into the ByteArray
.
When that is complete, the application grabs the name of the file so
that it can reference it later. It then uses EncryptedLocalStore.setItem
to add the file
data to the encrypted local store.
The next function is the loadFile
function. It takes the data out of
EncryptedLocalStore
and saves it to
the hard drive. It also takes a File
object so that the application knows where to store the file when it is
finished reading it. Just like with any other File API, a ByteArray
is created that will contain the
file bits. Then the EncryptedLocalStore.getItem
method is called
to grab the data out of the encrypted local store. When that is
complete, a new FileStream
is
created, and it writes that file data to the file.
If you ever need to clear the encrypted local store, you can just
call the EncryptedLocalStore.reset
function, and that will wipe away the data. The EncryptedLocalStore
APIs are some of the
easiest in AIR, but they allow for the ability to save and protect all
kinds of data thanks to the ByteArray
.
Here is the completed application in Flex with both the
saveFile
and loadFile
methods:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <![CDATA[ import mx.events.FileEvent; public function onSaveClick(event:Event) : void { // Clear out everything EncryptedLocalStore.reset(); var file : File = new File(); file.browseForOpen("Save File in Encrypted Local Store"); // Add the event listener for the selection file.addEventListener(Event.SELECT, function onSaveSelect(event:Event) : void { saveFile(event.currentTarget as File); }); } public function saveFile(file:File) : void { var stream : FileStream = new FileStream(); var filedataArray : ByteArray = new ByteArray(); stream.open(file, FileMode.READ); stream.readBytes(filedataArray, 0, file.size); stream.close(); // Set the file name so we can pull it out later. fileName = file.name; // Set the item in the Encrypted Local Store EncryptedLocalStore.setItem(fileName,filedataArray); } public function onLoadClick(event:Event) : void { var file : File = File.desktopDirectory.resolvePath(fileName); file.addEventListener(Event.SELECT, function onLoadSelect(event:Event) : void { loadFile(event.currentTarget as File); }); file.browseForSave("Load File From Encrypted Local Store"); } public function loadFile(file:File) : void { // Create the ByteArray and pull it out of the local store var byteArray : ByteArray = new ByteArray(); byteArray = EncryptedLocalStore.getItem(fileName); // Open and write the file using the regular File APIs var stream : FileStream = new FileStream(); stream.open(file, FileMode.WRITE) stream.writeBytes(byteArray); stream.close(); } private var fileName : String; ]]> </mx:Script> <mx:Label text="Click the button below to save a file into the Encrypted Local Store" /> <mx:Button id="btnSave" click="onSaveClick(event)" label="Save File to Encrypted Local Store" /> <mx:Label text="Click the button below to save the file from the Encrypted Local Store back to your hard drive" /> <mx:Button id="btnLoad" click="onLoadClick(event)" label="Load File from Encrpted Local Store" y="50" /> </mx:WindowedApplication>
Here is the completed application in JavaScript with
both the saveFile
and loadFile
methods:
<html> <head> <title>Encrypted Local Store File Storage</title> <script type="text/javascript" src="AIRAliases.js"></script> <script type="text/javascript"> var fileName; function onSaveClick(event) { // Clear out everything air.EncryptedLocalStore.reset(); var file = new air.File(); file.browseForOpen("Save File in Encrypted Local Store"); // Add the event listener for the selection file.addEventListener(air.Event.SELECT,onSaveSelect); } function onSaveSelect(event) { saveFile(event.currentTarget); } function saveFile(file) { var stream = new air.FileStream(); var filedataArray = new air.ByteArray(); stream.open(file, air.FileMode.READ); stream.readBytes(filedataArray, 0, file.size); stream.close(); // Set the file name so we can pull it out later. fileName = file.name; // Set the item in the Encrypted Local Store air.EncryptedLocalStore.setItem(fileName,filedataArray); } function onLoadClick(event) { var file = air.File.desktopDirectory.resolvePath(fileName); file.addEventListener(air.Event.SELECT,onLoadSelect); file.browseForSave("Load File From Encrypted Local Store"); } function onLoadSelect(event) { loadFile(event.currentTarget); } function loadFile(file) { // Create the ByteArray and pull it out of the local store var byteArray = new air.ByteArray(); byteArray = air.EncryptedLocalStore.getItem(fileName); // Open and write the file using the regular File APIs var stream = new air.FileStream(); stream.open(file, air.FileMode.WRITE) stream.writeBytes(byteArray); stream.close(); } </script> </head> <body> <p>Click the button below to save a file into the Encrypted Local Store</p> <input type="button" value="Save File to Encrypted Local Store" onclick="onSaveClick(event)" /><br /> <p> Click the button below to save the file from the Encrypted Local Store back to your hard drive</p> <input type="button" value="Load File from Encrpted Local Store" onclick="onLoadClick(event)" /> </body> </html>