The Preferences API is now included in the core Java release, as of Version 1.4. It provides a simple, fully cross-platform mechanism for storing small amounts of data, and uses a simple hierarchical "name/value" structure for organizing data. It is intended to be used for configuration and preference data.
Preference data is stored differently on each platform. In fact, is entirely up to each Java implementation how it will store the actual data that is, what backing store it will use. In general, the backing store is not intended to be secure. For example, it may be implemented on top of the Registry, or some other storage facility that does not provide a way to hide sensitive data.
This article considers the technique of automatically encrypting data before storing it in the preferences database. This permits applications to use the Preferences API even for sensitive data, such as passwords and personal information.
What You'll Learn
This article is not intended to provide a tutorial on encryption. It is assumed that you already understand how encryption in Java works, or that you are willing to learn about it elsewhere.
This article focuses on integrating encryption techniques with the Preferences API. We won't focus on the many encryption algorithm options we'll use a simple DES key to perform encryption and decryption, with the understanding that you may well want to replace this approach with another Java-based encryption method.
The most important aspect of this technique is making the encryption transparent. We want the encryption to happen behind-the-scenes, with as little intervention as possible. As you'll see, we'll be creating an EncryptedPreferences
object, which acts just like a regular Preferences object except that it transparently takes care of encryption for us.
If you haven't ever used the Preferences API, don't worry. You'll pick up what you need to know along the way.
A Simple Test Program
Before we get into the details of how it all works, let's take a look at a simple test program. This program (pkg.Test) stores a couple of values in the preferences database.
Preferences root = Preferences.userNodeForPackage( Test.class ); root.put( "not", "encrypted" ); Preferences subnode = root.node( "subnode" ); subnode.put( "also not", "encrypted" ); root.exportSubtree( System.out );
You can find the full source to pkg.Test in Listing One.
The first two lines acquire a Preferences object for this program, "Test.class." Or rather, for the package it's contained in, "pkg." Remember, each package gets its own private area within the preferences database. The userNodeForPackage()
method gets the Preferences object for our private area. This is the root node of the area in which we will store data.
Listing One: A simple test program. It stores a value in the preferences database in the root node for its package ("pkg"), and another value in a subnode of the root node.
// $Id$ package pkg; import java.util.prefs.*; public class Test { static public void main( String args[] ) throws Exception { Preferences root = Preferences.userNodeForPackage( Test.class ); root.put( "not", "encrypted" ); Preferences subnode = root.node( "subnode" ); subnode.put( "also not", "encrypted" ); root.exportSubtree( System.out ); } }
The next line stores a value or rather, a key/value pair. The key is "not," and the value is "encrypted." Later on, you can ask for the value corresponding to the key "not," and you'll get back the value "encrypted."
The next two lines create a subnode of our main node. Into this subnode, we put another key/value pair. The key is "also not," and the value is "encrypted."
Finally, we take a look at what we've done by exporting the entire database that is, the entire database for our program. While the backing store might store data in any format, the exported data always uses the same format, which you can see in Listing Two.
Listing Two: The preference data for our sample program pkg.Test, exported in XML format.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE preferences SYSTEM 'http://java.sun.com/dtd/preferences.dtd'> <preferences EXTERNAL_XML_VERSION="1.0"> <root type="user"> <map /> <node name="pkg"> <map> <entry key="not" value="encrypted" /> </map> <node name="encrypted"> <map> <entry key="mjdajaddcioehjeljmahmpdbfhomifhp" value="hknhbmkdphkpbjipdijphpniboiecadn" /> </map> <node name="subnode"> <map> <entry key="eempdaneimckpiod" value="bkoaejfbcjpkckmflkijoomngbopblco" /> </map> </node> </node> <node name="subnode"> <map> <entry key="also not" value="encrypted" /> </map> </node> </node> </root> </preferences>
If the data is being stored in the Registry, you can see it by using regedit. In my system, the preferences data is stored in \HKEY_CURRENT_USER\Software\ JavaSoft\Prefs\pkg, as you can see in Figure 1.
Trying It with Encryption
Figure 1: The results of running pkg.Test
Using encrypted preferences is easy. Here's the encrypted version, pkg.encrypted.EncryptedTest, which does the same thing as pkg.Test, except that it uses encryption:
Preferences root = EncryptedPreferences.userNodeForPackage( EncryptedTest.class, secretKey ); root.put( "transparent", "encryption" ); Preferences subnode = root.node( "subnode" ); subnode.put( "also", "encrypted" ); root.exportSubtree( System.out );
You can find the full source to pkg.encrypted.EncryptedTest in Listing Three.
Listing Three: A simple test program, this time using encryption. It does more or less the same thing as the program in Listing One, except that this variant uses an Encrypted Preferences object, which transparently encrypts the data before storing it, and decrypts it before retrieving it.
// $Id$ package pkg.encrypted; import java.security.*; import java.util.prefs.*; import javax.crypto.*; import javax.crypto.spec.*; import ep.*; import pkg.Util; public class EncryptedTest { static private final String algorithm = "DES"; static public void main( String args[] ) throws Exception { byte rawKey[] = Util.readFile( "key" ); DESKeySpec dks = new DESKeySpec( rawKey ); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( algorithm ); SecretKey secretKey = keyFactory.generateSecret( dks ); Preferences root = EncryptedPreferences.userNodeForPackage( EncryptedTest.class, secretKey ); root.put( "transparent", "encryption" ); Preferences subnode = root.node( "subnode" ); subnode.put( "also", "encrypted" ); root.exportSubtree( System.out ); } }
The most important thing to see here is that instead of using the Preferences.userNodeForPackage()
method, we're using the EncryptedPreferences.userNodeForPackage()
method. And this method returns an EncryptedPreferences, rather than a regular Preferences object.