您的位置:首頁技術文章
文章詳情頁

經驗之談:使用Oracle的TDE特性加密

瀏覽:27日期:2023-11-29 14:01:34

使用作為 Oracle高級安全選件(版本 10.2 及更高版本)的一部分引入的 Oracle數據庫透明數據加密 (TDE),可以有選擇地對保留在數據庫底層數據文件中的敏感數據庫數據以及所有下游文件組件(如聯機重做日志、歸檔重做日志和數據庫備份)進行加密。TDE 的基本目標是保護在這些原始操作系統文件中發現的敏感數據,防止不懷好意的人訪問磁盤或備份磁帶時對這些數據進行窺探,然后嘗試還原數據庫或掃描原始操作系統文件中的數據,如個人可識別信息或信用卡信息。

作為我的咨詢慣例的一部分,我已經實施 TDE 多次。但是,在其中一個最近的合約之前,我一直使用 TDE 對現有表中的新列或屬于全新表的列進行加密。在這兩種情況下使用 TDE 非常簡單,因為目標列為空,因此由于缺乏數據和現有應用程序相關性而不會涉及較大的風險。

我最近實施 TDE 的體驗有所不同。我幫助一家大型公司對一個已超過一百萬行的表中的現有列進行加密。還有一個依賴于列的關鍵任務應用程序,因此,您可以設想一下,在開始工作之前有很多重要的事情要考慮。在 Internet 上搜索可提供經驗的類似情形之后,我發現只有幾個優秀的資源可以幫助我。

本文概述了我在通過使用 TDE 對現有數據進行加密的過程中總結出的經驗教訓。如果您嘗試對現有列數據使用 TDE,我希望此處提供的信息可幫助您迅速有效地開展類似工作。

確定可能的限制

研究客戶的系統時,我做的第一件事情就是查找與目標列有關的將禁止我們對列加密的數據模型特征,或者查找可能對現有操作產生負面影響的有關列的事項。該研究包括查找列索引和完整性約束。

正如 Oracle 文檔明確聲明,當您想對具有索引的某個列進行加密時,需要了解很多限制條件。Oracle 不允許對具有位圖索引的列進行加密,這與我們的情況沒有密切關系。但是,目標列具有多個普通的(B 樹)索引。盡管 Oracle 允許對具有普通索引的列進行加密,但是 Oracle 禁止對索引列進行“salt 處理”加密。Salt 處理通過在加密之前向數據添加隨機字符串來提高重復數據的安全性,因此竊賊使用模式匹配識別技術更加難于破解加密的數據??偠灾?,經過這個最初的分析之后,我們會遇到一種情況,那就是我們可以對列進行加密,但不能進行 salt 處理。

對列索引進行分析后,我本可以到此為止,但是我想回答的下一個問題是“使用這些索引合適嗎?”我的思考過程是這樣:如果索引沒有用,那么我會將其刪除,從而減少維護索引條目所必需的系統開銷,尤其是考慮到加密的額外負擔。要判斷索引是否有用,我使用 Oracle 數據庫的索引監視特性。我發現,實際上索引正處于使用當中,因此我們必須對其繼續進行維護。

接下來,我查看了引用完整性約束條件中是否涉及目標列。由于每個表都具有其自己的加密密鑰,因此 Oracle 不允許您使用 TDE 對外鍵關系中涉及的列進行加密。在我們的情況下,引用完整性約束條件中未涉及目標列。

評估性能開銷

我的客戶詢問的第一組問題之一就是“TDE 對我的應用程序的一般性能影響如何?”Oracle 文檔中有一小部分論述了一般情況下 TDE 對相關應用程序性能的影響。但是我的客戶希望獲得一些具體的統計信息,以幫助他們了解 TDE 如何影響日常進行的有嚴格時間要求的數據加載過程。

為了滿足客戶需求,我計算了每天在有嚴格時間要求的過程中插入到目標表中的平均行數。然后,我在客戶端的相同沙箱環境中創建了一個類似的測試表和索引,測量在加密目標列前后插入相同數量的行所花費的時間。時間消耗上的差別讓我們更好地了解了在該過程中對列數據進行加密所造成的“性能損失”。列表 1 是我如何使用 SQL*Plus 執行該操作的示例。

