明/密文数据的转换与同步
开启全密态数据库的内存加密逃生通道功能,且将密钥传输到服务端内存后,支持明文数据和密文数据之间的互相转换和数据同步。可以通过ALTER TABLE语句进行明/密文数据的转换与同步,详细信息请参见《参考》中“SQL参考 > SQL语法 > A > ALTER TABLE”章节。
明文数据与密文数据转换时,涉及修改列类型,需要重建数据,会对操作的表加长时间的排他锁,阻塞对该表的并行读写操作,阻塞时长与数据量成正比,影响用户业务运行。相比离线方式,使用在线方式进行明密文转换时,会减少加排他锁的时间,减少对业务运行的影响。
约束
- 明/密文数据间的转换前需要开启内存解密模式开关,配置密钥信息并创建密钥,建立安全通道并将密钥传输到服务端内存。
- 修改明/密文数据的字段类型后,其他数据库连接需要刷新客户端缓存或者重连,否则会触发报错信息。
- 不支持对加密列直接使用ALTER TABLE语法进行密钥轮转。
- 进行明/密文字段迁移时,明文表不能包含check约束,仅支持check(column is not null)语法。
- 进行明/密文字段迁移时,明文表不能包含外键。
- ALTER TABLE语法其他约束《参考》中“SQL参考 > SQL语法 > A > ALTER TABLE”章节。
- 在线方式进行明/密文数据的转换与同步约束:
- 在线从明文字段到密文字段的数据同步,不支持指定在线DDL中指定多并行追增线程数。
- 在线DDL进行明/密文字段的数据同步转换时,需预留足够的磁盘空间,即剩余空间是所操作的表(及其上索引等附属品)总和。
- 若DDL语句包含本特性支持操作(包含在线修改列类型/精度/长度缩减、表级在线移动表空间、在线加非空/检查/主键/唯一约束、在线VACUUM FULL)以外的操作,DDL默认以非在线方式执行。
- 长事务可能会阻塞在线DDL,建议避免在长事务存在时进行在线DDL。在线DDL执行后期将阻塞DQL操作,对并行的DML操作进行报错。
- 在线DDL不支持在事务内执行,不支持在存储过程中执行。
- 在线DDL执行期间,应尽量避免与DDL并行执行,否则有一定概率出现死锁、报错等;若出现此类情况,可以重新连接客户端进行重试。
- 在线DDL执行过程中会生成名为online$$ddl$$[(表所在的模式+表名)的hash值]的schema。
- Schema的属主默认为初始用户,不建议在线DDL执行中操作此schema,可能会导致中止在线DDL并产生未知错误。
- 用户不可创建online$$为前缀的schema,会有报错提示。若已存在同名schema冲突,在线DDL将不会生效,DDL默认以非在线方式执行,并提示当前DDL语句是因为schema名冲突不可在线。
- 在线DDL进行明密文字段数据同步转换时,若操作失败,表上可能会有明显存储空间膨胀。
- 在线DDL执行过程中发生严重故障,如FATAL、PANIC、数据库故障和节点断连等故障时,创建的临时模式和表的追加状态可能残留,需要手动清理和恢复,通过online_ddl_cleanup()进行手动清理(参见《参考》中“SQL参考 > 函数和操作符 > 其他系统函数”章节)。如不及时清理,可能导致reloption中online_ddl_status、online_ddl_internal_status、online_ddl_start_ctid_internal、online_ddl_end_ctid_internal、online_ddl_toast_end_ctid_internal、online_ddl_toast_index_end_ctid_internal、online_ddl_toast_oid、online_ddl_lock_tag残留,进而影响依赖于以上一系列的操作。不建议在线DDL正常执行过程中进行online_ddl_cleanup()清理操作,可能会导致中止在线DDL或产生未知错误。
- 在线DDL进行明/密文字段数据同步转换时,若使用MODIFY [ COLUMN ] column_name data_type语法且子句包含CHARSET、COLLATE、FIRST、AFTER等任何一个关键字或column_constraint(参见《参考》中“SQL参考 > SQL语法 > A > ALTER TABLE”章节),在线DDL将不会生效,DDL默认以非在线方式执行。
- 在线DDL进行明密文字段数据同步转换时,若使用CHANGE [ COLUMN ] column_name new_column_name data_type语法(参见《参考》中“SQL参考 > SQL语法 > A > ALTER TABLE”章节),在线DDL将不会生效,DDL默认以非在线方式执行。
语法格式
- 修改表字段的数据类型。将字段由明文类型改为密文类型,此场景下,执行该语句会进行整表重写。
ALTER TABLE [ OFFLINE | [ ONLINE [ WITH ( { online_parameter = value } [, ... ] ) ] ] ] [ IF EXISTS ] table_name { ALTER [ COLUMN ] column_name [ SET DATA ] TYPE data_type [ ENCRYPTED WITH ( COLUMN_ENCRYPTION_KEY = column_encryption_key, ENCRYPTION_TYPE = encryption_type_value ) ] } [, ... ];
- 修改表已存在字段的定义(包括将明文类型改为密文类型,NULL约束等),将用新定义替换字段原定义。此语法只能在参数sql_compatibility='B'时使用。
ALTER TABLE [ OFFLINE | [ ONLINE [ WITH ( { online_parameter = value } [, ... ] ) ] ] ] [ IF EXISTS ] table_name MODIFY [ COLUMN ] column_name data_type [ ENCRYPTED WITH ( COLUMN_ENCRYPTION_KEY = column_encryption_key, ENCRYPTION_TYPE = encryption_type_value ) ];
- 修改表已存在字段的名称和定义(包括修改字段名称,将明文类型改为密文类型,NULL约束等),字段新名称不能是已有字段的名称。此语法只能在参数sql_compatibility='B'时使用。
ALTER TABLE [ OFFLINE | [ ONLINE [ WITH ( { online_parameter = value } [, ... ] ) ] ] ] [ IF EXISTS ] table_name CHANGE [ COLUMN ] column_name new_column_name data_type [ ENCRYPTED WITH ( COLUMN_ENCRYPTION_KEY = column_encryption_key, ENCRYPTION_TYPE = encryption_type_value ) ];
参数说明
- online_parameter = value
当指定ONLINE关键字时,用于指定在线DDL选项。若未指定ONLINE关键字或不满足在线DDL的条件,该参数不生效。
取值范围:在线DDL会记录数据重建过程中的DML增量数据到临时表,并采用多轮次循环追增,将增量数据进行重建。为防止由于DML并行数据量过大,在线DDL无法完成追增的情况,用户可以设置该参数,以限制追增次数。
- COLUMN_ENCRYPTION_KEY = column_encryption_key
为ENCRYPTED WITH约束中列加密密钥的名称。
取值范围:字符串,要符合标识符命名规范。
- ENCRYPTION_TYPE = encryption_type_value
为ENCRYPTED WITH约束中的加密类型,encryption_type_value的值为[ DETERMINISTIC | RANDOMIZED ]。
示例
- 使用gsql进行明密文数据迁移。
--解压GaussDB-Kernel_数据库版本号_操作系统版本号_64bit_Gsql.tar.gz安装包,找到脚本gsql_env.sh。 --通过脚本,自动配置环境变量。 source gsql_env.sh -- 1) 初始化环境。 --连接数据库,创建B兼容性数据库db1,创建新用户例如alice。 gsql -p 端口号 -d postgres -r gaussdb=# CREATE DATABASE db1 with DBCOMPATIBILITY = 'B'; CREATE DATABASE gaussdb=# CREATE USER alice PASSWORD '*******'; CREATE ROLE gaussdb=# \q --连接数据库 gsql -p 端口号 -d db1 -r db1=# GRANT USAGE,CREATE ON SCHEMA PUBLIC TO alice; GRANT gsql -p 端口号 -d db1 -U alice -r --创建明文表并插入数据 db1=> CREATE TABLE ce_t1 (c1 int, c2 int default 4, c3 int); CREATE TABLE db1=> INSERT INTO ce_t1 VALUES (1,1,1),(2,2,2),(3,3,3); INSERT 0 3 db1=> \d ce_t1 Table "public.ce_t1" Column | Type | Modifiers --------+---------+----------- c1 | integer | c2 | integer | default 4 c3 | integer | -- 2)开始进行明/密文数据转换。 --连接数据库,使用C3开启内存加解密模式 gsql -p 端口号 -d db1 -U alice -r -C3 --设置用户密码或派生密钥,gsql可以通过password=stdin或key_token=stdin进行交互式输入。 --此处可以使用其他密钥管理方式,比如kms,具体参考使用gsql操作内存解密逃生通道章节 --设置用户密码,密码长度不小于8,包含大、小写字母、数字、符号的其中3种。 db1=> \key_info keyType=user_token,password=********* --或者直接通过对接满足安全强度的密钥(二选一即可),此处密钥为16进制编码。 db1=> \key_info keyType=user_token,key_token=******************************** --关联主密钥。 db1=> CREATE CLIENT MASTER KEY cmk1 WITH ( KEY_STORE = user_token , ALGORITHM = AES_256_GCM); CREATE CLIENT MASTER KEY --创建列密钥 db1=> CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (CLIENT_MASTER_KEY = cmk1, ALGORITHM = AES_256_GCM); CREATE COLUMN ENCRYPTION KEY --传输密钥 db1=> \st Token cache enabled in Trusted Domain. -- 修改列类型为密文类型并进行数据同步,ALTER TABLE ALTER TYPE修改列类型 db1=> ALTER TABLE ONLINE ce_t1 ALTER COLUMN c2 TYPE int ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = cek1, ENCRYPTION_TYPE = DETERMINISTIC); ALTER TABLE db1=> \d ce_t1 Table "public.ce_t1" Column | Type | Modifiers --------+---------+--------------------------- c1 | integer | c2 | integer | default 4::int4 encrypted c3 | integer | db1=> SELECT * FROM ce_t1; c1 | c2 | c3 ----+----+---- 1 | 1 | 1 2 | 2 | 2 3 | 3 | 3 (3 rows) -- 3)清理环境 db1=> DROP TABLE ce_t1; DROP TABLE --删除列密钥 db1=> DROP COLUMN ENCRYPTION KEY cek1; DROP COLUMN ENCRYPTION KEY --删除主密钥。 db1=> DROP CLIENT MASTER KEY cmk1; DROP CLIENT MASTER KEY db1=> \q --连接数据库,删除用户alice,删除数据库db1。 gsql -p 端口号 -d postgres -r gaussdb=# DROP DATABASE db1; DROP DATABASE gaussdb=# DROP USER alice; DROP ROLE
- 使用JDBC进行明密文数据迁移。 使用JDBC操作密态数据库具体操作指导请参见使用JDBC操作密态数据库和使用JDBC驱动操作内存解密逃生通道,此处仅作为数据转换示例。
// 以下用例以gscejdbc.jar驱动为例,如果使用其他驱动包,仅需修改驱动类名和数据库连接的url前缀。 // gsjdbc4.jar: 主类名为“org.postgresql.Driver”,数据库连接的url前缀为“jdbc:postgresql”。 // opengaussjdbc.jar:主类名为“com.huawei.opengauss.jdbc.Driver”,数据库连接的url前缀为“jdbc:opengauss”。 // gscejdbc.jar:主类名为“com.huawei.gaussdb.jdbc.Driver”,数据库连接的url前缀为“jdbc:gaussdb” // gaussdbjdbc.jar:主类名为“com.huawei.gaussdb.jdbc.Driver”,数据库连接的url前缀为“jdbc:gaussdb”。 public static void main(String[] args) { // 驱动类。 String driver = "com.huawei.gaussdb.jdbc.Driver"; // 数据库连接描述符。enable_ce=3表示开启内存加解密模式。 String sourceURL = "jdbc:gaussdb://127.0.0.1:8000/postgres?enable_ce=3"; // 在环境变量USER、PASSWORD分别配置用户名密码。 String username = System.getenv("USER"); String passwd = System.getenv("PASSWORD"); Connection conn = null; try { // 加载驱动 Class.forName(driver); Properties properties = new Properties(); properties.setProperty("user", username); properties.setProperty("password", password); // 此处使用user_token用户口令的密钥管理方式作为示例。 // 使用其他密钥管理方式,比如kms,具体参考设置密态等值查询->使用JDBC操作密态数据库章节 // 设置用户口令进行派生主密钥,口令长度不小于8,包含大、小写字母、数字、符号的其中3种。 properties.setProperty("key_info", "keyType=user_token,password=*********"); // 创建连接 conn = DriverManager.getConnection(sourceURL, properties); System.out.println("Connection succeed!"); // 创建语句对象 Statement stmt = conn.createStatement(); // 定义客户端主密钥:cmk1为主密钥名字,可自行取名,此处使用user_token用户口令的密钥管理方式作为示例 int rc = stmt.executeUpdate("CREATE CLIENT MASTER KEY cmk1 WITH ( KEY_STORE = user_token, ALGORITHM = AES_256_GCM);"); /* 解释:执行本语法是将主密钥的相关信息存储在数据库中,后续访问主密钥时,会通过用户设置口令和数据库存储的盐值迭代派生客户端主密钥 */ // 定义列加密密钥 int rc2 = stmt.executeUpdate("CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (CLIENT_MASTER_KEY = cmk1, ALGORITHM = AES_256_GCM);"); // 创建明文表 int rc3 = stmt.executeUpdate("CREATE TABLE creditcard_info (id_number int, name varchar(50),credit_card varchar(19));"); // 插入数据 int rc4 = stmt.executeUpdate("INSERT INTO creditcard_info VALUES (1,'joe','6217986500001288393');"); // 传输密钥到服务端内存 conn.setClientInfo("send_token", null); // 修改列类型为密文类型并进行数据同步,ALTER TABLE ALTER TYPE修改列类型 int rc5 = stmt.executeUpdate("ALTER TABLE ONLINE creditcard_info ALTER COLUMN name TYPE varchar(50) ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = cek1, ENCRYPTION_TYPE = DETERMINISTIC);"); // 查询加密表 ResultSet rs = null; rs = stmt.executeQuery("select * from creditcard_info where name = 'joe';"); // 删除加密表 int rc6 = stmt.executeUpdate("DROP TABLE IF EXISTS creditcard_info;"); // 删除列加密密钥 int rc7 = stmt.executeUpdate("DROP COLUMN ENCRYPTION KEY IF EXISTS cek1;"); // 删除客户端主密钥 int rc8 = stmt.executeUpdate("DROP CLIENT MASTER KEY IF EXISTS cmk1;"); // 关闭语句对象 stmt.close(); // 关闭连接 conn.close(); } catch (Exception e) { e.printStackTrace(); return; } }