Paul Robertson's words, punctuated

Thoughts on development, user-centered design, code, etc. by Paul Robertson

Problem (and solution): Any AIR app can read any other app's databases

In a side note on a recent post about the AIR functionality for working with local SQL databases, Tim Anderson raised some concerns about the security model for AIR local SQL databases, that I thought would be valuable to discuss more.

Tim also raises other concerns which aren’t so much issues to keep in mind when developing AIR apps as they are issues surrounding the documentation. I’ve attempted to respond to those concerns in a separate post.

Tim says:

unlike [Google] Gears, AIR makes no attempt to isolate databases based on the origin of the application. In AIR, a SQLite database may be anywhere in the file system, and it’s equally available to any AIR application - a big hole in the AIR sandbox.

I think Tim raises an important point (although I disagree a bit with his conclusion). Very soon after I started working with the local SQL database functionality in AIR I realized the same thing – since any AIR database can be read by any AIR application, it means that I can write a database application in AIR, and you can write an application that finds the file I create with my app, and reads its data.

But let’s take a moment to get a little perspective, after which we’ll consider what we can do about it.

It’s true that in AIR, a SQLite database may be anywhere in the file system, and in general it’s equally available to any AIR application. However, while this differs from Google Gears’ approach, there are some key reasons why this difference is allowed, and why it’s not considered a “big hole” security-wise.

First of all, there’s a significant difference between Google Gears and Adobe AIR. Google Gears is an extension to the standard capabilities of web browsers, but ultimately an application that uses Google Gears runs in a web browser and is therefore subject to all the security constraints of a browser-based application. This is similar to content that runs in the Flash Player browser plugin, or in a non-Gears-enabled HTML/JavaScript application – neither of which can freely access a user’s hard drive. Why is that? Well, it’s because all it takes for a user to access a Google Gears application is for the user to visit the url of that application in his or her web browser. That could be by clicking a link, or even by an automatic redirect that the user has limited control over.

On the other hand, an AIR application doesn’t require or use a browser. In order for a user to access an AIR application, he or she must first choose to install the application, including going through a security dialog that will describe whether the application was signed with a security certificate. In this way, an AIR application is comparable to any other desktop application, such as one written in C++. Since any C++ application could theoretically include the SQLite library, installing an AIR application is no different from installing any C++ application in the sense that, by doing so, a user opens himself up to possible abuses and security risks.

Likewise, any application that can read files from the file system (AIR or not) has the same potential for “stealing” sensitive information. If someone has written down his passwords in a Microsoft Word file, an installed application could search the hard drive for all Word files and read them all and send their contents to a malicious author. Even if your application uses a custom binary file format, there’s nothing that can be done to prevent someone else from reverse-engineering your file format and then writing an app that reads your files and extracts the data. Of course, it’s true that by using a SQLite-format database file, in return for the convenience and benefits it gives you, you’re saving Joe Evil the trouble of needing to reverse engineer your file format, and instead you’re handing your data over quite handily in a wonderful structured way.

On the other hand, all this openness actually has benefits. Since my app can read files written by another app, I can write two different apps that can understand each others’ data. If I make a certain kind of app, and later you make another app that does the same thing but does it better, you can read my file format and import my data into your app – meaning you can help users migrate from my crummy app to your awesome one.

Having said all that, I don’t want you to think that I’m simply washing my hands of the issue. I think it’s an extremely important one, and I’m very glad it came up.

Tell me how to fix it, already

All openness aside, if you’re storing sensitive data in your application, data that you don’t think other applications ought to be able to read, there are some things you can do to try to minimize the potential for damage. Note that since the problem isn’t exclusive to apps that use a local SQL database (although it is perhaps more apparent for those apps), the possible solutions aren’t exclusive to local SQL databases either.

Use user-specific directories

Every AIR application has a special folder in the operating system that can be used to store files related to the app. The folder, known as the “application storage directory,” is actually different for different logged-in users of the same application. In that way it’s a convenient way to separate files for different users of your application. It’s location is always available using the File.applicationStorageDirectory property. Similarly, you can access the directories representing the user’s desktop and his/her documents directory; again, these are folders that will be different per-user, but your app can use the same code to access them regardless of who is using the app.

Note that this only provides limited protection. Any AIR app or any other app can traverse the file system (assuming the logged-in user has permission to do so) and discover and read files in the application storage directory or other user-specific directories. So while this might protect files belonging to other user accounts (if the user running the malicious app isn’t an administrator), it won’t protect files belonging to the logged-in user.

Encrypt your data

The most reliable way to protect sensitive data from other applications is to encrypt it. This can be done in a couple of ways:

  • Encrypt the database file itself. This approach could be somewhat involved. When a user opens the app you’d decrypt the database file and store the decrypted copy in a temporary location from which the app would actually access the db. Then, when your app shuts down (or at some other time) you’d encrypt the temporary file and save the encrypted version wherever you store that master version, and delete the temporary file. As a simpler but less secure variation, you could use a simpler form of encryption. For example, you could append some bytes (either random or meaningful) to the beginning of the database file (or end, or middle or any combination of the three). Before using the database file, you’d need to remove the extra bytes, of course, and actually access the unencrypted version of the file with your application.
  • Encrypt sensitive data within the database. Rather than encrypting the entire database file, if your application stores some data that would be considered sensitive, you could simply encrypt the raw data before writing it to the database in your INSERT/UPDATE statement, then decrypt it after reading it with a SELECT statement.

Note that in both of these cases I’m talking about using a two-way encryption algorithm. Typically such algorithms require you to have some sort of secret – the encryption key – that is used by the application to encrypt and decrypt the data. Since an AIR app consists of HTML and JavaScript files (plain text) and/or SWF files (binary, but known to be de-compileable), you won’t want to store the encryption key for your application within the source code of your application. Rather, you’ll want to generate the key for each user, and store it separately from the application data. Christian Cantrell’s “Salsa” application demonstrates how to do this (it actually uses a user-selected passphrase as the encryption key, although the release notes say that future versions won’t require that) so that’s an example app to look at for an example of two-way encryption.

Not just reading

On a somewhat related note, another concern with other applications being able to access your app’s data has to do with the integrity of the data. Not only could another app read your application’s data, but it could just as easily change the data as well. Apart from two-way encrypting the entire database file, there isn’t really any way to protect against this. If you don’t want to encrypt the entire database file, one way you can at least verify the integrity of your data is by using a one-way encryption algorithm (also know as a hash). It works like this:

  1. After closing your database, your app encrypts all or some of your database (either the bytes of the db file, or the db data) using a one-way encryption algorithm.
  2. The next time your app opens your database (or before opening the db if you hashed the file itself) you run the same file (or portion of the file or data) through the encryption algorithm again.
  3. If the source data (your db’s contents) hasn’t changed, the resulting hash value should be identical. If the contents of the database have changed, the resulting hash value will have changed – meaning the data has been tampered with by something other than your application.

You could even combine an integrity check with two-way encrypting the file. For example, you could create a hash of all or part of the database file, then append the result to the file. When you reopen the file, you would need to extract the hash, then you could compare it to the rest of the database as well as using the database file (minus the hash) for your application.

Finally, I should acknowledge that, while I’ve done some study of encryption and securing applications, by no means do I consider myself an expert on the topic. If anyone has other ideas, suggestions, and especially if you see some issues with the techniques I’ve recommended here, please share your experience!

Comments