SQL> CONNECT system

Enter password:

Connected.

SQL> -- Configure Oracle-Managed (Data) Files

SQL> ALTER SYSTEM

2 SET db_create_file_dest = '/data01/oracle/'

3 SCOPE = MEMORY;

System altered.

SQL> -- Create two new tablespaces for the demo,

SQL> -- one for data segments, one for index segments

SQL> CREATE TABLESPACE data_001

2 DATAFILE SIZE 1G;

Tablespace created.

SQL> CREATE TABLESPACE indx_001

2 DATAFILE SIZE 500M;

Tablespace created.

SQL> -- Create a user for the demo

SQL> CREATE USER app_001 IDENTIFIED BY app

2 DEFAULT TABLESPACE data_001

3 TEMPORARY TABLESPACE temp

4 QUOTA UNLIMITED ON data_001

5 QUOTA UNLIMITED ON indx_001;

User created.

SQL> GRANT CREATE SESSION, CREATE TABLE TO app_001;

Grant succeeded.

SQL> -- Work as the demo user

SQL> CONNECT app_001/app;

Connected.

SQL> -- Create the demo table in the default tablespace

SQL> CREATE TABLE app_001.transactions (

2 trans_id INTEGER

3 CONSTRAINT transactions_pk PRIMARY KEY

4 USING INDEX TABLESPACE indx_001,

5 credit_card INTEGER NOT NULL

6 );

Table created.

SQL> -- Create an index in the INDX_001 tablespace

SQL> CREATE INDEX app_001.transactions_ndx1

2 ON app_001.transactions(credit_card)

3 TABLESPACE indx_001;

Index created.

SQL> -- Time how long it takes to load data in the clear

SQL> SET TIMING ON;

SQL> BEGIN

2 -- AMEX

3 FOR i IN 1 .. 100000 LOOP

4 INSERT INTO app_001.transactions(trans_id, credit_card)

5 VALUES (

6 i,

7 '34' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

8 );

9 END LOOP;

10 COMMIT;

11 -- VISA

12 FOR i IN 100001 .. 400000 LOOP

13 INSERT INTO app_001.transactions(trans_id, credit_card)

14 VALUES (

15 i,

16 '4' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>999999999999999))

17 );

18 END LOOP;

19 COMMIT;

20 -- MASTERCARD

21 FOR i IN 400001 .. 500000 LOOP

22 INSERT INTO app_001.transactions(trans_id, credit_card)

23 VALUES (

24 i,

25 '54' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

26 );

27 END LOOP;

28 COMMIT;

29 END;

30 /

PL/SQL procedure successfully completed.

Elapsed: 00:00:56.14

SQL> SET TIMING OFF;

SQL> -- Remove existing synthetic data

SQL> TRUNCATE TABLE app_001.transactions;

Table truncated.

SQL> -- Enable encryption of the credit card column

SQL> ALTER TABLE app_001.transactions

2 MODIFY (credit_card ENCRYPT NO SALT);

Table altered.

SQL> -- Time how long it takes to load encrypted data

SQL> SET TIMING ON;

SQL> BEGIN

2 -- AMEX

3 FOR i IN 1 .. 100000 LOOP

4 INSERT INTO app_001.transactions(trans_id, credit_card)

5 VALUES (

6 i,

7 '34' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

8 );

9 END LOOP;

10 COMMIT;

11 -- VISA

12 FOR i IN 100001 .. 400000 LOOP

13 INSERT INTO app_001.transactions(trans_id, credit_card)

14 VALUES (

15 i,

16 '4' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>999999999999999))

17 );

18 END LOOP;

19 COMMIT;

20 -- MASTERCARD

21 FOR i IN 400001 .. 500000 LOOP

22 INSERT INTO app_001.transactions(trans_id, credit_card)

23 VALUES (

24 i,

25 '54' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

26 );

27 END LOOP;

28 COMMIT;

29 END;

30 /

PL/SQL procedure successfully completed.

Elapsed: 00:01:16.31

SQL> SET TIMING OFF;

