Safely storing configuration with passwords in Adobe AIR applications

Almost every application designed to manipulate users data requires storing custom settings, often with passwords. MySQL on AIR is no different. One of the major features is saving connections settings locally. The most obvious way is to use XML but quickly an issue pops out – how to store passwords? XML is saved as open text unless some kind of encryption/decryption mechanism is implemented. But is it worth implementing such a solution when in most cases it will require storing hash keys on the hard drive or compiled directly in the application? Fortunately Adobe AIR provides mechanism called Encrypted Locale Store. It allows for saving critical bits of information in completely isolated, safe place. I won’t go into details how it works, . I rather focus on how I solved the problem with XML for all noncritical settings and Encrypted Local Store for passwords without the need of compiling any hash into the application itself.

The key lays inside the uk.co.riait.controllers.ConnectionsController class. Basically the idea is to store some unique ID in connection nodes and use the same unique ID as an EncryptedLocalStore.setItem() name argument. Let’s see how it’s done.

[source lang='as3']
package uk.co.riait.controllers {
  import flash.data.EncryptedLocalStore;
  import flash.filesystem.File;
  import flash.filesystem.FileMode;
  import flash.filesystem.FileStream;
  import flash.utils.ByteArray;

  import org.swizframework.controller.AbstractController;

  public class ConnectionsController extends AbstractController {

    [Bindable] public var connections:XML = ;
    private var _initialized:Boolean = false;

    public function init():void {
      if (!_initialized) {
        var dir:File = File.userDirectory.resolvePath( ".mysqlonair" );
        if (!dir.exists) {
          dir.createDirectory();
          writeXml();
        } else {
          var file:File = File.userDirectory.resolvePath( ".mysqlonair/connections.xml" );
          if ( !file.exists ) {
            writeXml();
          }
          var stream:FileStream = new FileStream();
          stream.open( file, FileMode.READ );
          connections = new XML(stream.readMultiByte(stream.bytesAvailable, "utf-8"));
          stream.close();
        }
        _initialized = true;
      }
    }

    public function addConnection(
      type:String, host:String, port:Number, user:String, pass:String, schema:String, name:String, uid:String, existing:Object ):void {
      if ( existing != null ) {
        dropConnection(XML(existing).@uid.toString());
      }
      var node:String = "";
      var xml:XML = new XML(node);
      connections.appendChild(xml);
      var passwordBytes:ByteArray = new ByteArray();
      passwordBytes.writeUTFBytes( pass );
      EncryptedLocalStore.setItem(uid, passwordBytes);
      writeXml();
    }
  }
}
[/source]

Let’s scan through the code quickly, but focus on addConnection() method first. All connection parameters and uid are passed as arguments. First the method checks if connection with given uid exists. If it does it is going to delete it ensuring there is always just one instance of given connection. Next it createss new connection XML node, password is not saved there. Instead uid attribute is set on XML node. Right after appending newly created nodea ByteArray is used to save the password in the local store under uid key and XML is saved to the file.

It is time to focus on init() method. First the method checks it ConnectionsController is already initialized. If it is not it verifies existence of .mysqlonair directory in user’s home directory, creates it if necessary and writes an empty XML file with just a root node. Otherwise, if the directory was found it is going to check if the file is still there. If it is missing it is going to be created, again just root node is going to be saved, then all connections settings will be loaded. So the question now is how the password is accessed?

[source lang='as3']
    [Bindable("dataChange")] public function getConnectionPassword(uid:String):String {
      var passwordBytes:ByteArray = EncryptedLocalStore.getItem(uid);
      return passwordBytes.readUTFBytes(passwordBytes.length);
    }
[/source]

Above method is the answer. This method takes uid argument and reads the password from Encrypted Local Store. To see how it is executed let’s take a look at following test class (my wrapper class does not use events so I’m going to simulate time lapse using timers):

[source lang='as3']
package uk.co.riait.tests {
  import flash.events.TimerEvent;
  import flash.utils.Timer;

  import mx.utils.UIDUtil;

  import uk.co.riait.controllers.ConnectionsController;

  public class ConnectionsControllerTest {

    private var ctrl:ConnectionsController = new ConnectionsController();
    private var uid:String = UIDUtil.createUID();

    public function runTest():void {
      ctrl.init();
      var timer:Timer = new Timer(1000, 1);
      timer.addEventListener(TimerEvent.TIMER_COMPLETE, onInitializedTimer);
      timer.start();
    }

    private function onInitializedTimer(event:TimerEvent):void {
      ctrl.addConnection(
        "MySQL", "someHost", 1000, "username", "P4ssw0rd", "mysql", "Some connection",
        uid,
        null);
      var timer:Timer = new Timer(1000, 1);
      timer.addEventListener(TimerEvent.TIMER_COMPLETE, onSavedTimer);
      timer.start();
    }

    private function onSavedTimer(event:TimerEvent):void {
      trace(ctrl.getConnectionPassword(uid));
    }
  }
}
[/source]

Running the test class creates new ConnectionsController and initializes it. Next in onInitializedTimer() the connection is added and new timer is spawn. In onSavedTimer() method the string P4ssw0rd is displayed. And that was the password I used for new connection.

To delete a connection from XML file a dropConnection() method is used:

[source lang='as3']
    public function dropConnection(uid:String):void {
      delete connections.node.(@uid==uid)[0];
      EncryptedLocalStore.removeItem(uid);
      writeXml();
    }
[/source]

Thanks to this approach MySQL on AIR stores passwords safely without having any knowledge about IDs being used. The only place where IDs are stored is XML file.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>