數倉安全:數據脫敏技術深度解析

本文分享自華爲雲社區《GaussDB(DWS)安全管理之數據脫敏原理與使用方法介紹》,作者: VV一笑。

1. 前言

  • 適用版本:8.2.0及以上版本

GaussDB (DWS)產品數據脫敏功能,是數據庫產品內化和夯實數據安全能力的重要技術突破。提供指定用戶範圍內列級敏感數據的脫敏功能,具有靈活、高效、透明、友好等優點,極大地增強產品的數據安全能力,實現敏感數據的可靠保護。

大數據時代的到來,顛覆了傳統業態的運作模式,激發出新的生產潛能。數據成爲重要的生產要素,是信息的載體,數據間的流動也潛藏着更高階維度的價值信息。對於數據控制者和數據處理者而言,如何最大化數據流動的價值,是數據挖掘的初衷和意義。然而,一系列信息泄露事件的曝光,使得數據安全越來越受到廣泛的關注。各國各地區逐步建立健全和完善數據安全與隱私保護相關法律法規,提供用戶隱私保護的法律保障。如何加強技術層面的數據安全和隱私保護,對數據倉庫產品本身提出更多的功能要求,也是數據安全建設最行之有效的辦法。

GaussDB (DWS)產品8.1.1版本發佈數據脫敏特性,提供指定用戶範圍內列級敏感數據的脫敏功能,具有靈活、高效、透明、友好等優點,極大地增強產品的數據安全能力。

2. 數據脫敏概念

數據脫敏(Data Masking),顧名思義,是對於敏感數據進行屏蔽。任何泄露後可能會給社會或個人帶來嚴重危害的數據都屬於常見的敏感數據。個人身份信息,如姓名、身份證號、住址、手機號、郵箱,企業不適合公開信息,如營業執照號碼、稅務登記證、員工薪水,設備信息如IP地址、MAC地址,銀行卡號、受保護的健康信息、知識產權等都屬於敏感信息。對這些敏感信息通過脫敏規則進行數據的變形,實現隱私數據的可靠保護。業界常見的脫敏規則有,替換、重排、加密、截斷、掩碼,用戶也可以根據期望的脫敏算法自定義脫敏規則。

通常,良好的數據脫敏實施,需要遵循如下兩個原則,第一,儘可能地爲脫敏後的應用,保留脫敏前的有意義信息;第二,最大程度地防止黑客進行破解。

數據脫敏分爲靜態數據脫敏和動態數據脫敏。靜態數據脫敏,是數據的“搬移並仿真替換”,是將數據抽取進行脫敏處理後,下發給下游環節,隨意取用和讀寫的,脫敏後數據與生產環境相隔離,滿足業務需求的同時保障生產數據庫的安全。而動態數據脫敏則是在訪問敏感數據的同時實時進行脫敏處理,可以爲不同角色、不同權限、不同數據類型執行不同的脫敏方案,從而確保返回的數據可用而安全。

2.1 DWS動態數據脫敏

GaussDB (DWS)的數據脫敏功能,摒棄業務應用層脫敏依賴性高、代價大等痛點,將數據脫敏內化爲數據庫產品自身的安全能力,提供了一套完整、安全、靈活、透明、友好的數據脫敏解決方案,屬於動態數據脫敏。用戶識別敏感字段後,基於目標字段,綁定內置脫敏函數,即可創建脫敏策略。脫敏策略(Redaction Policy)與表對象是多對一的關係。一個脫敏策略包含表對象、生效條件、脫敏列-脫敏函數對三個關鍵要素,是該表對象上所有脫敏列的集合,不同字段可以根據數據特徵採用不同的脫敏函數,根據生效條件、脫敏列-脫敏函數對的不同也可以在同一張表上設置不同的脫敏策略。當且僅當生效條件爲真時,查詢語句纔會觸發敏感數據的脫敏,而脫敏過程是內置在SQL引擎內部實現的,對生成環境用戶是透明不可見的。