列表 1使用與您的生產環境相同的沙箱環境,簡單比較啟用列加密前后加載代表性數據集所花費的時間,以使您更好地了解列加密對生產系統性能的影響。

和所有的性能測試一樣,我懷疑對列進行加密所造成的性能損失會因系統而異,具體取決于普通變量(CPU、平均負載等)。在列表 1 中,您注意到計算的性能損失為 36% (((56.14-76.31)/56.14)*100),但是,使用我們在客戶系統中收集的實驗證據,預計數據加載過程所耗費的時間應該大約增加 11%,這與在生產中使用 TDE 獲得結果完全一樣。

在本例中,我側重于對具有索引的數據加載過程估計數據加密的性能損失。如果您的系統具有不同類型的關鍵過程,如要求苛刻的報表生成周期,那么我建議您使用沙箱環境來比較數據加密前后該過程所花費的時間。本文后面的“確定潛在查詢計劃更改”部分將討論查詢和數據加密的特別注意事項。

處理停機和維護時間

我的客戶比較關心的另一個問題是,在加密約一百萬行的表中的現有列數據時,需要對哪些生產應用程序(如果有)進行必要的停用。我最初想法是,理論上不需要停止任何應用程序 — 畢竟,Oracle 文檔明確表示了對現有列的數據進行加密本質上就是對整個表進行多行更新。如果沒有更多地考慮這件事,我不會明白為什么新行無法并發插入到表中以及為什么現有行更新無法繼續。當我咕噥著熟悉的 Oracle 口號“讀取方不會阻止寫入方,寫入方也不會阻止讀取方”時,我的確沒有想到列加密會影響查詢。

但是,在長時間從事 DBA 工作后,我才總結出,若要對生產系統進行最終的實際更改,需要對理論進行測試,以避免出現意外問題,這一點非常重要。您瞧,當我在加密列期間,針對沙箱數據庫對應用程序本身進行了測試,從而發現了很多問題。最重要的是,我發現進行中的加密延長了某些查詢的響應時間,以至于應用程序會遇到響應超時。這些超時又會造成連接斷開,然后導致后續的事務失敗,進而會更加麻煩 — 我將為您提供詳細信息。

必須一提的是,測試之后,我了解到停止應用程序運行絕對不是沒有理由的。但下一個問題是,生產應用程序需要脫機多久?在計劃每個周末進行的正常兩小時的維護時間之內能夠對列進行加密嗎?或者,需要更長的停機時間?為了弄清這個問題,我只需測量在沙箱環境中對列進行加密所花費的時間,因為沙箱環境與生產環境具有相同的服務器硬件和數據集。我發現,列加密要花費一個小時多一點的時間才能完成。坦白地說,由于我使用類似數據在筆記本電腦上模擬測試加密運行才花費了不到 5 分鐘的時間,因此對于它花費這么長時間,我感到非常震驚。但是當我們在生產數據庫系統中對列進行加密時,最要緊的是要使用陳舊服務器硬件所發生的情況。

了解到在正常維護時間內執行其他任務需要更多時間,我決定必須找到減少加密列花費時間的方法。我的第一個直覺就是刪除包含目標列的兩個索引。這樣,Oracle 只需加密表本身中的列數據,之后我可以有效地重建索引,而沒有日志記錄開銷。經過一些新的測試之后,我將加密列以及相關索引所需的時間從 70 分鐘(在加密期間存在索引)減少到僅 20 分鐘(加密列后重建索引)。列表 2 是我用來得出結論的測試示例(從我們在列表 1 中停止的位置繼續)。此外,請注意,列表中的時間來自用來編寫本文的測試系統,而不是來自我的客戶端使用的實際系統。

SQL> -- Remove existing synthetic data

SQL> TRUNCATE TABLE app_001.transactions;

Table truncated.

SQL> -- Disable encryption of the credit card column

SQL> ALTER TABLE app_001.transactions

2 MODIFY (credit_card DECRYPT);

Table altered.

SQL> -- Load new synthetic data

SQL> BEGIN

2 -- AMEX

3 FOR i IN 1 .. 100000 LOOP

4 INSERT INTO app_001.transactions(trans_id, credit_card)

5 VALUES (

6 i,

7 '34' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

8 );

