Across the internet, including [Hibernate's own examples|https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#fetching-strategies], it appears the conventional wisdom is that the @ColumnTransformer annotation is the way to go should you be relying on a database's native symmetric encryption functions for security.
In the case of Postgres' pgcrypto:
{code:java}@ColumnTransformer( write = "pgp_sym_encrypt(?, 'somepassword', 'compress-algo=1, cipher-algo=aes256')", read = "pgp_sym_decrypt(passport_number, 'somepassword', 'compress-algo=1, cipher-algo=aes256')" ) private String passportNumber;{code}
While the solution is functional there's a problem: the password is hard-coded into the transformer's arguments. Even if abstracted to a constants file, because of the limitations of annotations, the value must be provided at compile time.
This means that whatever encryption key/passphrase you use in a development environment is the same as staging and is the same as production.
This goes against security best practices. You want your environments to have different keys. Typically this is done in the form of ENV vars or a configuration file that the program loads at boot.
Although I am open to anything that will allow me to somehow dynamically supply a password at runtime to the transformer, I think suggesting a way to support dynamic string generation is general and desirable.
One such approach I could see is supporting a "transformer" class that will resolve the appropriate custom read and write Strings. This is consistent with the @Convert(converter = '')/AttributeConverter pattern:
{code:java}@ColumnTransform(transformer = EncryptionColumnTransformer.class) private String passportNumber;{code}
what the class might look like:
{code:java}public class EncryptionColumnTransformer extends ColumnTransformerImpl { public String forColumn() { return ""; }
public String read() { return "pgp_sym_decrypt( encrypted_number passport_number , '" + getPassword() + "', 'compress-algo=1, cipher-algo=aes256')"; }
public String write() { return "pgp_sym_encrypt(?, '" + getPassword() + "', 'compress-algo=1, cipher-algo=aes256')"; }
private String getPassword() { return System.getenv("ENCRYPTION_PASSPHRASE"); } }{code}
To support this usage pattern, it's simply a matter of adjusting the {{Ejb3Column::processExpression}} [method |https://github.com/hibernate/hibernate-orm/blob/09cc94c4d9f9093ba990c4bdd8c660056ea9826b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java#L651-L672]to instantiate EncryptionColumnTransformer if the annotation has {{transformer()}} as non-empty and call its methods:
{code:java}// inside processExpression() if (annotation.transform() != null) { ColumnTransformerImpl transformer = annotation.transformer().newInstance(); readExpression = transformer.read(); writeExpression = transformer.write(); // etc. for forColumn } else { // existing behaviour }{code}
Forgive me if there is an approach already available to afford what I'm looking for, Java is still not familiar to me\! |
|