摘要:如何增強技術層面的數據安全和隱私保護,對數據倉庫產品自己提出更多的功能要求,也是數據安全建設最行之有效的辦法。
本文分享自華爲雲社區《GaussDB(DWS)安全:隱私保護現真招兒——數據脫敏》,原文做者:wo華噠噠。算法
大數據時代的到來,顛覆了傳統業態的運做模式,激發出新的生產潛能。數據成爲重要的生產要素,是信息的載體,數據間的流動也潛藏着更高階維度的價值信息。對於數據控制者和數據處理者而言,如何最大化數據流動的價值,是數據挖掘的初衷和意義。然而,一系列信息泄露事件的曝光,使得數據安全愈來愈受到普遍的關注。數據庫
各國各地區逐步創建健全和完善數據安全與隱私保護相關法律法規,提供用戶隱私保護的法律保障。如何增強技術層面的數據安全和隱私保護,對數據倉庫產品自己提出更多的功能要求,也是數據安全建設最行之有效的辦法。express
數據脫敏(Data Masking),顧名思義,是屏蔽敏感數據,對某些敏感信息(好比,身份證號、手機號、卡號、客戶姓名、客戶地址、郵箱地址、薪資等等 )經過脫敏規則進行數據的變形,實現隱私數據的可靠保護。業界常見的脫敏規則有,替換、重排、加密、截斷、掩碼,用戶也能夠根據指望的脫敏算法自定義脫敏規則。
segmentfault
一般,良好的數據脫敏實施,須要遵循以下兩個原則,第一,儘量地爲脫敏後的應用,保留脫敏前的有意義信息;第二,最大程度地防止黑客進行破解。安全
數據脫敏分爲靜態數據脫敏和動態數據脫敏。靜態數據脫敏,是數據的「搬移並仿真替換」,是將數據抽取進行脫敏處理後,下發給下游環節,隨意取用和讀寫的,脫敏後數據與生產環境相隔離,知足業務需求的同時保障生產數據庫的安全。動態數據脫敏,在訪問敏感數據的同時實時進行脫敏處理,能夠爲不一樣角色、不一樣權限、不一樣數據類型執行不一樣的脫敏方案,從而確保返回的數據可用而安全。框架
GaussDB (DWS)的數據脫敏功能,摒棄業務應用層脫敏依賴性高、代價大等痛點,將數據脫敏內化爲數據庫產品自身的安全能力,提供了一套完整、安全、靈活、透明、友好的數據脫敏解決方案,屬於動態數據脫敏。用戶識別敏感字段後,基於目標字段,綁定內置脫敏函數,便可建立脫敏策略。脫敏策略(Redaction Policy)與表對象是一一對應的。一個脫敏策略包含表對象、生效條件、脫敏列-脫敏函數對三個關鍵要素,是該表對象上全部脫敏列的集合,不一樣字段能夠根據數據特徵採用不一樣的脫敏函數。當且僅當生效條件爲真時,查詢語句纔會觸發敏感數據的脫敏,而脫敏過程是內置在SQL引擎內部實現的,對生成環境用戶是透明不可見的。
函數
動態數據脫敏,是在查詢語句執行過程當中,根據生效條件是否知足,實現實時的脫敏處理。生效條件,一般是針對當前用戶角色的判斷。敏感數據的可見範圍,便是針對不一樣用戶預設的。系統管理員,具備最高權限,任什麼時候刻對任何表的任何字段均可見。肯定受限制用戶角色,是建立脫敏策略的第一步。post
敏感信息依賴於實際業務場景和安全維度,以天然人爲例,用戶個體的敏感字段包括:姓名、身份證號、手機號、郵箱地址等等;在銀行系統,做爲客戶,可能還涉及銀行卡號、過時時間、支付密碼等等;在公司系統,做爲員工,可能還涉及薪資、教育背景等;在醫療系統,做爲患者,可能還涉及就診信息等等。因此,識別和梳理具體業務場景的敏感字段,是建立脫敏策略的第二步。性能
產品內置一系列常見的脫敏函數接口,能夠針對不一樣數據類型和數據特徵,指定參數,從而達到不同的脫敏效果。脫敏函數可採用以下三種內置接口,同時支持自定義脫敏函數。三種內置脫敏函數可以涵蓋大部分場景的脫敏效果,不推薦使用自定義脫敏函數。測試
不一樣脫敏列能夠採用不一樣的脫敏函數。好比,手機號一般顯示後四位尾號,前面用"*"替換;金額統一顯示爲固定值0,等等。肯定脫敏列須要綁定的脫敏函數,是建立脫敏策略的第三步。
以某公司員工表emp,表的屬主用戶alice以及用戶matu、july爲例,簡單介紹數據脫敏的使用過程。其中,表emp包含員工的姓名、手機號、郵箱、發薪卡號、薪資等隱私數據,用戶alice是人力資源經理,用戶matu和july是普通職員。
假設表、用戶及用戶對錶emp的查看權限均已就緒。
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 | smithWu@163.com | 99999.9990 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 0 | ####-####-####-3456 | 66allen_mm@qq.com | 9999.9990 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 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 | smithWu@163.com | 99999.9990 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 0 | ####-####-####-3456 | 66allen_mm@qq.com | 9999.9990 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 1992-11-06 00:00:00 (3 rows)
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 | smithWu@163.com | 10000.0000 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 3456345634563456 | 3456-3456-3456-3456 | 66allen_mm@qq.com | 9999.9900 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 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 | smithWu@163.com | 99999.9990 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 0 | ####-####-####-3456 | 66allen_mm@qq.com | 9999.9990 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 1992-11-06 00:00:00 (3 rows)
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)
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)
postgres=> DROP REDACTION POLICY mask_emp ON emp;
更多用法詳情,請參考GaussDB (DWS) 8.1.1產品文檔。
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對原始數據作部分脫敏,從而可以在脫敏結果上保留原始數據的外部可讀性。因而,如圖(c)所示,當脫敏列均關聯部分脫敏函數時,帶有數據脫敏的語句比原始語句劣化10%左右,理論上講,這種劣化是在可接受範圍的。上述測試僅針對簡單的查詢語句,當語句複雜到帶有彙集函數或複雜表達式運算時,可能這種性能劣化會更明顯。
GaussDB (DWS)產品數據脫敏功能,是數據庫產品內化和夯實數據安全能力的重要技術突破,主要涵蓋如下三個方面:
總而言之,此數據脫敏功能能夠充分知足客戶業務場景的數據脫敏訴求,支持常見隱私數據的脫敏效果,實現敏感數據的可靠保護。