9 END LOOP;

10 COMMIT;

11 -- VISA

12 FOR i IN 100001 .. 400000 LOOP

13 INSERT INTO app_001.transactions(trans_id, credit_card)

14 VALUES (

15 i,

16 '4' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>999999999999999))

17 );

18 END LOOP;

19 COMMIT;

20 -- MASTERCARD

21 FOR i IN 400001 .. 500000 LOOP

22 INSERT INTO app_001.transactions(trans_id, credit_card)

23 VALUES (

24 i,

25 '54' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

26 );

27 END LOOP;

28 COMMIT;

29 END;

30 /

PL/SQL procedure successfully completed.

SQL> -- Time how long it takes to encrypt credit card data

SQL> -- with corresponding indexes in place

SQL> SET TIMING ON;

SQL> ALTER TABLE app_001.transactions

2 MODIFY (credit_card ENCRYPT NO SALT);

Table altered.

Elapsed: 00:02:27.18

SQL> SET TIMING OFF;

SQL> -- Remove existing synthetic data

SQL> TRUNCATE TABLE app_001.transactions;

Table truncated.

SQL> -- Drop all indexes that correspond to the credit card column

SQL> DROP INDEX app_001.transactions_ndx1;

Index dropped.

SQL> -- Disable encryption of the credit card column

SQL> ALTER TABLE app_001.transactions

2 MODIFY (credit_card DECRYPT);

Table altered.

SQL> -- Load new synthetic data

SQL> BEGIN

2 -- AMEX

3 FOR i IN 1 .. 100000 LOOP

4 INSERT INTO app_001.transactions(trans_id, credit_card)

5 VALUES (

6 i,

7 '34' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

8 );

9 END LOOP;

10 COMMIT;

11 -- VISA

12 FOR i IN 100001 .. 400000 LOOP

13 INSERT INTO app_001.transactions(trans_id, credit_card)

14 VALUES (

15 i,

16 '4' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>999999999999999))

17 );

18 END LOOP;

19 COMMIT;

20 -- MASTERCARD

21 FOR i IN 400001 .. 500000 LOOP

22 INSERT INTO app_001.transactions(trans_id, credit_card)

23 VALUES (

24 i,

25 '54' || TRUNC(DBMS_RANDOM.VALUE(low=>0, high=>99999999999999))

26 );

27 END LOOP;

28 COMMIT;

29 END;

30 /

PL/SQL procedure successfully completed.

SQL> -- Time how long it takes to:

SQL> -- 1. Encrypt credit card data without corresponding indexes in place

SQL> -- 2. Recreate corresponding indexes

SQL> SET TIMING ON;

SQL> ALTER TABLE app_001.transactions

2 MODIFY (credit_card ENCRYPT NO SALT);

Table altered.

Elapsed: 00:01:15.48

SQL> CREATE INDEX app_001.transactions_ndx1

2 ON app_001.transactions(credit_card)

3 TABLESPACE indx_001

4 PARALLEL 2

5 NOLOGGING;

Index created.

Elapsed: 00:00:02.98

SQL> SET TIMING OFF;

列表 2要快速執行對現有數據進行加密的過程,只需在對其進行加密之前刪除列的底層索引,然后再重建索引。

注:本文的模擬環境中使用了 CREATE INDEX 語句。在實際的設置中,可考慮使用 Oracle 數據庫的 DBMS_METADATA 實用程序包來生成 CREATE INDEX 語句,您可以使用這些語句在完成數據加密之后重新創建索引。

總之,在列加密之后重建索引的新策略可留出更多時間來處理整個過程中最具挑戰性的問題,這將在下一部分中進行說明。

刪除未加密數據的虛副本

Oracle 和底層主機操作系統使用優化的算法來更新數據塊中的數據,目的是最大程度地減少降低性能的磁盤 I/O。在對現有列數據進行加密的特定情況下,通常發生的一件事是 Oracle 將加密的列數據寫入到新數據塊并且只是將之前未加密的值占用的空間標記為未使用。換句話說,Oracle 不會嘗試清除較舊的未加密數據。只要所討論的系統持續遇到大量更新活動,您就有理由確信當重用塊空間時 Oracle 將最終覆蓋較舊的未加密數據。但是考慮到我的客戶正在準備進行合規性審計,我必須確保在加密過程之后立即擦除未加密的敏感數據。

