經(jīng)驗(yàn)之談:使用Oracle的TDE特性加密
使用作為 Oracle 高級(jí)安全選件(版本 10.2 及更高版本)的一部分引入的 Oracle 數(shù)據(jù)庫(kù)透明數(shù)據(jù)加密 (TDE),可以有選擇地對(duì)保留在數(shù)據(jù)庫(kù)底層數(shù)據(jù)文件中的敏感數(shù)據(jù)庫(kù)數(shù)據(jù)以及所有下游文件組件(如聯(lián)機(jī)重做日志、歸檔重做日志和數(shù)據(jù)庫(kù)備份)進(jìn)行加密。TDE 的基本目標(biāo)是保護(hù)在這些原始操作系統(tǒng)文件中發(fā)現(xiàn)的敏感數(shù)據(jù),防止不懷好意的人訪問磁盤或備份磁帶時(shí)對(duì)這些數(shù)據(jù)進(jìn)行窺探,然后嘗試還原數(shù)據(jù)庫(kù)或掃描原始操作系統(tǒng)文件中的數(shù)據(jù),如個(gè)人可識(shí)別信息或信用卡信息。
作為我的咨詢慣例的一部分,我已經(jīng)實(shí)施 TDE 多次。但是,在其中一個(gè)最近的合約之前,我一直使用 TDE 對(duì)現(xiàn)有表中的新列或?qū)儆谌卤淼牧羞M(jìn)行加密。在這兩種情況下使用 TDE 非常簡(jiǎn)單,因?yàn)槟繕?biāo)列為空,因此由于缺乏數(shù)據(jù)和現(xiàn)有應(yīng)用程序相關(guān)性而不會(huì)涉及較大的風(fēng)險(xiǎn)。
我最近實(shí)施 TDE 的體驗(yàn)有所不同。我?guī)椭患掖笮凸緦?duì)一個(gè)已超過一百萬行的表中的現(xiàn)有列進(jìn)行加密。還有一個(gè)依賴于列的關(guān)鍵任務(wù)應(yīng)用程序,因此,您可以設(shè)想一下,在開始工作之前有很多重要的事情要考慮。在 Internet 上搜索可提供經(jīng)驗(yàn)的類似情形之后,我發(fā)現(xiàn)只有幾個(gè)優(yōu)秀的資源可以幫助我。
本文概述了我在通過使用 TDE 對(duì)現(xiàn)有數(shù)據(jù)進(jìn)行加密的過程中總結(jié)出的經(jīng)驗(yàn)教訓(xùn)。如果您嘗試對(duì)現(xiàn)有列數(shù)據(jù)使用 TDE,我希望此處提供的信息可幫助您迅速有效地開展類似工作。
確定可能的限制
研究客戶的系統(tǒng)時(shí),我做的第一件事情就是查找與目標(biāo)列有關(guān)的將禁止我們對(duì)列加密的數(shù)據(jù)模型特征,或者查找可能對(duì)現(xiàn)有操作產(chǎn)生負(fù)面影響的有關(guān)列的事項(xiàng)。該研究包括查找列索引和完整性約束。
正如 Oracle 文檔明確聲明,當(dāng)您想對(duì)具有索引的某個(gè)列進(jìn)行加密時(shí),需要了解很多限制條件。Oracle 不允許對(duì)具有位圖索引的列進(jìn)行加密,這與我們的情況沒有密切關(guān)系。但是,目標(biāo)列具有多個(gè)普通的(B 樹)索引。盡管 Oracle 允許對(duì)具有普通索引的列進(jìn)行加密,但是 Oracle 禁止對(duì)索引列進(jìn)行“salt 處理”加密。Salt 處理通過在加密之前向數(shù)據(jù)添加隨機(jī)字符串來提高重復(fù)數(shù)據(jù)的安全性,因此竊賊使用模式匹配識(shí)別技術(shù)更加難于破解加密的數(shù)據(jù)??偠灾?,經(jīng)過這個(gè)最初的分析之后,我們會(huì)遇到一種情況,那就是我們可以對(duì)列進(jìn)行加密,但不能進(jìn)行 salt 處理。
對(duì)列索引進(jìn)行分析后,我本可以到此為止,但是我想回答的下一個(gè)問題是“使用這些索引合適嗎?”我的思考過程是這樣:如果索引沒有用,那么我會(huì)將其刪除,從而減少維護(hù)索引條目所必需的系統(tǒng)開銷,尤其是考慮到加密的額外負(fù)擔(dān)。要判斷索引是否有用,我使用 Oracle 數(shù)據(jù)庫(kù)的索引監(jiān)視特性。我發(fā)現(xiàn),實(shí)際上索引正處于使用當(dāng)中,因此我們必須對(duì)其繼續(xù)進(jìn)行維護(hù)。
接下來,我查看了引用完整性約束條件中是否涉及目標(biāo)列。由于每個(gè)表都具有其自己的加密密鑰,因此 Oracle 不允許您使用 TDE 對(duì)外鍵關(guān)系中涉及的列進(jìn)行加密。在我們的情況下,引用完整性約束條件中未涉及目標(biāo)列。
評(píng)估性能開銷
我的客戶詢問的第一組問題之一就是“TDE 對(duì)我的應(yīng)用程序的一般性能影響如何?”O(jiān)racle 文檔中有一小部分論述了一般情況下 TDE 對(duì)相關(guān)應(yīng)用程序性能的影響。但是我的客戶希望獲得一些具體的統(tǒng)計(jì)信息,以幫助他們了解 TDE 如何影響日常進(jìn)行的有嚴(yán)格時(shí)間要求的數(shù)據(jù)加載過程。
為了滿足客戶需求,我計(jì)算了每天在有嚴(yán)格時(shí)間要求的過程中插入到目標(biāo)表中的平均行數(shù)。然后,我在客戶端的相同沙箱環(huán)境中創(chuàng)建了一個(gè)類似的測(cè)試表和索引,測(cè)量在加密目標(biāo)列前后插入相同數(shù)量的行所花費(fèi)的時(shí)間。時(shí)間消耗上的差別讓我們更好地了解了在該過程中對(duì)列數(shù)據(jù)進(jìn)行加密所造成的“性能損失”。列表 1 是我如何使用 SQL*Plus 執(zhí)行該操作的示例。
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 使用與您的生產(chǎn)環(huán)境相同的沙箱環(huán)境,簡(jiǎn)單比較啟用列加密前后加載代表性數(shù)據(jù)集所花費(fèi)的時(shí)間,以使您更好地了解列加密對(duì)生產(chǎn)系統(tǒng)性能的影響。
和所有的性能測(cè)試一樣,我懷疑對(duì)列進(jìn)行加密所造成的性能損失會(huì)因系統(tǒng)而異,具體取決于普通變量(CPU、平均負(fù)載等)。在列表 1 中,您注意到計(jì)算的性能損失為 36% (((56.14-76.31)/56.14)*100),但是,使用我們?cè)诳蛻粝到y(tǒng)中收集的實(shí)驗(yàn)證據(jù),預(yù)計(jì)數(shù)據(jù)加載過程所耗費(fèi)的時(shí)間應(yīng)該大約增加 11%,這與在生產(chǎn)中使用 TDE 獲得結(jié)果完全一樣。
在本例中,我側(cè)重于對(duì)具有索引的數(shù)據(jù)加載過程估計(jì)數(shù)據(jù)加密的性能損失。如果您的系統(tǒng)具有不同類型的關(guān)鍵過程,如要求苛刻的報(bào)表生成周期,那么我建議您使用沙箱環(huán)境來比較數(shù)據(jù)加密前后該過程所花費(fèi)的時(shí)間。本文后面的“確定潛在查詢計(jì)劃更改”部分將討論查詢和數(shù)據(jù)加密的特別注意事項(xiàng)。
處理停機(jī)和維護(hù)時(shí)間
我的客戶比較關(guān)心的另一個(gè)問題是,在加密約一百萬行的表中的現(xiàn)有列數(shù)據(jù)時(shí),需要對(duì)哪些生產(chǎn)應(yīng)用程序(如果有)進(jìn)行必要的停用。我最初想法是,理論上不需要停止任何應(yīng)用程序 — 畢竟,Oracle 文檔明確表示了對(duì)現(xiàn)有列的數(shù)據(jù)進(jìn)行加密本質(zhì)上就是對(duì)整個(gè)表進(jìn)行多行更新。如果沒有更多地考慮這件事,我不會(huì)明白為什么新行無法并發(fā)插入到表中以及為什么現(xiàn)有行更新無法繼續(xù)。當(dāng)我咕噥著熟悉的 Oracle 口號(hào)“讀取方不會(huì)阻止寫入方,寫入方也不會(huì)阻止讀取方”時(shí),我的確沒有想到列加密會(huì)影響查詢。
但是,在長(zhǎng)時(shí)間從事 DBA 工作后,我才總結(jié)出,若要對(duì)生產(chǎn)系統(tǒng)進(jìn)行最終的實(shí)際更改,需要對(duì)理論進(jìn)行測(cè)試,以避免出現(xiàn)意外問題,這一點(diǎn)非常重要。您瞧,當(dāng)我在加密列期間,針對(duì)沙箱數(shù)據(jù)庫(kù)對(duì)應(yīng)用程序本身進(jìn)行了測(cè)試,從而發(fā)現(xiàn)了很多問題。最重要的是,我發(fā)現(xiàn)進(jìn)行中的加密延長(zhǎng)了某些查詢的響應(yīng)時(shí)間,以至于應(yīng)用程序會(huì)遇到響應(yīng)超時(shí)。這些超時(shí)又會(huì)造成連接斷開,然后導(dǎo)致后續(xù)的事務(wù)失敗,進(jìn)而會(huì)更加麻煩 — 我將為您提供詳細(xì)信息。
必須一提的是,測(cè)試之后,我了解到停止應(yīng)用程序運(yùn)行絕對(duì)不是沒有理由的。但下一個(gè)問題是,生產(chǎn)應(yīng)用程序需要脫機(jī)多久?在計(jì)劃每個(gè)周末進(jìn)行的正常兩小時(shí)的維護(hù)時(shí)間之內(nèi)能夠?qū)α羞M(jìn)行加密嗎?或者,需要更長(zhǎng)的停機(jī)時(shí)間?為了弄清這個(gè)問題,我只需測(cè)量在沙箱環(huán)境中對(duì)列進(jìn)行加密所花費(fèi)的時(shí)間,因?yàn)樯诚洵h(huán)境與生產(chǎn)環(huán)境具有相同的服務(wù)器硬件和數(shù)據(jù)集。我發(fā)現(xiàn),列加密要花費(fèi)一個(gè)小時(shí)多一點(diǎn)的時(shí)間才能完成。坦白地說,由于我使用類似數(shù)據(jù)在筆記本電腦上模擬測(cè)試加密運(yùn)行才花費(fèi)了不到 5 分鐘的時(shí)間,因此對(duì)于它花費(fèi)這么長(zhǎng)時(shí)間,我感到非常震驚。但是當(dāng)我們?cè)谏a(chǎn)數(shù)據(jù)庫(kù)系統(tǒng)中對(duì)列進(jìn)行加密時(shí),最要緊的是要使用陳舊服務(wù)器硬件所發(fā)生的情況。
了解到在正常維護(hù)時(shí)間內(nèi)執(zhí)行其他任務(wù)需要更多時(shí)間,我決定必須找到減少加密列花費(fèi)時(shí)間的方法。我的第一個(gè)直覺就是刪除包含目標(biāo)列的兩個(gè)索引。這樣,Oracle 只需加密表本身中的列數(shù)據(jù),之后我可以有效地重建索引,而沒有日志記錄開銷。經(jīng)過一些新的測(cè)試之后,我將加密列以及相關(guān)索引所需的時(shí)間從 70 分鐘(在加密期間存在索引)減少到僅 20 分鐘(加密列后重建索引)。列表 2 是我用來得出結(jié)論的測(cè)試示例(從我們?cè)诹斜?1 中停止的位置繼續(xù))。此外,請(qǐng)注意,列表中的時(shí)間來自用來編寫本文的測(cè)試系統(tǒng),而不是來自我的客戶端使用的實(shí)際系統(tǒng)。
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 要快速執(zhí)行對(duì)現(xiàn)有數(shù)據(jù)進(jìn)行加密的過程,只需在對(duì)其進(jìn)行加密之前刪除列的底層索引,然后再重建索引。
注:本文的模擬環(huán)境中使用了 CREATE INDEX 語(yǔ)句。在實(shí)際的設(shè)置中,可考慮使用 Oracle 數(shù)據(jù)庫(kù)的 DBMS_METADATA 實(shí)用程序包來生成 CREATE INDEX 語(yǔ)句,您可以使用這些語(yǔ)句在完成數(shù)據(jù)加密之后重新創(chuàng)建索引。
總之,在列加密之后重建索引的新策略可留出更多時(shí)間來處理整個(gè)過程中最具挑戰(zhàn)性的問題,這將在下一部分中進(jìn)行說明。
#p#
刪除未加密數(shù)據(jù)的虛副本
Oracle 和底層主機(jī)操作系統(tǒng)使用優(yōu)化的算法來更新數(shù)據(jù)塊中的數(shù)據(jù),目的是最大程度地減少降低性能的磁盤 I/O。在對(duì)現(xiàn)有列數(shù)據(jù)進(jìn)行加密的特定情況下,通常發(fā)生的一件事是 Oracle 將加密的列數(shù)據(jù)寫入到新數(shù)據(jù)塊并且只是將之前未加密的值占用的空間標(biāo)記為未使用。換句話說,Oracle 不會(huì)嘗試清除較舊的未加密數(shù)據(jù)。只要所討論的系統(tǒng)持續(xù)遇到大量更新活動(dòng),您就有理由確信當(dāng)重用塊空間時(shí) Oracle 將最終覆蓋較舊的未加密數(shù)據(jù)。但是考慮到我的客戶正在準(zhǔn)備進(jìn)行合規(guī)性審計(jì),我必須確保在加密過程之后立即擦除未加密的敏感數(shù)據(jù)。
進(jìn)行了很多研究之后,我在 Oracle 技術(shù)網(wǎng)上發(fā)現(xiàn)了一個(gè) FAQ 以及一個(gè)網(wǎng)志,該網(wǎng)志確認(rèn)這一特定問題并提供了有關(guān)解決該問題的一些基本想法。一般的想法是將包含之前未加密數(shù)據(jù)的所有段移動(dòng)到新的表空間(以及數(shù)據(jù)文件),然后使用一個(gè)操作系統(tǒng)實(shí)用程序刪除舊數(shù)據(jù)文件。但該做法聽起來容易,做起來難。事實(shí)是,在安全刪除舊表空間及其數(shù)據(jù)文件之前,您很有可能需要移動(dòng)大量段以及包含敏感數(shù)據(jù)的段。
為了使這一可能費(fèi)時(shí)費(fèi)力且易于出錯(cuò)的過程自動(dòng)進(jìn)行,我將一些腳本放在一起,以幫助我構(gòu)建完成這一切所需的 DDL 語(yǔ)句。這里,我要向 Tom Kyte 表示謝意,因?yàn)榇颂幍囊恍┕ぷ魇切薷奈以?Asktom 站點(diǎn)找到的內(nèi)容查詢。列表 3 顯示了我使用的整個(gè)過程的一個(gè)示例。
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 刪除剩余敏感數(shù)據(jù)的未加密虛副本需要一系列步驟才能解決很多相關(guān)性。
列表 3 中的最后一步針對(duì)特定的操作系統(tǒng)。在本例中,我演示了 Linux/Unix shred 實(shí)用程序的用法。其他您可能想探究的實(shí)用程序是 Linux/Unix wipe、scrub 和 srm 程序。
準(zhǔn)備可能的重新生成密鑰操作
我的客戶還想做些準(zhǔn)備以應(yīng)對(duì)將來可能需要重新生成密鑰的情況。為現(xiàn)有數(shù)據(jù)重新生成密鑰意味著您要使用新密鑰對(duì)現(xiàn)有數(shù)據(jù)重新加密。當(dāng)您懷疑某人已經(jīng)獲得對(duì)現(xiàn)有表密鑰的訪問權(quán)限并且可能會(huì)在您的控制之外破解敏感數(shù)據(jù)時(shí),可能需要執(zhí)行該操作。
完成重新生成密鑰操作的步驟與最初加密現(xiàn)有數(shù)據(jù)的步驟類似:刪除目標(biāo)列上的索引,重新生成該列數(shù)據(jù)的密鑰,然后重建刪除的索引?;蛘撸绻鷵?dān)心與使用以前的密鑰加密的數(shù)據(jù)相對(duì)應(yīng)的虛副本,可以重復(fù)將段移動(dòng)到新表空間、刪除舊表空間、然后清除舊表空間的數(shù)據(jù)文件的過程。
注:以我的經(jīng)驗(yàn),PCI 審計(jì)員會(huì)對(duì)重新生成萬能加密密鑰非常滿意,因?yàn)樵撨^程不需要訪問任何數(shù)據(jù),并且 PCI 標(biāo)準(zhǔn)不包含對(duì) 2 層密鑰體系結(jié)構(gòu)(如 Oracle 的體系結(jié)構(gòu))的建議。從符合 PCI 的角度看,重新生成萬能加密密鑰應(yīng)該足夠了,而且 PCI 審計(jì)員無法強(qiáng)制具有幾十億行的公司將其業(yè)務(wù)關(guān)閉幾天,僅僅只是為了重新生成密鑰。
確定可能的查詢計(jì)劃更改
Oracle 文檔、若干文章以及我閱讀過的一些論壇帖子幾乎都提到了有關(guān)加密列的數(shù)據(jù)后現(xiàn)有查詢執(zhí)行計(jì)劃可能發(fā)生更改的概要信息以及某些具體信息。一般來說,相對(duì)于沒有索引的列,在加密具有索引的列時(shí),您必須注意執(zhí)行 SQL 語(yǔ)句所發(fā)生的情況。當(dāng) Oracle 加密具有索引的列時(shí),Oracle 還將加密相應(yīng)的索引值。如果您花點(diǎn)時(shí)間考慮這個(gè)問題,就會(huì)清楚地發(fā)現(xiàn)以具有索引的數(shù)據(jù)為目標(biāo)的相等謂詞應(yīng)該繼續(xù)利用索引,但由于該索引值存儲(chǔ)在索引中的方式,加密值的隨機(jī)性質(zhì)使得加密索引的范圍掃描成本過高。列表 4 演示了這些詳細(xì)描述的基本情況。
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 仔細(xì)識(shí)別引用加密列數(shù)據(jù)的 SQL 語(yǔ)句,然后比較加密前后這些語(yǔ)句的執(zhí)行計(jì)劃,以查找是否存在任何更改。
我還想知道加密的開銷是否會(huì)改變計(jì)劃成本以及優(yōu)化程序的選擇,即使在文檔建議不要這樣做的情況下。為了確保我確切知道進(jìn)行生產(chǎn)時(shí)關(guān)鍵任務(wù)應(yīng)用程序所發(fā)生的情況,我在沙箱環(huán)境中進(jìn)行了一些額外的工作。首先,我從各種自動(dòng)負(fù)載信息庫(kù) (AWR) 快照中收集了一個(gè)使用頻繁的 SQL 語(yǔ)句(CPU、Gets 和 I/O)列表。然后,我比較了加密列前后每個(gè) SQL 語(yǔ)句的查詢執(zhí)行計(jì)劃。我的研究轉(zhuǎn)向?qū)Χ鄠€(gè)基于相等的條件使用一個(gè)謂詞的復(fù)雜查詢,其中一個(gè)條件是以將要進(jìn)行加密的列為目標(biāo)。讓我吃驚的是,在對(duì)列進(jìn)行加密之后,該查詢的執(zhí)行計(jì)劃發(fā)生了改變。遺憾的是,我無法在我的測(cè)試實(shí)驗(yàn)室中為本文復(fù)制這些結(jié)果,我仍然無法完全確定查詢計(jì)劃發(fā)生改變的原因。但我之所以在此處提到這種情況是想指出,在對(duì)生產(chǎn)系統(tǒng)進(jìn)行更改之前,最好在測(cè)試環(huán)境中研究生產(chǎn)應(yīng)用程序密鑰查詢的執(zhí)行計(jì)劃。如果我假設(shè)沒有任何使用頻繁的查詢會(huì)發(fā)生改變,那么我們將對(duì)生產(chǎn)系統(tǒng)進(jìn)行更改并且不得不勉強(qiáng)拼湊一個(gè)解決方案。
此處的教訓(xùn)是,在進(jìn)行更改之前,您應(yīng)該始終對(duì)這些事項(xiàng)進(jìn)行測(cè)試,無論您在文檔和其他來源中閱讀了什么內(nèi)容都是如此。
結(jié)論
使用 Oracle 的 TDE 特性加密新表與沒有任何數(shù)據(jù)的表中的列或者現(xiàn)有表中的新列非常簡(jiǎn)單,原因是不存在任何需要擔(dān)心的相關(guān)性。相反,加密現(xiàn)有列數(shù)據(jù)需要仔細(xì)研究并在您的沙箱環(huán)境中進(jìn)行測(cè)試,然后才能在實(shí)際生產(chǎn)系統(tǒng)中實(shí)施您的計(jì)劃,因?yàn)榧用芸赡軙?huì)影響很多相關(guān)性。
Steve Bobrowski 自 Oracle 數(shù)據(jù)庫(kù)版本 5 開始一直使用該軟件,曾就職于 Oracle;他還是 The Database Domain (dbdomain.com) 的創(chuàng)始人以及五本 Oracle 出版社出版的書籍的作者(包括《Oracle 數(shù)據(jù)庫(kù) 10g 快捷版上機(jī)操作》系列)。最近,Steve 擔(dān)任著幾個(gè)大型公司(如 Computer Sciences Corporation、BEA Systems 和 Salesforce.com)的 SaaS 顧問和 SaaS 首席技術(shù)官。
【編輯推薦】