Channels ▼
RSS

.NET

Encrypted Preferences in Java


Encryption

Finally, the encryption. We've done most of the work in the three previous classes. First and foremost, EncryptedPreferences has to do encryption. As we saw in the previous section, this functionality is placed inside obfuscateString() and deObfuscateString(). If you look at the source in Listing Eight, you'll see that the actual encryption/decryption work is done in a class called "EncryptionStuff," which can be found in Listing Nine.

Listing Eight: The source for EncryptedPreferences, which encrypts keys and values before writing them to the preferences database, and then decrypts them on the way back out.
// $Id$

package ep;

import java.security.*;
import java.util.prefs.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class EncryptedPreferences extends ObfuscatedPreferences
{
  private EncryptionStuff stuff;

  protected EncryptedPreferences( AbstractPreferences parent, String name,
      AbstractPreferences target ) {
    super( parent, name, target );
  }

  private void setStuff( EncryptionStuff stuff ) {
    this.stuff = stuff;
  }

  private EncryptionStuff getStuff() {
    return stuff;
  }

  public String obfuscateString( String string ) {
    try {
      return getStuff().obfuscateString( string );
    } catch( GeneralSecurityException gse ) {
      gse.printStackTrace();
    }
    return null;
  }

  public String deObfuscateString( String string ) {
    try {
      return getStuff().deObfuscateString( string );
    } catch( GeneralSecurityException gse ) {
      gse.printStackTrace();
    }
    return null;
  }

  public WrappedPreferences wrapChild( WrappedPreferences parent,
                                              String name,
                                              AbstractPreferences child ) {
    EncryptedPreferences ep = new EncryptedPreferences( parent, name, child );
    ep.setStuff( stuff );
    return ep;
  }

  static public Preferences userNodeForPackage( Class clasz,
                                                SecretKey secretKey ) {
    AbstractPreferences ap =
      (AbstractPreferences)Preferences.userNodeForPackage( clasz );
    EncryptedPreferences ep = new EncryptedPreferences( null, "", ap );
    try {
      ep.setStuff( new EncryptionStuff( secretKey ) );
      return ep;
    } catch( GeneralSecurityException gse ) {
      gse.printStackTrace();
    }
    return null;
  }

  static public Preferences systemNodeForPackage( Class clasz,
                                                SecretKey secretKey ) {
    AbstractPreferences ap =
      (AbstractPreferences)Preferences.systemNodeForPackage( clasz );
    EncryptedPreferences ep = new EncryptedPreferences( null, "", ap );
    try {
      ep.setStuff( new EncryptionStuff( secretKey ) );
      return ep;
    } catch( GeneralSecurityException gse ) {
      gse.printStackTrace();
    }
    return null;
  }
}
Listing Nine: The source for EncryptedStuff, which contains the objects required for encryption and decryption of preferences data.

// $Id$

package ep;

import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class EncryptionStuff
{
  static private final String algorithm = "DES";
  static private SecureRandom sr = new SecureRandom();
  private SecretKey secretKey;
  private Cipher cipher;

  public EncryptionStuff( SecretKey secretKey ) throws GeneralSecurityException {
    this.secretKey = secretKey;

    cipher = Cipher.getInstance( algorithm );
  }

  public String arrayToString( byte raw[] ) {
    StringBuffer sb = new StringBuffer();
    for (int i=0; i<raw.length; ++i) {
      short s = (short)raw[i];
      if (s>0)
        s += 256;
      int hi = s>>4;
      int lo = s&0xf;
      sb.append( (char)('a'+hi) );
      sb.append( (char)('a'+lo) );
    }
    return sb.toString();
  }

  public byte[] stringToArray( String string ) {
    StringBuffer sb = new StringBuffer( string );
    int len = sb.length();

    if ((len&1)==1)
      throw new RuntimeException( "String must be of even length! "+string );

    byte raw[] = new byte[len/2];
    int ii=0;
    for (int i=0; i<len; i+=2) {
      int hic = sb.charAt( i ) - 'a';
      int loc = sb.charAt( i+1 ) - 'a';
      byte b = (byte)( (hic><4) | loc );
      raw[ii++] = b;
    }
    return raw;
  }

  synchronized public String obfuscateString( String string )
      throws GeneralSecurityException {
    cipher.init( Cipher.ENCRYPT_MODE, secretKey, sr );
    byte raw[] = string.getBytes();
    byte oraw[] = cipher.doFinal( raw );
    String ostring = arrayToString( oraw );
    return ostring;
  }

  synchronized public String deObfuscateString( String string )
      throws GeneralSecurityException {
    cipher.init( Cipher.DECRYPT_MODE, secretKey, sr );
    byte raw[] = stringToArray( string );
    byte draw[] = cipher.doFinal( raw );
    String dstring = new String( draw );
    return dstring;
  }
}

Also, if you'll recall, EncryptedPreferences has to implement wrapChild(), which is used to ensure that subnodes are wrapped in EncryptedPreference objects as well.

Trying It Out

Testing this stuff is easy. As mentioned previously, run generatekey.bat to generate the key file, and then run pkg.encrypted.EncryptedTest to see the EncryptedPreferences in action, as follows:


C:\> generatekey.bat
Generating key....
C:\> java pkg.encrypted.EncryptionTest
[XML output not shown]

Remember, look in the registry to see the encrypted and nonencrypted values. If you don't know where to find them, do a search for the string "pkg."

Finally, a utility class (pkg.Util), which is called by the other classes, is included in Listing Ten. It provides functionality for reading and writing byte arrays.

Listing Ten: The source for Util.java, a utility class used by other classes in the packaged "pkg."
// $Id$

package pkg;

import java.io.*;

public class Util
{
  // Read a file into a byte array
  static public byte[] readFile( String filename ) throws IOException {
    File file = new File( filename );
    long len = file.length();
    byte data[] = new byte[(int)len];
    FileInputStream fin = new FileInputStream( file );
    int r = fin.read( data );
    if (r != len)
      throw new IOException( "Only read "+r+" of "+len+" for "+file );
    fin.close();
    return data;
  }

  // Write byte array to a file
  static public void writeFile( String filename, byte data[] )
      throws IOException {
    FileOutputStream fout = new FileOutputStream( filename );
    fout.write( data );
    fout.close();
  }
}
Conclusion

This implementation is fairly complicated. While I was writing the code, I found the number of classes growing naturally as I realized that many aspects of this architecture could be reused for other purposes.

The nice thing about the four-way separation of labor is that it gave itself over to a hierarchical relationship— each of the four Preferences subclasses relied on the one before it. This structure wasn't clear from the beginning-'as I began dividing the functionality into multiple pieces, I found that I had to think a while before I was able to make everything fit into a nice hierarchical ordering.

The Preferences API is well structured, but something like this was needed. The nine SPI methods help the process of extending, modifying, and/or filtering any interactions with the Preferences API.

The result is that we're able to modify the functionality behind the scenes. Once we've created our EncryptedPreferences object, we can use it everywhere we use a regular Preferences object. This makes it easy to convert an existing application to use encrypted preferences, if necessary, without having to make extensive changes to the code.

Resources


Greg Travis is a freelance Java programmer and technology writer living in New York City. His interests include algorithm optimization, programming language design, signal processing (with emphasis on music), and real-time 3D graphics. Other articles he's written can be found at http://www.panix.com/~mito/articles/. He can be reached at mito@panix.com.


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