The security people at my were suggesting that we needed to create an encryption service, to securely store passwords so that even rogue DBAs could not get at them. The idea is that no matter how good your access is to the database, you shouldn’t be able to decrypt the passwords unless you have the secret key. In a solution like this, the key is generally stored offline with the application and loaded into memory sometime during startup. The encrypted data never leaves the database.
Here is what I want:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public void testRetrieveWithCorrectKey() { EncryptedStringUserType.setKey(masterKey); String secretMessage = "this is the secret message"; EncryptedEntity entity = new EncryptedEntity(secretMessage); Long id = dao.store(entity); dao.evictAll(); EncryptedEntity retrievedCopy = dao.get(id); assertEquals(entity.getSecretMessage(), retrievedCopy.getSecretMessage()); } public void testRetrievedWithIncorrectKey() { EncryptedStringUserType.setKey(masterKey); String secretMessage = "this is teh secret message"; EncryptedEntity entity = new EncryptedEntity(secretMessage); Long id = dao.store(entity); dao.evictAll(); EncryptedStringUserType.setKey(<b>wrongKey</b>); try { dao.get(id); fail("should fail to read message"); } catch (RuntimeException e) { assertEquals("Whoa! Master password wrong", e.getMessage()); } } |
Here, EncryptedEntity is an entity that has some property encrypted. The EncryptedEntity.secretMessage property is just a String variable. All the magic happens in the Hibernate mapping file:
1 2 3 4 5 6 7 8 9 |
<class name="EncryptedEntity"> <id name="key" column="id"> <generator class="native" /> </id> <property name="secretMessage" type="<b>no.brodwall.insanejava.crypto.EncryptedStringUserType</b>" /> </class> |
The interesting bits are in EncryptedStringUserType, which implements org.hibernate.usertype.UserType. Here is the code for saving the String property.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
protected void noNullSet(PreparedStatement st, Object value, int index) throws SQLException { byte[] clearText = ((String)value).getBytes(); try { Cipher encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM); encryptCipher.init(Cipher.ENCRYPT_MODE, masterKey, paramSpec); st.setBytes(index, encryptCipher.doFinal(clearText)); } catch (GeneralSecurityException e) { throw new RuntimeException("should never happen", e); } } |
Reading and decrypting is the same, mutatis mutandis. The master key is of course set by the tests. One example may be as a global variable:
1 2 3 4 5 6 7 8 9 10 11 |
public static void setKey(char[] masterKeyText) { try { SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); PBEKeySpec keySpec = new PBEKeySpec(masterKeyText); masterKey = secretKeyFactory.generateSecret(keySpec); } catch (GeneralSecurityException e) { throw new RuntimeException("should never happen", e); } } |
Get the full EncryptedStringUserType to see the not so gory details (notice: This is implemented with “Password Based Encryption”, but it should be simple to replace it with any Java Encryption algorithm you’d like.