進行了很多研究之后,我在 Oracle 技術網上發現了一個 FAQ 以及一個網志,該網志確認這一特定問題并提供了有關解決該問題的一些基本想法。一般的想法是將包含之前未加密數據的所有段移動到新的表空間(以及數據文件),然后使用一個操作系統實用程序刪除舊數據文件。但該做法聽起來容易,做起來難。事實是,在安全刪除舊表空間及其數據文件之前,您很有可能需要移動大量段以及包含敏感數據的段。

為了使這一可能費時費力且易于出錯的過程自動進行,我將一些腳本放在一起,以幫助我構建完成這一切所需的 DDL 語句。這里,我要向 Tom Kyte 表示謝意,因為此處的一些工作是修改我在 Asktom 站點找到的內容查詢。列表 3 顯示了我使用的整個過程的一個示例。

Enter password:

Connected.

SQL> -- Create new tablespaces for data and index segments

SQL> CREATE TABLESPACE data_002 DATAFILE SIZE 1G;

Tablespace created.

SQL> CREATE TABLESPACE indx_002 DATAFILE SIZE 500M;

Tablespace created.

SQL> -- Generate a script to move existing segments to new tablespaces

SQL> COL ORDER_COL1 NOPRINT;

SQL> COL ORDER_COL2 NOPRINT;

SQL> SET HEADING OFF;

SQL> SET VERIFY OFF;

SQL> SET ECHO OFF;

SQL> SELECT DECODE( segment_type, 'TABLE' , segment_name, table_name ) order_col1,

2 DECODE( segment_type, 'TABLE', 1, 2 ) order_col2,

3 'ALTER ' || segment_type || ' ' || LOWER(owner) || '.' || LOWER(segment_name) ||

4 DECODE( segment_type, 'TABLE', ' MOVE ', ' REBUILD ' ) ||

5 'TABLESPACE ' || LOWER(DECODE( segment_type, 'TABLE' , '&&NEW_DATA_TBS' , '&&NEW_INDX_TBS' )) || ';'

6 FROM dba_segments,

7 (SELECT table_name, index_name FROM dba_indexes WHERE tablespace_name = UPPER('&&OLD_INDX_TBS'))

8 WHERE segment_type in ( 'TABLE', 'INDEX' )

9 AND segment_name = index_name (+)

10 AND tablespace_name IN (UPPER('&&OLD_DATA_TBS'), UPPER('&&OLD_INDX_TBS'))

11 AND owner = UPPER('&&OWNER')

12 ORDER BY 1, 2;

Enter value for new_data_tbs: data_002

Enter value for new_indx_tbs: indx_002

Enter value for old_indx_tbs: indx_001

Enter value for old_data_tbs: data_001

Enter value for owner: app_001

ALTER TABLE app_001.transactions MOVE TABLESPACE data_002;

ALTER INDEX app_001.transactions_pk REBUILD TABLESPACE indx_002;

ALTER INDEX app_001.transactions_ndx1 REBUILD TABLESPACE indx_002;

SQL> SET HEADING ON;

SQL> SET VERIFY ON;

SQL> SET ECHO ON;

SQL> -- execute script output

SQL> ALTER TABLE app_001.transactions MOVE TABLESPACE data_002;

Table altered.

SQL> ALTER INDEX app_001.transactions_pk REBUILD TABLESPACE indx_002;

Index altered.

SQL> ALTER INDEX app_001.transactions_ndx1 REBUILD TABLESPACE indx_002;

Index altered.

SQL> -- Check for any unusable indexes

SQL> SELECT owner, index_name, tablespace_name

2 FROM dba_indexes

3 WHERE STATUS = 'UNUSABLE';

no rows selected

SQL> -- Gather new schema stats

SQL> EXEC dbms_stats.gather_schema_stats('app_001');

PL/SQL procedure successfully completed.

SQL> -- Check for remaining segments in old tablespaces

SQL> SELECT distinct owner

2 FROM dba_segments

