Across the internet, including Hibernate's own examples, it appears the conventional wisdom is that should you be relying on a database's native symmetric encryption functions for security, the @ColumnTransformer annotation is the way to go. In the case of Postgres' pgcrypto:
@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;
While the solution works, 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:
@ColumnTransform(transformer = EncryptionColumnTransformer.class)
private String passportNumber;
public class EncryptionColumnTransformer extends ColumnTransformerImpl {
public String forColumn() { return ""; }
public String read() {
return "pgp_sym_decrypt(encrypted_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");
}
}
To support this usage pattern, it's simply a matter of adjusting the Ejb3Column::processExpression method to instantiate EncryptionColumnTransformer if the annotation has class() as non-empty and call its methods:
if (annotation.getTransformerClass() != null) {
ColumnTransformerImpl transformer = annotation.getTransformerClass().newInstance();
readExpression = transformer.read();
writeExpression = transformer.write();
} else {
}
Forgive me if there is an approach already available to afford what I'm looking for, Java is still not familiar to me! |