三方代理脫敏工具 vs 數倉DWS脫敏引擎

  • 三方代理工具是用戶與數倉集羣的中轉站,是底座之外的外掛脫敏工具,無法直接參與生成環境,複雜場景較難處理
  • DWS則是基於數倉底座與存儲引擎、SQL引擎直接交互的脫敏引擎,在查詢執行過程中實時脫敏,脫敏結果直接返回給用戶
  • 代理脫敏工具是靜態脫敏,DWS脫敏引擎是動態脫敏

DWS動態脫敏引擎的優勢

  • 良好的底座協同。脫敏引擎貫穿於數倉底座的諸多環節,基於預置脫敏策略,參與SQL引擎的解析、重寫、優化與執行。脫敏過程用戶無感知。
  • 策略可配置。客戶可結合自身業務場景識別敏感數據並對業務表的指定列靈活預置脫敏策略。
  • 策略可擴展。產品內置脫敏函數,可以涵蓋大部分常見脫敏效果,支持用戶自定義脫敏函數。
  • 數據可用性。數據庫內原始敏感數據參與運算,僅在出庫時刻(返回結果時)纔會做脫敏處理。
  • 數據訪問受控。脫敏策略生效條件的用戶均對原始敏感數據不可見。

  • 全場景數據不泄露。底座交互,可減少敏感數據傳輸鏈路潛在的泄露風險,

更加安全可靠,且充分識別各種惡意套取潛在場景並有效防護。

3. 數據脫敏使用方法

動態數據脫敏,是在查詢語句執行過程中,根據生效條件是否滿足,實現實時的脫敏處理。生效條件,通常是針對當前用戶角色的判斷。敏感數據的可見範圍,即是針對不同用戶預設的。系統管理員,具有最高權限,任何時刻對任何表的任何字段都可見。確定受限制用戶角色,是創建脫敏策略的第一步。

敏感信息依賴於實際業務場景和安全維度,以自然人爲例,用戶個體的敏感字段包括:姓名、身份證號、手機號、郵箱地址等等;在銀行系統,作爲客戶,可能還涉及銀行卡號、過期時間、支付密碼等等;在公司系統,作爲員工,可能還涉及薪資、教育背景等;在醫療系統,作爲患者,可能還涉及就診信息等等。所以,識別和梳理具體業務場景的敏感字段,是創建脫敏策略的第二步。

產品內置一系列常見的脫敏函數接口,可以針對不同數據類型和數據特徵,指定參數,從而達到不一樣的脫敏效果。脫敏函數可採用如下三種內置接口,同時支持自定義脫敏函數。三種內置脫敏函數能夠涵蓋大部分場景的脫敏效果,不推薦使用自定義脫敏函數。

  • MASK_NONE:不作脫敏處理,僅內部測試用。

  • MASK_FULL:全脫敏成固定值。

  • MASK_PARTIAL:使用指定的脫敏字符對脫敏範圍內的內容做部分脫敏

不同脫敏列可以採用不同的脫敏函數。比如,手機號通常顯示後四位尾號,前面用"*"替換;金額統一顯示爲固定值0,等等。確定脫敏列需要綁定的脫敏函數,是創建脫敏策略的第三步。

以某公司員工表emp,表的屬主用戶alice以及用戶matu、july爲例,簡單介紹數據脫敏的使用過程。其中,表emp包含員工的姓名、手機號、郵箱、發薪卡號、薪資等隱私數據,用戶alice是人力資源經理,用戶matu和july是普通職員。

假設表、用戶及用戶對錶emp的查看權限均已就緒。

1.創建脫敏策略mask_emp,僅允許alice查看員工所有信息,matu和july對發薪卡號、薪資均不可見。字段card_no是數值類型,採用MASK_FULL全脫敏成固定值0;字段card_string是字符類型,採用MASK_PARTIAL按指定的輸入輸出格式對原始數據作部分脫敏;字段salary是數值類型,採用數字9部分脫敏倒數第二位前的所有數位值。

postgres=# CREATE REDACTION POLICY mask_emp ON emp WHEN (current_user != 'alice')

ADD COLUMN card_no WITH mask_full(card_no),

ADD COLUMN card_string WITH mask_partial(card_string, 'VVVVFVVVVFVVVVFVVVV','VVVV-VVVV-VVVV-VVVV','#',1,12), 

