加密

Ebean 支持对特定属性进行透明的加密/解密。我们用 @Encrypted 标记我们想要加密的属性,这些属性将根据需要自动加密和解密。

加密/解密可以在客户端/应用程序端或数据库端进行。当我们使用数据库加密时,我们可以在查询中将这些属性用作 whereorder by 子句的一部分。实际上,属性的加密对应用程序是完全透明的。

当我们使用客户端/应用程序端加密/解密时,我们应该只在 where 子句中使用 EQ(等于)运算符。

客户端/应用程序加密

使用客户端/应用程序加密时,属性由 Java 函数(io.ebean.config.Encryptor 接口的实现)加密/解密。默认实现使用基于 AES 128 位的实现,我们还可以将 Ebean 配置为使用其他实现。

使用客户端/应用程序加密加密的属性应该where 子句中与 EQ(等于)运算符一起使用 - 其他运算符不应该与客户端加密属性一起使用。

package io.ebean.config;

/**
 * Used for Java side encryption of properties when DB encryption is not used.
 *
 * By default this is used on non-varchar types such as Blobs.
 */
public interface Encryptor {

  /**
   * Encrypt the data using the key.
   */
  byte[] encrypt(byte[] data, EncryptKey key);

  /**
   * Decrypt the data using the key.
   */
  byte[] decrypt(byte[] data, EncryptKey key);

  /**
   * Encrypt the formatted string value using a key.
   */
  byte[] encryptString(String formattedValue, EncryptKey key);

  /**
   * Decrypt the data returning a formatted string value using a key.
   */
  String decryptString(byte[] data, EncryptKey key);

}

数据库加密

使用数据库端加密/解密时,我们使用数据库存储过程来加密和解密属性。例如,对于 Postgres,Ebean 使用 pgp_sym_encrypt()pgp_sym_decrypt()

数据库加密函数

用于每个平台的默认数据库加密解密函数是

  • Postgres、YugabyteDB - pgp_sym_decrypt()、pgp_sym_encrypt()
  • MySql、MariaDB - aes_encrypt()、aes_decrypt()
  • SQL Server - DecryptByPassPhrase()、EncryptByPassPhrase()
  • Oracle - 需要 dbms_crypto,并使用自定义函数进行加密和解密
  • H2 - 使用“AES”选项进行 encrypt() 和 decrypt()

支持的类型

以下是数据库加密支持的类型。任何不受数据库加密支持的类型都将使用客户端/应用程序加密。

  • 枚举(如果基于 VARCHAR)
  • 字符串(VARCHAR、CHAR、CLOB、LONGVARCHAR)
  • 日期类型 - LocalDate、Date、Joda LocalDate
  • 时间戳类型 - Timestamp、Instant、OffsetDateTime、ZonedDateTime

重要提示:当前不支持以下类型

  • 基本类型
  • 时间戳

EncryptKeyManager

每当对属性进行加密或解密时,都必须使用“密钥”。Ebean 会在内部根据表和列名称向 EncryptKeyManager 索取密钥。

我们必须提供 EncryptKeyManager 的实现。

package io.ebean.config;

/**
 * Determine keys used for encryption and decryption.
 */
@FunctionalInterface
public interface EncryptKeyManager {

  /**
   * Initialise the EncryptKeyManager.
   *
   * This gives the EncryptKeyManager the opportunity to get keys etc.
   */
  default void initialise() {}

  /**
   * Return the key used to encrypt and decrypt a property mapping to the given
   * table and column.
   */
  EncryptKey getEncryptKey(String tableName, String columnName);
}

@Encrypted

使用 @Encrypted 注解标记要加密的属性。默认情况下,属性将为 dbEncryption = true,并且我们明确将其设置为 false 以进行客户端/应用程序端加密。

// use database side encryption
@Encrypted
String name;

// use client side encryption (not db functions)
@Encrypted(dbEncryption=false)
String description;

示例

// Use @Encrypted annotation to mark the encrypted properties

@Entity
@Table(name="patient")
public class Patient {

  @Id
  long id;

  // database side encryption
  @Encrypted
  String name;

  // client side encryption
  @Lob
  @Encrypted(dbEncryption=false)
  String description;

  @Encrypted
  LocalDate dob;
  ...

限制

  • 使用 Java 客户端加密的属性应仅在 WHERE 子句中使用 EQ(等于)运算符
  • H2、Postgres、YugabyteDB、MySql、MariaDB、Sql Server 和 Oracle 内置了数据库加密支持。
  • 我们不能对带位置(1、2、3...)的参数使用加密。我们必须使用命名参数或条件 API 来定义查询。

示例

List<Patient> list =
  new QPatient()
    .name.eq("Rob")
    .findList();

在以下 Postgres SQL 中生成结果

select t0.id, pgp_sym_decrypt(t0.name,?)
from patient t0
where pgp_sym_decrypt(t0.name,?) = ?

配置

在 ebean.properties 文件中指定 EncryptKeyManager 实现,如下所示

ebean.encryptKeyManager=org.example.BasicEncyptKeyManager

使用 DatabaseConfig 以编程方式进行配置。

DatabaseConfig config = DatabaseConfig();
...
EncryptKeyManager keyManager = ...;
config.setEncryptKeyManager(keyManager);
...
Database database = DatabaseFactory.create(config);

EncryptKeyManager 的示例为

package org.example.encrypt;

import io.ebean.config.EncryptKey;
import io.ebean.config.EncryptKeyManager;

public class BasicEncyptKeyManager implements EncryptKeyManager {

  public void initialise() {
    // can load keys or initialise source resources ...
  }

  public EncryptKey getEncryptKey(String tableName, String columnName) {
    // get the key for the given table and column
    String keyValue = ...;
    return new BasicEncryptKey(keyValue);
  }

}

内部

Ebean 会检测何时使用加密属性。它会使用属性的表和列调用 EncryptKeyManager 以获取加密密钥。然后将此密钥作为绑定变量添加到已准备好的语句中。

由于密钥作为绑定变量添加到语句中,因此我们不能对“带位置”的参数使用加密,因为它实际上可以更改其他参数的位置。我们可以使用命名参数或条件 API 来构建查询,但不能使用带位置(1、2、3、4...)的参数。