How to encrypt a properties file

Status
Released 14/1/2008

Overview
Many applications need to store secrets, such as passwords, database credentials, certificates, etc...

To do this, every application must have a "master key" that is available to the application. You can't encrypt this key, because that implies that you have another key to decrypt it. Some products go through many levels of this shell game before finally getting to where the actual master key is stored. Just keep asking, "where is the key for that stored"?

So where should you store this master key? Some environments have protected locations, such as the WebSphere configuration files, or the system registry. You can use a file in a protected location, that uses OS access control to limit access to only the application. You could put the key in the code, but that makes it difficult to change and deploy securely. You can also force the master key to be entered when the system boots up so it's only in memory, but that means you lose automatic reboot. You can even split the key and put parts in more than one of these locations.

Once you have a master key, you can use it to encrypt a properties file. A sample implementation is detailed below.

Approach
We're going to extend the base Properties class and override getProperty and setProperty with encrypting and decrypting versions.

public class EncryptedProperties extends Properties { private static sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder; private static sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder; private Cipher encrypter, decrypter; private static byte[] salt = { (byte) 0x03, ...}; // make up your own

public EncryptedProperties(String password) throws Exception { PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt, 20); SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey k = kf.generateSecret(new javax.crypto.spec.PBEKeySpec(password.toCharArray)); encrypter = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding"); decrypter = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding"); encrypter.init(Cipher.ENCRYPT_MODE, k, ps); decrypter.init(Cipher.DECRYPT_MODE, k, ps); }

public String getProperty(String key) { try { return decrypt(super.getProperty(key)); } catch( Exception e ) { throw new RuntimeException("Couldn't decrypt property"); }	}

public synchronized Object setProperty(String key, String value) { try { return super.setProperty(key, encrypt(value)); } catch( Exception e ) { throw new RuntimeException("Couldn't encrypt property"); }	}

private synchronized String decrypt(String str) throws Exception { byte[] dec = decoder.decodeBuffer(str); byte[] utf8 = decrypter.doFinal(dec); return new String(utf8, "UTF-8"); }

private synchronized String encrypt(String str) throws Exception { byte[] utf8 = str.getBytes("UTF-8"); byte[] enc = encrypter.doFinal(utf8); return encoder.encode(enc); } }

You'll also need a loader program, because you can't edit the encrypted properties directly. You can put this in the main method of the same class if you like.

// usage: java EncryptedProperties test.properties password public static void main(String[] args) throws Exception { File f = new File(args[0]); EncryptedProperties ep = new EncryptedProperties( args[1] ); try { if (f.exists) { FileInputStream in = new FileInputStream(f); ep.load(in); }

BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String key = null; do { System.out.print("Enter key: "); key = br.readLine; System.out.print("Enter value: "); String value = br.readLine; if (key.length > 0 && value.length > 0) { ep.setProperty(key, value); }			} while (key != null && key.length > 0);

FileOutputStream out = new FileOutputStream(f); ep.store(out, "Encrypted Properties File"); out.close;

System.out.println("Dump"); Iterator i = ep.keySet.iterator; while (i.hasNext) { String k = (String) i.next; String value = (String) ep.get(k); System.out.println("  " + k + "=" + value); }		}		catch (Exception e) { System.out.println("Couldn't access encrypted properties file: " + f.getAbsolutePath); e.printStackTrace; }	}