ADD COLUMN salary WITH mask_partial(salary, '9', 1, length(salary) - 2);

切換到matu和july,查看員工表emp。

postgres=> SET ROLE matu PASSWORD 'Gauss@123';

postgres=> SELECT * FROM emp;

 id | name |  phone_no   | card_no |     card_string     |        email         |   salary   |      birthday       

----+------+-------------+---------+---------------------+----------------------+------------+---------------------

  1 | anny | 13420002340 |       0 | ####-####-####-1234 | [email protected]      | 99999.9990 | 1999-10-02 00:00:00

  2 | bob  | 18299023211 |       0 | ####-####-####-3456 | [email protected]    |  9999.9990 | 1989-12-12 00:00:00

  3 | cici | 15512231233 |         |                     | [email protected] |            | 1992-11-06 00:00:00

(3 rows)

postgres=> SET ROLE july PASSWORD 'Gauss@123';

postgres=> SELECT * FROM emp;

 id | name |  phone_no   | card_no |     card_string     |        email         |   salary   |      birthday       

----+------+-------------+---------+---------------------+----------------------+------------+---------------------

  1 | anny | 13420002340 |       0 | ####-####-####-1234 | [email protected]      | 99999.9990 | 1999-10-02 00:00:00

  2 | bob  | 18299023211 |       0 | ####-####-####-3456 | [email protected]    |  9999.9990 | 1989-12-12 00:00:00

  3 | cici | 15512231233 |         |                     | [email protected] |            | 1992-11-06 00:00:00

(3 rows)

2.由於工作調整,matu進入人力資源部參與公司招聘事宜,也對員工所有信息可見,修改策略生效條件。

postgres=> ALTER REDACTION POLICY mask_emp ON emp WHEN(current_user NOT IN ('alice', 'matu'));

切換到用戶matu和july,重新查看員工表emp。

postgres=> SET ROLE matu PASSWORD 'Gauss@123';

postgres=> SELECT * FROM emp;

 id | name |  phone_no   |     card_no      |     card_string     |        email         |   salary   |      birthday       

----+------+-------------+------------------+---------------------+----------------------+------------+---------------------

  1 | anny | 13420002340 | 1234123412341234 | 1234-1234-1234-1234 | [email protected]      | 10000.0000 | 1999-10-02 00:00:00

  2 | bob  | 18299023211 | 3456345634563456 | 3456-3456-3456-3456 | [email protected]    |  9999.9900 | 1989-12-12 00:00:00

  3 | cici | 15512231233 |                  |                     | [email protected] |            | 1992-11-06 00:00:00

(3 rows)

postgres=> SET ROLE july PASSWORD 'Gauss@123';

postgres=> SELECT * FROM emp;

 id | name |  phone_no   | card_no |     card_string     |        email         |   salary   |      birthday       

----+------+-------------+---------+---------------------+----------------------+------------+---------------------

  1 | anny | 13420002340 |       0 | ####-####-####-1234 | [email protected]      | 99999.9990 | 1999-10-02 00:00:00

  2 | bob  | 18299023211 |       0 | ####-####-####-3456 | [email protected]    |  9999.9990 | 1989-12-12 00:00:00

  3 | cici | 15512231233 |         |                     | [email protected] |            | 1992-11-06 00:00:00

(3 rows)

3.員工信息phone_no、email和birthday也是隱私數據,更新脫敏策略mask_emp,新增三個脫敏列。

postgres=> ALTER REDACTION POLICY mask_emp ON emp ADD COLUMN phone_no WITH mask_partial(phone_no, '*', 4);

postgres=> ALTER REDACTION POLICY mask_emp ON emp ADD COLUMN email WITH mask_partial(email, '*', 1, position('@' in email));

postgres=> ALTER REDACTION POLICY mask_emp ON emp ADD COLUMN birthday WITH mask_full(birthday);

切換到用戶july,查看員工表emp。

postgres=> SET ROLE july PASSWORD 'Gauss@123';

postgres=> SELECT * FROM emp;

 id | name |  phone_no   | card_no |     card_string     |        email         |   salary   |      birthday       

