Автор оригинала: Vlad Mihalcea.
Вступление
Сегодня один из моих подписчиков в Твиттере отправил мне следующий вопрос StackOverflow , и , отвечая на него, я понял, что он определенно заслуживает отдельного поста.
В этом посте я объясню, как вы можете шифровать и расшифровывать данные с помощью Hibernate.
Крипто-модуль PostgreSQL
Поскольку в вопросе StackOverflow упоминается PostgreSQL, нам сначала нужно включить расширение pgcrypto . Для этого нам нужно выполнить следующее утверждение:
CREATE EXTENSION pgcrypto;
pgcrypto позволит нам использовать функции pgp_sym_encrypt и pgp_sym_decrypt .
Модель предметной области
Предполагая, что у нас есть следующая сущность:
Столбец storage должен быть зашифрован при записи и расшифровке во время операции чтения.
@ColumnTransformer аннотация к спасению!
К счастью, Hibernate предлагает аннотацию @ColumnTransformer , которая была добавлена именно для такого типа сценариев.
Поэтому отображение Хранилище выглядит следующим образом:
@Entity(name = "Vault")
public class Vault {
@Id
private Long id;
@ColumnTransformer(
read = """
pgp_sym_decrypt(
storage,
current_setting('encrypt.key')
)
""",
write = """
pgp_sym_encrypt(
?,
current_setting('encrypt.key')
)
"""
)
@Column(columnDefinition = "bytea")
private String storage;
//Getter and setters omitted for brevity
}
Поскольку жесткое кодирование ключа шифрования в сопоставлении не кажется очень хорошей идеей, вместо этого мы будем использовать поддержку PostgreSQL для пользовательских настроек.
Итак, ключ encrypt.key хранится в файле конфигурации postgresql.conf :
encrypt.key = 'Wow! So much security.'
Обратите внимание, что хранение ключа шифрования в файле конфигурации postgresql.conf необходимо только для того, чтобы избежать его жесткого кодирования. Это не предназначено для использования в производственной среде, где эксперт по безопасности должен посоветовать вам наилучший способ хранения этой очень конфиденциальной информации.
Время тестирования
При сохранении Хранилища сущности:
Vault user = new Vault();
user.setId(1L);
user.setStorage("my_secret_key");
entityManager.persist(user);
Hibernate будет шифровать столбец, поэтому, если вы выберете его с помощью собственного SQL-запроса:
String encryptedStorage = (String) entityManager.createNativeQuery("""
select encode(storage, 'base64')
from Vault
where id = :id
""")
.setParameter("id", 1L)
.getSingleResult();
LOGGER.info("Encoded storage: \n{}", encryptedStorage);
Вы увидите такую ценность, как эта:
Encoded storage: ww0EBwMC3If4VmIUn2x+0j4BKrKR9j0GFpg87Qoz/v21etflhGPE6l9p7O5Sz9yOhynbvr+gwncW
Однако при загрузке объекта в режим гибернации:
Vault vault = entityManager.find( Vault.class, 1L );
assertEquals("my_secret_key", vault.getStorage());
Атрибут storage правильно расшифровывается обратно к исходному значению.
Вывод
Как я объяснил в своей книге “Высокопроизводительная сохраняемость Java”, если вы не воспользуетесь преимуществами базового поставщика JPA или возможностями реляционной базы данных , вы потеряете множество функций, таких как простое шифрование.