Channels ▼
RSS

Design

Encrypted Preferences in Java


Our EncryptedPreferences object, which lives in the package "ep," is a subclass of AbstractPreferences. This means it can serve in the same capacity as the object you get from Preferences.userNodeForPackage()— it's a drop-in replacement. And this is clear from the listing above— it does just what the unencrypted version did, except for storing different strings. It also exports the subtree in the same way, producing a similar dump (see Listing Four).

Listing Four: The transparently encrypted preference data for our sample program pkg.encrypted.EncryptedTest, 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>

Another important thing to notice is that this variant of userNodeForPreferences() takes an additional argument: a DES key.

Our Encryption Model

As mentioned, we're not going to go into much detail about encryption, since there are many different algorithms and no single algorithm is right for everything. To get our example working, we'll be using the DES algorithm and generate a DES key, which will just be stored in a file. Although this works, it isn't recommended in practice unless you can be sure that the file can be kept secret from prying eyes.

You can use pkg.GenerateKey to generate the key file, supplying the name of the destination file on the command line. For convenience, just run generatekey.bat to generate the key file.

EncryptedTest reads the key from the file. It converts the raw data from the file into a SecretKey object, as follows:

byte rawKey[] = Util.readFile( "key" );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory =
  SecretKeyFactory.getInstance( algorithm );
SecretKey secretKey =
  keyFactory.generateSecret( dks );

You can find the full source to pkg.encrypted.EncryptedTest in Listing Three.

This SecretKey is required by the EncryptedPreferences.userNodeForPackage() method. Thus, once you've acquired a SecretKey and passed it to that method, you don't have to worry about encryption again— it happens automatically and transparently.

The Encrypted Data

If you run EncryptedTest instead of Test and then look at the registry in regedit, you'll see something different; see Figure 2. Instead of the key "transparent," you'll see the key "mjdajaddcioehjeljmahmpdbfhomifhp." And instead of the value "encrypted," you'll see "hknhbmkdphkpbjipdijphpniboiecadn." These gibberish strings are the encrypted forms of the strings that our test program stored. Well, actually they aren't just encrypted— they're also encoded. Since the Preferences API is fundamentally string based, we need to encode our encrypted data as Strings. But bugs can result if the default charset doesn't encode all our bytes faithfully, so to be as sure as possible, we encode the raw encrypted data using characters that exist in all charsets. Each nybble (4-bit sequence) in our data is mapped to a different character from 'a' to 'p.'

Figure 2: The results of running pkg.encrypted.EncryptedTest.

It's important to recognize that you can't mix encrypted data and unencrypted data in the same node of the preferences database. This means that each package will have to decide independently whether it wants to use encryption for its data, and then call Preferences.userNodeForPreferences() or EncryptedPreferences.userNodeForPreferences() accordingly. As you'll see, an encrypted preferences Node transparently encrypts its own data, as well as data in any of its subnodes. Keep this in mind when planning out your application.

The Preferences Architecture

The java.util.prefs package is structured around two crucial classes: Preferences and AbstractPreferences. Despite the names, Preferences is actually more abstract than AbstractPreferences— that is, AbstractPreferences is a subclass of Preferences, and implements some of the required methods.

Generally speaking, you don't need to subclass either of these classes. If you're just using the Preferences API to store and retrieve data, you call Preferences.userNodeForPackage() (or a similar method), you get a Preferences object, and you use it. Actually, your Preferences object is really something else. Under Windows, it's a WindowsPreferences object. Under Linux, on the other hand, it's a FileSystemPreferences object because the preferences data is stored in the filesystem in an XML-formatted file. These classes are subclasses of AbstractPreferences. See Figure 3 for the relationships between these classes.

Figure 3: Inheritance relationships between classes in the java.util.prefs package.

Normally, you don't need to subclass these classes, but our program isn't normal. We're seeking to modify the underlying implementation, so we need to create our own subclass of AbstractPreferences. AbstractPreferences provides you the option to override only nine methods, the so-called Service Provider Interface (SPI) methods. It implements all other necessary methods in terms of these methods, so you only have to deal with these nine. They are as follows:

  • getSpi()
  • putSpi()
  • removeSpi()
  • childSpi()
  • removeNodeSpi()
  • keysSpi()
  • childrenNamesSpi()
  • syncSpi()
  • flushSpi()

Thus, any subclass that provides these nine also winds up implementing everything else necessary for being a full-fledged Preferences node. All other methods use these nine to actually read and write data. This is represented schematically in Figure 4.

Figure 4: Subclassing AbstractPreferences, and the role of the nine SPI methods.

We override these methods to implement encryption. We'll see exactly how in the next section.

How EncryptedPreferences Works

In fact, we don't just have one subclass of AbstractPreferences, we have four of them. More precisely, we have a four-layer inheritance hierarchy— or five, if you include Abstract Preferences. This hierarchy is shown in Figure 5. This is a pretty complicated hierarchy, so we'll have to spend some time justifying it.

Figure 5: Our inheritance hierarchy.

Let's consider our main requirements. First, we want to use a regular Preferences object to store our encrypted data. That is, when writing values, we want a special, custom Preferences object to take care of the encryption, and then pass the encrypted data on to a regular Preferences object. Likewise, when reading values, we want to read them from a regular Preferences object, decrypt them, and return them to the caller. In short, we want to use delegation. This way, the encryption acts like a filter on the key/value data.

Once we've created an EncryptedPreferences object for a particular node, we want all children of that node to be encrypted as well. These requirements provide a natural division of labor into four classes:

  1. Delegation— modifying requests to read or write and passing them to another Preferences object.
  2. Wrapping-'we want any subnode of a custom node to also be custom.
  3. Obfuscation— this implements a filter, whereby each key and value is first modified ("obfuscated") before being stored.
  4. Encryption— the encryption process is implemented as a special case of obfuscation.

Of course, it would be possible to implement all four of these pieces in a single class, with all the logic merged together. But it should be considered good programming practice to divide it into pieces because the pieces are useful on their own.

The next four sections will consider each of these pieces, both as a part of the encryption process and as a useful class that could fit well into other programs.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video