----+------+-------------+---------+---------------------+----------------------+------------+---------------------

  1 | anny | 134******** |       0 | ####-####-####-1234 | ********163.com      | 99999.9990 | 1970-01-01 00:00:00

  2 | bob  | 182******** |       0 | ####-####-####-3456 | ***********qq.com    |  9999.9990 | 1970-01-01 00:00:00

  3 | cici | 155******** |         |                     | ************sina.com |            | 1970-01-01 00:00:00

(3 rows)

4.考慮用戶交互的友好性,GaussDB (DWS) 提供系統視圖redaction_policies和redaction_columns,方便用戶直接查看更多脫敏信息。

postgres=> SELECT * FROM redaction_policies;

 object_schema | object_owner | object_name | policy_name |            expression             | enable | policy_description 

---------------+--------------+-------------+-------------+-----------------------------------+--------+--------------------

 public        | alice        | emp         | mask_emp    | ("current_user"() = 'july'::name) | t      | 

(1 row)

postgres=> SELECT object_name, column_name, function_info FROM redaction_columns;

 object_name | column_name |                                             function_info                                             

-------------+-------------+-------------------------------------------------------------------------------------------------------

 emp         | card_no     | mask_full(card_no)

 emp         | card_string | mask_partial(card_string, 'VVVVFVVVVFVVVVFVVVV'::text, 'VVVV-VVVV-VVVV-VVVV'::text, '#'::text, 1, 12)

 emp         | email       | mask_partial(email, '*'::text, 1, "position"(email, '@'::text))

 emp         | salary      | mask_partial(salary, '9'::text, 1, (length((salary)::text) - 2))

 emp         | birthday    | mask_full(birthday)

 emp         | phone_no    | mask_partial(phone_no, '*'::text, 4)

(6 rows)

5.突然某一天,公司內部可共享員工信息時,直接刪除表emp的脫敏策略mask_emp即可。

postgres=> DROP REDACTION POLICY mask_emp ON emp;

更多用法詳情,請參考GaussDB (DWS)產品文檔。

4. 可算不可見的數據脫敏

在使用數據脫敏功能時,存在先對敏感數據進行加工計算在進行輸出的情況。對於這種情況,如果採用經過脫敏後的數據進行庫內計算,那麼在例如聚集函數、過濾條件等條件下經過脫敏的數據本身會對查詢結果產生影響,因此針對這一現象對數據脫敏引入了可算不可見功能。所謂可算不可見,就是在數據庫內使用原始的敏感數據參與加工計算,只在出庫時對敏感數據進行脫敏處理。想要使用可算不可見功能,需要設置開關enable_redactcol_computable=on。

目前支持將敏感數據直接參與加工計算的場景如下:

  • SELECT nullif(salary, 1) FROM emp;投影列表達式nullif

  • SELECT email LIKE ‘%.com’ FROM emp;投影列LIKE表達式

  • SELECT to_days(birth) FROM david.emp;投影列函數to_days

  • SELECT count(*) FROM emp;聚集函數

  • SELECT * FROM emp WHERE cardid IS NOT NULL; 過濾條件

  • SELECT name, avg(salary) * 12 FROM emp GROUP BY name; 分組條件(name是脫敏字段)

  • SELECT (SELECT salary+10 FROM emp ORDER BY id LIMIT 1);子查詢位置投影列表達式

  • 兩表使用敏感字段作關聯條件

  • CTE表達式投影列

出庫時刻會觸發數據脫敏的場景如下:

  • 表查詢

  • 視圖查詢

  • DML RETURNING子句

  • COPY導出

  • GDS外表導出

  • 遊標CURSOR… FETCH

  • 存儲過程定義使用脫敏表,查詢存儲過程

4.1 脫敏策略的繼承

對於INSERT/UPDATE/MERGE INTO/CREATE TABLE AS語句,當子查詢是對某個敏感字段的投影操作時,就會觸發脫敏繼承,從而使得包含了敏感信息的新表上含有與源表相同的脫敏策略,避免了通過在新表中插入源表敏感數據,再查詢新表導致敏感數據泄露的問題。此外,脫敏策略的繼承屬於表維度的活動,繼承活動不關注子查詢部分當前會話或角色條件下脫敏策略是否生效。

