PostgreSQL 安全管理 数据脱敏 Anonymizer 脱敏函数

1 背景知识

本章将介绍 Anonymizer 提供的提供了8种脱敏函数。

根据数据类型的不同,对于不同的列使用不同的策略。

  1. 对于 姓名 或者 直接标识符,高级伪造(Advanced Faking) 通常非常有用。
  2. 对于外键适合于 随机排列(Shuffing)
  3. 对于 数值日期 数据类型, 非常适合添加噪音(Adding Noise)`。
  4. 对于 email电话号码 数据类型,非常适合部分脱敏(Partial Scrambling)。
  5. 等等。

2 数据销毁函数(Destruction)

首先,对数据进行最快,最安全的方法就是销毁数据。
隐藏列的内容的最佳方法就是使用一个静态值替换所有的值。

例如:你可以使用 'CONFIDENTIAL' 替换字段内容。

SECURITY LABEL FOR anon
  ON COLUMN people.phone
  IS 'MASKED WITH VALUE ''CONFIDENTIAL'' ';

3 噪音干扰函数(Adding Noise)

噪音脱敏也称之为方差。通过偏移日期和数值到达数据脱敏的效果。
例如,通过对工资应用 +/- 10% 的方差,确保数据集保持有意义。

anon.noise(original_value,ratio) :函数 original_value 可以是 intbigintdouble precision 。如果噪音比例为 0.33 ,返回值将会按照 +/-33% 偏移。

SECURITY LABEL FOR anon ON COLUMN people.id IS 'MASKED WITH FUNCTION anon.noise(id, 0.33)';

anon.dnoise(original_value, interval):函数 original_value 可以是 datetimestamp,或者 time。如果 interval='2 days',返回值将会按照 +/- 2 days 偏移。

SECURITY LABEL FOR anon ON COLUMN people.date IS 'MASKED WITH FUNCTION anon.dnoise(date,$2 days$)';
Warning

noise() 脱敏函数容易受到重复性攻击,尤其是使用动态脱敏时。被屏蔽的用户通过多次请求值来猜测原始值,然后只需要使用 AVG() 函数即可获得近似值。请参考 噪音函数攻击。总之,这些工鞥适合于导出脱敏静态脱敏。应该避免在动态脱敏中使用。

类似的函数有 add_noise_on_numeric_columnadd_noise_on_datetime_column

4 随机化函数(Randomization)

此扩展提供了大量的函数用于生成随机数据。

4.1 基本随机值函数

4.2 数区间中随机值函数

在数据区间中随机取值

Note

这些函数都包含下限和上限。例如,anon.random_int_between(1,3) 返回值是包含1、2、3.

4.3 随机数组函数

ramdom_in 函数将会随机返回数组中的一个元素。

4.4 随机枚举函数

对于数据使用枚举类型特别有用。

CREATE TYPE card AS ENUM ('visa', 'mastercard', ‘amex’);
SELECT anon.random_in_enumCARD;
//屏幕输出:
 random_in_enum
----------------
 mastercard
CREATE TABLE customer (
  id INT,
  ...
  credit_card CARD
);

SECURITY LABEL FOR anon ON COLUMN customer.creditcard
IS 'MASKED WITH FUNCTION anon.random_in_enum(creditcard)'

4.5 随机区间函数

对于 RANGE 数据类型可以描述区间值的强大方式,其中定义包含或排除边界。
PostgreSQL: Documentation: 16: 8.17. Range Types
对于每个 RANGE 数据类型都有一个函数。

Note

如果 RANGE 无边界,则无法获取随机值。例如,anon.random_in_int4range('[2022,)') 返回值为NULL。

5 伪造(Faking)

伪造脱敏的原理是使用随机但可信的数值替换敏感数据。这样做的目的避免正式数据泄露。但是仍然适合测试、数据分析和数据处理。

要使用伪造函数,必须现在数据中启动扩展引擎。

SELECT anon.init();

init() 函数导入一个包含随机数据(银行账户、姓名、城市等)默认数据集。
此数据集为英文版,数据量较小(每个类别为1000个值)。如果你想使用本地化数据或者加载特定数据集,请阅读 自定义伪造数据

对于 TEXTVARCHAR 列,可以使用经典的 Lorem Ipsum 生成器。

6 高级伪造

伪造数据是一个复杂的主题。对于高级的伪造数据,请使用使用扩展程序 PostgreSQL Faker,此扩展是基于 Faker python library 基础做的二次开发。

CREATE SCHEMA faker;
CREATE EXTENSION faker SCHEMA faker;
SELECT faker.faker('de_DE');
SELECT faker.first_name_female();
//屏幕输出:
 first_name_female
-------------------
 Mirja

7 假名化(Pseudonymization)

假名化和伪造类似,都是生成类似的真实值。区别在于假名化返回值是确定的:函数会根据 SEEDSLAT 参数返回相同的假名化。

要使用伪造函数,必须现在数据中启动扩展引擎。

SELECT anon.init();

第二个参数 SALT 是可选的,设置这个值是为了增加脱敏的复杂性,避免字典攻击和暴力攻击。如果没有给定 SALT 值,则会使用 anon.salt GUC 参数替换。请见 通用哈希 章节。

第一个参数 SEED 是与生成数据的主题相关的信息。例如,我们可以使用某人的登录名作为 SEED,为其生成伪造的电子邮件地址。

SECURITY LABEL FOR anon
  ON COLUMN users.emailaddress
  IS 'MASKED WITH FUNCTION anon.pseudo_email(users.login) ';
Note

如果想要使用假名化函数生成唯一值,你需要自定义一个比现有真实数据大得多的自定义伪造数据集。否则很有可能会出现重复,即会出现相同的伪列值。

Warning

假名化与匿名化经常被混淆,但实际上他们有两个不同的目的:假名化任与真实数据相关联。GDPR

8 通用哈希(Generic hashing)

从理论上将,哈希不是一种有效的脱敏技术,然而在实际生产中有必要生成原始数据的哈希值。

例如,对于一对主外键,可以保持参照完整性不变进行脱敏。

ALTER DATABASE foo SET anon.salt TO 'xsfnjefnjsnfjsnf';
ALTER DATABASE foo SET anon.algorithm TO 'sha384';
Note

哈希化不是真正的脱敏方式。因为可以使用 SALTalgorithm 这两个元素,进行暴力反向破解。所以 SALTalgorithm 需要收到保护注意不要泄露。

此外:哈希函数将会返回很长的字符串,如下所示:

SELECT anon.hash('bob');
               hash
--------------------------------------------
95b6accef02c5a725a8c9abf19ab5575f99ca3d9997984181e4b3f81d96cbca4d0977d694ac490350e01d0d213639909987ef52de8e44d6258d536c55e427397

对于某些列来说,这可能太长,你需要剪切哈希值的某些部分,以便放入字段中。

例如,电话号码的字段为18 位,你可以使用以下函数进行转换。

SECURITY LABEL FOR anon ON COLUMN people.phone_number
IS 'MASKED WITH FUNCTION anon.left(anon.hash(phone_number),12)';

SECURITY LABEL FOR anon ON COLUMN call_history.fk_phone_number
IS 'MASKED WITH FUNCTION anon.left(anon.hash(fk_phone_number),12)';
Warning

当然,如果将值剪切位12个字符时,会增加冲突的记录,请自行评估方案的可行性。

9 部分脱敏(Partial Scrambling)

部分脱敏将会部分数据进行脱敏。例如:信用卡号码可以替换为"40XX XXXX XXXX XX96"。

select anon.partial('554221108910240067',1,'xxxxxxxxxxx',3);
//屏幕输出:
     partial     
-----------------
 5xxxxxxxxxxx067
(1 row)

10 条件脱敏(Conditional Masking )

条件脱敏是指只对表中的某些数据,或某些行进行脱敏。

例如,如果你想要仅脱敏包含数值的数据,NULL值保留,可以在 CASE WHEN x THEN y ELSE z 语句中使用 anon.ternary 函数。

SECURITY LABEL FOR anon ON COLUMN player.score
  IS 'MASKED WITH FUNCTION anon.ternary(score IS NULL,
                                        NULL,
anon.random_int_between(0,100));

如果还要排除表中的某些行。比如保留某些用户的密码,以便他们仍能被应用程序所使用。

SECURITY LABEL FOR anon ON COLUMN account.password IS 'MASKED WITH FUNCTION anon.ternary( id > 1000, NULL::TEXT, password)';
Warning

条件脱敏会在原始数据和屏蔽数据之间建立一种关联性,例如,如果 deaders_date(死亡日期) 字段保留了NULL,就会显示那些人实际还活着。

11 泛化脱敏(Generalization)

泛化的原理是用一个值的区间范围替换原来的值。例如,“保罗42岁”,可以泛化为“保罗40 岁到50岁”。

泛化脱敏函数是一种数据类型转换,所以无法用于动态脱敏。不过他们在创建匿名视图时非常有用。

SELECT * FROM patient;
 id |   name   |  zipcode |   birth    |    disease
----+----------+----------+------------+---------------
  1 | Alice    |    47678 | 1979-12-29 | Heart Disease
  2 | Bob      |    47678 | 1959-03-22 | Heart Disease
  3 | Caroline |    47678 | 1988-07-22 | Heart Disease
  4 | David    |    47905 | 1997-03-04 | Flu
  5 | Eleanor  |    47909 | 1999-12-15 | Heart Disease
  6 | Frank    |    47906 | 1968-07-04 | Cancer
  7 | Geri     |    47605 | 1977-10-30 | Heart Disease
  8 | Harry    |    47673 | 1978-06-13 | Cancer
  9 | Ingrid   |    47607 | 1991-12-12 | Cancer

我们可以在表上建立一个视图,对邮政编码和出生日期进行泛化脱敏。

CREATE VIEW anonymized_patient AS
SELECT
    'REDACTED' AS lastname,
    anon.generalize_int4range(zipcode,100) AS zipcode,
    anon.generalize_tsrange(birth,'decade') AS birth
    disease
FROM patients;

脱敏之后的视图查询后应该是这样的。

SELECT * FROM anonymized_patient;
 lastname |   zipcode     |           birth             |    disease
----------+---------------+-----------------------------+---------------
 REDACTED | [47600,47700) | ["1970-01-01","1980-01-01") | Heart Disease
 REDACTED | [47600,47700) | ["1950-01-01","1960-01-01") | Heart Disease
 REDACTED | [47600,47700) | ["1980-01-01","1990-01-01") | Heart Disease
 REDACTED | [47900,48000) | ["1990-01-01","2000-01-01") | Flu
 REDACTED | [47900,48000) | ["1990-01-01","2000-01-01") | Heart Disease
 REDACTED | [47900,48000) | ["1960-01-01","1970-01-01") | Cancer
 REDACTED | [47600,47700) | ["1970-01-01","1980-01-01") | Heart Disease
 REDACTED | [47600,47700) | ["1970-01-01","1980-01-01") | Cancer
 REDACTED | [47600,47700) | ["1990-01-01","2000-01-01") | Cancer

泛化脱敏后的值仍然有用,因为他仍然是真实的。但准确度非常低。

PostgreSQL 提供了多 RANGE 函数,非常适合日期和数值。

12 使用 pg_catalog 函数

自从 1.3 版本起,pg_catalog 模式下的函数便设置为不可信。为了防止用户在 pg+ 脱敏规则使用不应该作为脱敏函数的复杂函数,例如:pg_catalog.ts_statpg_catalog.ts_stat

为了方便起见,此扩展将会和 pg_catalog 函数进行绑定使用。

13 自定义脱敏函数

在这个小结您可以使用自己的函数作为脱敏函数。自定义脱敏函数必须是破坏性的(像 部分脱敏),或者在数据集中插入随机性(像伪造)。

自定义脱敏函数主要是针对复杂的数据类型,例如需要隐藏 JSON 字段的某些部分,以下是一个简单的例子。

CREATE TABLE company (
  business_name TEXT,
  info JSONB
)

信息字段包含JSON内容如下:

SELECT jsonb_pretty(info) FROM company WHERE business_name = 'Soylent Green';
//屏幕输出:
           jsonb_pretty
----------------------------------
 {
     "employees": [
         {
             "lastName": "Doe",
             "firstName": "John"
         },
         {
             "lastName": "Smith",
             "firstName": "Anna"
         },
         {
             "lastName": "Jones",
             "firstName": "Peter"
         }
     ]
 }
(1 row)

使用 PostgreSQL JSON functions and operators,将敏感数据进行脱敏。

CREATE SCHEMA custom_masks;

-- This step requires superuser privilege
SECURITY LABEL FOR anon ON SCHEMA custom_masks IS 'TRUSTED';

CREATE FUNCTION custom_masks.remove_last_name(j JSONB)
RETURNS JSONB
VOLATILE
LANGUAGE SQL
AS $func$
SELECT
  json_build_object(
    'employees' ,
    array_agg(
      jsonb_set(e ,'{lastName}', to_jsonb(anon.fake_last_name()))
    )
  )::JSONB
FROM jsonb_array_elements( j->'employees') e
$func$;

检查函数是否正确。

SELECT custom_masks.remove_last_name(info) FROM company;

如果验证成功使用此函数进行脱敏。

SECURITY LABEL FOR anon ON COLUMN company.info
IS 'MASKED WITH FUNCTION custom_masks.remove_last_name(info)';

查看脱敏后的数据

# SELECT anonymize_table('company');
# SELECT jsonb_pretty(info) FROM company WHERE business_name = 'Soylent Green';
            jsonb_pretty
-------------------------------------
 {
     "employees": [                 +
         {                          +
             "lastName": "Prawdzik",+
             "firstName": "John"    +
         },                         +
         {                          +
             "lastName": "Baltazor",+
             "firstName": "Anna"    +
         },                         +
         {                          +
             "lastName": "Taylan",  +
             "firstName": "Peter"   +
         }                          +
     ]                              +
 }
(1 row)

14 小结

这里还有一些其他脱敏函数,请查看具体适用场景。
PostgreSQL Anonymizer 脱敏函数 anon.anonymize_column
PostgreSQL Anonymizer 脱敏函数 anon.anonymize_table
PostgreSQL Anonymizer 脱敏函数 anon.shuffle_column
PostgreSQL 安全管理 数据脱敏 Anonymizer 泛化脱敏(Generalization)
PostgreSQL 安全管理 数据脱敏 Anonymizer 自动脱敏(Searching for Identifiers)