3 WHERE tablespace_name IN (UPPER('&&OLD_DATA_TBS'), UPPER('&&OLD_INDX_TBS'));

old 3: WHERE tablespace_name IN (UPPER('&&OLD_DATA_TBS'), UPPER('&&OLD_INDX_TBS'))

new 3: WHERE tablespace_name IN (UPPER('data_001'), UPPER('indx_001'))

no rows selected

SQL> -- Check for users assigned to old tablespaces

SQL> SELECT username, default_tablespace FROM dba_users

2 WHERE default_tablespace IN (UPPER('&&OLD_DATA_TBS'), UPPER('&&OLD_INDX_TBS'));

old 2: WHERE default_tablespace IN (UPPER('&&OLD_DATA_TBS'), UPPER('&&OLD_INDX_TBS'))

new 2: WHERE default_tablespace IN (UPPER('data_001'), UPPER('indx_001'))

USERNAME DEFAULT_TABLESPACE

------------------------------ ------------------------------

APP_001 DATA_001

SQL> -- Assign new default tablespaces for users, as necessary

SQL> ALTER USER app_001

2 DEFAULT TABLESPACE data_002;

User altered.

SQL> -- List the data file names of old tablespaces

SQL> COL tablespace_name FOR A15;

SQL> COL file_name FOR A70;

SQL> SET LINES 100;

SQL> SELECT tablespace_name, file_name

2 FROM dba_data_files

3 WHERE tablespace_name IN (UPPER('&&OLD_DATA_TBS'), UPPER('&&OLD_INDX_TBS'));

old 3: WHERE tablespace_name IN (UPPER('&&OLD_DATA_TBS'), UPPER('&&OLD_INDX_TBS'))

new 3: WHERE tablespace_name IN (UPPER('data_001'), UPPER('indx_001'))

TABLESPACE_NAME FILE_NAME

--------------- ----------------------------------------------------------------------

DATA_001 /data01/oracle/db001/datafile/o1_mf_data_001_4m081w7m_.dbf

INDX_001 /data01/oracle/db001/datafile/o1_mf_indx_001_4m082l4q_.dbf

SQL> -- Drop old tablespaces, but keep data files in place

SQL> DROP TABLESPACE data_001

2 INCLUDING CONTENTS KEEP DATAFILES;

Tablespace dropped.

SQL> DROP TABLESPACE indx_001

2 INCLUDING CONTENTS KEEP DATAFILES;

Tablespace dropped.

SQL> -- Shred/remove old data files

SQL> HOST shred -n 200 -z -u /data01/oracle/db001/datafile/o1_mf_data_001_4m081w7m_.dbf

SQL> HOST shred -n 200 -z -u /data01/oracle/db001/datafile/o1_mf_indx_001_4m082l4q_.dbf

列表 3刪除剩余敏感數據的未加密虛副本需要一系列步驟才能解決很多相關性。

列表 3 中的最后一步針對特定的操作系統。在本例中,我演示了 Linux/Unix shred 實用程序的用法。其他您可能想探究的實用程序是 Linux/Unix wipe、scrub 和 srm 程序。

準備可能的重新生成密鑰操作

我的客戶還想做些準備以應對將來可能需要重新生成密鑰的情況。為現有數據重新生成密鑰意味著您要使用新密鑰對現有數據重新加密。當您懷疑某人已經獲得對現有表密鑰的訪問權限并且可能會在您的控制之外破解敏感數據時,可能需要執行該操作。

完成重新生成密鑰操作的步驟與最初加密現有數據的步驟類似:刪除目標列上的索引,重新生成該列數據的密鑰,然后重建刪除的索引?;蛘?,如果您擔心與使用以前的密鑰加密的數據相對應的虛副本,可以重復將段移動到新表空間、刪除舊表空間、然后清除舊表空間的數據文件的過程。

注:以我的經驗,PCI 審計員會對重新生成萬能加密密鑰非常滿意,因為該過程不需要訪問任何數據,并且 PCI 標準不包含對 2 層密鑰體系結構(如 Oracle 的體系結構)的建議。從符合 PCI 的角度看,重新生成萬能加密密鑰應該足夠了,而且 PCI 審計員無法強制具有幾十億行的公司將其業務關閉幾天,僅僅只是為了重新生成密鑰。