繼承脫敏策略的第一步是進行敏感血緣分析,對於任何用戶執行DML語句,都遍歷子查詢部分源表及其目標投影列,一旦源表存在脫敏策略且目標投影列是脫敏字段,則認爲使用源表插入/更新目標表數據時,存在暴露源表敏感數據的風險。

對脫敏策略進行繼承時,首先從從遍歷標記的源表敏感信息中,生成作用於目標表的脫敏策略信息及脫敏字段信息。隨後系統內置生成策略創建語句並執行寫入系統表pg_redaction_policy/pg_redaction_column,對於內置創建的脫敏策略,統一命名爲“inherited_rp”。最後再將系統表元數據inherit標記字段爲true。

注意,如果INSERT執行會話/用戶滿足觸發條件,當帶有RETURNING子句打印插入結果時,返回結果會脫敏,日誌信息“Parent redaction policies/columns”會記錄策略繼承的來源。

隨着脫敏策略繼承行爲的引入,產生了一些脫敏策略衝突的場景。例如SELECT語句查詢目標列非原始敏感字段,而是敏感字段的複雜表達式,表達式先算後脫敏,此時如何界定脫敏行爲?SETOP集合運算兩個子分支的對應同一目標列採用不同的脫敏效果,此時如何界定外層語句目標列的脫敏結果?多次INSERT/UPDATE操作的敏感血緣分析中,同一目標列的源表投影列採用的不一樣的脫敏效果,且源表策略的生效條件也可能不同,此時脫敏策略該如何繼承?

針對這些衝突場景,基於保護用戶任何敏感數據不致泄露優先於敏感數據脫敏效果不具有原始特徵的原則,當遇到脫敏效果衝突時,都提升爲通用脫敏效果mask_full。mask_full是可覆蓋任何數據類型的全脫敏函數,只關注表達式返回值類型,可以保證脫敏數據不會泄露,但是會導致脫敏結果無法表徵原始數據特徵,使得脫敏結果的可讀性較差。此外,針對length、count等函數表達式,其計算結果不會暴露任何原始數據特徵及信息,所以提供了ALTER FUNCTION … [NOT] MASKED語法,支持用戶手動配置不脫敏函數白名單。

4.2 防護惡意套取

已知某些敏感信息,通過多次試探性匹配,反向佐證可見的用戶信息,從而竊取用戶的隱私數據。藉助助等值判斷形式表達式的過濾條件或投影操作試探性匹配敏感信息。這些通過已知常量值和等值/類等值判斷表達式來進行套取用戶隱私數據的行爲成爲惡意套取。

postgres=> SELECT name FROM emp WHERE name in('張三');

INFO: The result of target column {"name"} is masked.

 name

------

  張*

 (1 row)

如上述例子所示,儘管已經對用戶的name信息進行了脫敏,但由於查詢條件是針對’張三’用戶,因此即使被脫敏爲’張*’,我們還是可以很容易確定這裏的脫敏前信息是‘張三’,從而導致張三用戶的信息存在泄漏的風險。

針對這一問題,我們採用了“悲觀主義”模式,任何常量等值判斷都可能存在惡意套取的風險,都應當禁止,實例如下:

postgres=> SELECT name FROM emp WHERE name in('張三');

ERROR:  Redaction column "name" cannot be referenced in equivalence conditions with const value.

HINT:  Please use EXPLAIN command to see more details.

禁止使用常量惡意套取的場景總結如下:

1.脫敏字段的常量等值判斷表達式、複合表達式、等價表達式

2.假設name字段是脫敏字段且當前會話滿足策略觸發條件,則語句有如下(且不限於)特徵,存在惡意套取風險,禁止執行:

• name = '張三‘

• name = ‘張三’ OR name = ‘李四’

• name in (‘張三’, ‘李四’)

• CASE name WHEN ‘張三’ THEN true …

• CASE WHEN name in (‘張三’, ‘李四’) THEN …

• 高級包dbms_output.put_line

3.語句執行會報錯:ERROR: Redaction column “name” cannot be referenced in equivalence conditions with const value.