確定可能的查詢計劃更改

Oracle 文檔、若干文章以及我閱讀過的一些論壇帖子幾乎都提到了有關加密列的數據后現有查詢執行計劃可能發生更改的概要信息以及某些具體信息。一般來說,相對于沒有索引的列,在加密具有索引的列時,您必須注意執行 SQL 語句所發生的情況。當 Oracle 加密具有索引的列時,Oracle 還將加密相應的索引值。如果您花點時間考慮這個問題,就會清楚地發現以具有索引的數據為目標的相等謂詞應該繼續利用索引,但由于該索引值存儲在索引中的方式,加密值的隨機性質使得加密索引的范圍掃描成本過高。列表 4 演示了這些詳細描述的基本情況。

SQL> CONNECT app_001

Enter password:

Connected.

SQL> -- Create a plan table

SQL> @?/rdbms/admin/utlxplan.sql;

Table created.

SQL> -- Disable encryption of the credit card column

SQL> ALTER TABLE app_001.transactions

2 MODIFY (credit_card DECRYPT);

Table altered.

SQL> -- Ensure schema stats are current

SQL> EXEC dbms_stats.gather_schema_stats('app_001');

PL/SQL procedure successfully completed.

SQL> -- Display some representative data

SQL> COL credit_card FOR 9999999999999999;

SQL> SELECT * FROM app_001.transactions

2 WHERE rownum <5;

TRANS_ID CREDIT_CARD

---------- -----------------

389 3469681098409570

390 3441050723354352

391 3485598407754404

392 3485458104610650

SQL> -- Enable tracing and explain plan output

SQL> SET AUTOTRACE ON EXPLAIN;

SQL> -- Demonstrate an equality predicate targeting the

SQL> -- encrypted column

SQL> SELECT * FROM app_001.transactions

2 WHERE credit_card = 3485458104610650;

TRANS_ID CREDIT_CARD

---------- -----------------

392 3485458104610650

Execution Plan

----------------------------------------------------------

Plan hash value: 32329967

-------------------------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-------------------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 1 | 14 | 5 (0)| 00:00:01 |

| 1 | TABLE ACCESS BY INDEX ROWID| TRANSACTIONS | 1 | 14 | 5 (0)| 00:00:01 |

|* 2 | INDEX RANGE SCAN | TRANSACTIONS_NDX1 | 1 | | 3 (0)| 00:00:01 |

-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access('CREDIT_CARD'=3485458104610650)

SQL> -- Demonstrate a range predicate targeting the

SQL> -- encrypted column

SQL> SELECT * FROM app_001.transactions

2 WHERE credit_card BETWEEN 3499990000000000 AND 3499999999999999;

TRANS_ID CREDIT_CARD

---------- -----------------

4629 3499990987277941

18597 3499993250694089

13364 3499996558049599

79326 3499996616476145

60420 3499997873591732

24392 3499998608513414

97433 3499999831086288

72183 3499999977925392

8 rows selected.

Execution Plan

----------------------------------------------------------

Plan hash value: 32329967

-------------------------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-------------------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 3 | 42 | 6 (0)| 00:00:01 |

| 1 | TABLE ACCESS BY INDEX ROWID| TRANSACTIONS | 3 | 42 | 6 (0)| 00:00:01 |

|* 2 | INDEX RANGE SCAN | TRANSACTIONS_NDX1 | 3 | | 3 (0)| 00:00:01 |

-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access('CREDIT_CARD'>=3499990000000000 AND 'CREDIT_CARD'<=3499999999999999)

SQL> -- Disable tracing and explain plan output

SQL> SET AUTOTRACE OFF;

SQL> -- Encrypt the column (and indexes)

SQL> ALTER TABLE app_001.transactions

2 MODIFY (credit_card ENCRYPT NO SALT);

Table altered.

SQL> -- Ensure schema stats are current

SQL> EXEC dbms_stats.gather_schema_stats('app_001');

PL/SQL procedure successfully completed.

SQL> -- Enable tracing and explain plan output

SQL> SET AUTOTRACE ON EXPLAIN;

SQL> -- Rerun previous queries, compare execution plans

SQL> SELECT * FROM app_001.transactions

2 WHERE credit_card = 3485458104610650;

TRANS_ID CREDIT_CARD

---------- -----------------

392 3485458104610650

Execution Plan

----------------------------------------------------------

Plan hash value: 32329967

-------------------------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-------------------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 1 | 14 | 5 (0)| 00:00:01 |

| 1 | TABLE ACCESS BY INDEX ROWID| TRANSACTIONS | 1 | 14 | 5 (0)| 00:00:01 |

|* 2 | INDEX RANGE SCAN | TRANSACTIONS_NDX1 | 1 | | 3 (0)| 00:00:01 |

-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access('CREDIT_CARD'=3485458104610650)

SQL> SELECT * FROM app_001.transactions

2 WHERE credit_card BETWEEN 3499990000000000 AND 3499999999999999;

TRANS_ID CREDIT_CARD

---------- -----------------

60420 3499997873591732

4629 3499990987277941

18597 3499993250694089

13364 3499996558049599

24392 3499998608513414

79326 3499996616476145

72183 3499999977925392

97433 3499999831086288

8 rows selected.

Execution Plan

----------------------------------------------------------

Plan hash value: 1321366336

----------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

----------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 1250 | 17500 | 914 (2)| 00:00:11 |

|* 1 | TABLE ACCESS FULL| TRANSACTIONS | 1250 | 17500 | 914 (2)| 00:00:11 |

----------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter(INTERNAL_FUNCTION('CREDIT_CARD')>=3499990000000000 AND

INTERNAL_FUNCTION('CREDIT_CARD')<=3499999999999999)

SQL> -- Disable tracing and explain plan output

SQL> SET AUTOTRACE OFF;

列表 4 仔細識別引用加密列數據的 SQL 語句,然后比較加密前后這些語句的執行計劃,以查找是否存在任何更改。

我還想知道加密的開銷是否會改變計劃成本以及優化程序的選擇,即使在文檔建議不要這樣做的情況下。為了確保我確切知道進行生產時關鍵任務應用程序所發生的情況,我在沙箱環境中進行了一些額外的工作。首先,我從各種自動負載信息庫 (AWR) 快照中收集了一個使用頻繁的 SQL 語句(CPU、Gets 和 I/O)列表。然后,我比較了加密列前后每個 SQL 語句的查詢執行計劃。我的研究轉向對多個基于相等的條件使用一個謂詞的復雜查詢,其中一個條件是以將要進行加密的列為目標。讓我吃驚的是,在對列進行加密之后,該查詢的執行計劃發生了改變。遺憾的是,我無法在我的測試實驗室中為本文復制這些結果,我仍然無法完全確定查詢計劃發生改變的原因。但我之所以在此處提到這種情況是想指出,在對生產系統進行更改之前,最好在測試環境中研究生產應用程序密鑰查詢的執行計劃。如果我假設沒有任何使用頻繁的查詢會發生改變,那么我們將對生產系統進行更改并且不得不勉強拼湊一個解決方案。

此處的教訓是,在進行更改之前,您應該始終對這些事項進行測試,無論您在文檔和其他來源中閱讀了什么內容都是如此。

結論

使用 Oracle 的 TDE 特性加密新表與沒有任何數據的表中的列或者現有表中的新列非常簡單,原因是不存在任何需要擔心的相關性。相反,加密現有列數據需要仔細研究并在您的沙箱環境中進行測試,然后才能在實際生產系統中實施您的計劃,因為加密可能會影響很多相關性。

Steve Bobrowski自 Oracle 數據庫版本 5 開始一直使用該軟件,曾就職于 Oracle;他還是 The Database Domain (dbdomain.com) 的創始人以及五本 Oracle 出版社出版的書籍的作者(包括《Oracle 數據庫 10g 快捷版上機操作》系列)。最近,Steve 擔任著幾個大型公司(如 Computer Sciences Corporation、BEA Systems 和 Salesforce.com)的 SaaS 顧問和 SaaS 首席技術官。

標簽: Oracle 數據庫
相關文章:
国产综合久久一区二区三区