5. 數據脫敏實現原理

GaussDB (DWS)數據脫敏功能,基於SQL引擎既有的實現框架,在受限用戶執行查詢語句過程中,實現外部不感知的實時脫敏處理。關於其內部實現,如上圖所示。我們將脫敏策略(Redaction Policy)視爲表對象上綁定的規則,在優化器查詢重寫階段,遍歷Query Tree中TargetList的每個TargetEntry,如若涉及基表的某個脫敏列,且當前脫敏規則生效(即滿足脫敏策略的生效條件且enable開啓狀態),則斷定此TargetEntry中涉及要脫敏的Var對象,此時,遍歷脫敏列系統表pg_redaction_column,查找到對應脫敏列綁定的脫敏函數,將其替換成對應的FuncExpr即可。經過上述對Query Tree的重寫處理,優化器會自動生成新的執行計劃,執行器遵照新的計劃執行,查詢結果將對敏感數據做脫敏處理。

帶有數據脫敏的語句執行,相較於原始語句,增加了數據脫敏的邏輯處理,勢必會給查詢帶來額外的開銷。這部分開銷,主要受表的數據規模、查詢目標列涉及的脫敏列數、脫敏列採用的脫敏函數三方面因素影響。

針對簡單查詢語句,以tpch表customer爲例,針對上述因素展開測試,如下圖所示。

圖(a)、(b)中基表customer根據字段類型和特徵,既有采用MASK_FULL脫敏函數的,也有采用MASK_PARTIAL脫敏函數的。MASK_FULL對於任何長度和類型的原始數據,均只脫敏成固定值,所以,輸出結果相較於原始數據,差異很大。圖(a)顯示不同數據規模下,脫敏和非脫敏場景簡單查詢語句的執行耗時。實心圖標爲非脫敏場景,空心圖標爲被限制用戶,即脫敏場景。可見,數據規模越大,帶有脫敏的查詢耗時與原始語句差異越大。圖(b)顯示10x數據規模下查詢涉及脫敏列數不同對於語句執行性能的影響。涉及1列脫敏列時,帶有脫敏的查詢比原始語句慢,追溯發現,此列採用的是MASK_PARTIAL部分脫敏函數,查詢結果只是改變了結果的格式,結果內容的長度並未變化,符合“帶有脫敏的語句執行會有相應的性能劣化”的理論猜想。隨着查詢涉及脫敏列數的增加,我們發現一個奇怪的現象,脫敏場景反倒比原始語句執行更快。進一步追溯多列場景下脫敏列關聯的脫敏函數,發現,正是因爲存在使用MASK_FULL全脫敏函數的脫敏列,導致輸出結果集部分相比原始數據節省很多時間開銷,從而多列查詢下帶有數據脫敏的簡單查詢反倒提速不少。

爲了佐證上述猜測,我們調整脫敏函數,所有脫敏列均採用MASK_PARTIAL對原始數據做部分脫敏,從而能夠在脫敏結果上保留原始數據的外部可讀性。於是,如圖©所示,當脫敏列均關聯部分脫敏函數時,帶有數據脫敏的語句比原始語句劣化10%左右,理論上講,這種劣化是在可接受範圍的。上述測試僅針對簡單的查詢語句,當語句複雜到帶有聚集函數或複雜表達式運算時,可能這種性能劣化會更明顯。

6. 總結

GaussDB (DWS)產品數據脫敏功能,是數據庫產品內化和夯實數據安全能力的重要技術突破,主要涵蓋以下三個方面:

一套簡單、易用的數據脫敏策略語法;

一系列可覆蓋常見隱私數據脫敏效果的、靈活配置的內置脫敏函數;

一個完備、便捷的脫敏策略應用方案,使得原始語句在執行過程中可以實時、透明、高效地實現脫敏。

總而言之,此數據脫敏功能可以充分滿足客戶業務場景的數據脫敏訴求,支持常見隱私數據的脫敏效果,實現敏感數據的可靠保護。

【溫馨提示】使用過程中,如有疑問,歡迎隨時交流反饋。

點擊關注,第一時間瞭解華爲雲新鮮技術~

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章