Oracle PL/SQL 源代碼加密實(shí)戰(zhàn)
作者: 不剪發(fā)的Tony老師
畢業(yè)于北京航空航天大學(xué),十多年數(shù)據(jù)庫(kù)管理與開發(fā)經(jīng)驗(yàn),目前在一家全球性的金融公司從事數(shù)據(jù)庫(kù)架構(gòu)設(shè)計(jì)。CSDN學(xué)院簽約講師以及GitChat專欄作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net
文章目錄
PL/SQL 源代碼加密概述
加密的原則
加密局限性
使用 wrap 工具加密 PL/SQL 代碼
使用 DBMS_DDL 加密 PL/SQL 代碼
大家好,我是只談技術(shù)不剪發(fā)的 Tony 老師。
對(duì) PL/SQL 源代碼進(jìn)行加密可以在交付應(yīng)用時(shí)隱藏源碼和實(shí)現(xiàn)細(xì)節(jié),同時(shí)也可以防止發(fā)布出去的代碼被篡改;Oracle 數(shù)據(jù)庫(kù)系統(tǒng)內(nèi)置的PL/SQL 程序包和類型的代碼絕大部分經(jīng)過(guò)了加密處理。Oracle 為我們提供了兩種加密 PL/SQL 源代碼的方法:wrap 實(shí)用工具和 DBMS_DDL 子程序。本文就給大家介紹一下如何利用這些方法提高 PL/SQL 代碼的安全性。
如果覺得文章有用,歡迎評(píng)論??、點(diǎn)贊??、推薦??
PL/SQL 源代碼加密概述
加密(wrap) PL/SQL 源代碼就是通過(guò)混淆隱藏 PL/SQL 內(nèi)容的過(guò)程。包含加密后內(nèi)容的文件被稱為加密文件(wrapped file),加密文件可以被 SQL*Plus 或者導(dǎo)入/導(dǎo)出工具移動(dòng)、備份以及處理,但是內(nèi)容無(wú)法通過(guò)數(shù)據(jù)字典視圖 *_SOURCE 進(jìn)行查看。
以下 PL/SQL 對(duì)象的源代碼可以進(jìn)行加密:
程序包規(guī)范
程序包體
類型規(guī)范
類型體
函數(shù)
過(guò)程
PL/SQL 加密可以使用 wrap 實(shí)用工具或者 DBMS_DDL 子程序?qū)崿F(xiàn)。wrap 工具從命令行運(yùn)行,可以加密 SQL 腳本文件中的任何可加密的 PL/SQL 對(duì)象,例如一個(gè) SQL*Plus 安裝腳本。DBMS_DDL 子程序可以加密單個(gè)動(dòng)態(tài)生成的 PL/SQL 單元,例如 CREATE PROCEDURE 命令。
兩種加密方法都可以檢查標(biāo)記化錯(cuò)誤(例如字符串超長(zhǎng)),但是不會(huì)檢查語(yǔ)法或者語(yǔ)義錯(cuò)誤(例如不存在的表或視圖)。
加密的原則
加密PL/SQL 源代碼時(shí),建議遵循以下規(guī)則:
加密程序包或者對(duì)象類型時(shí)只加密包體,不加密包規(guī)范。這樣可以允許其他開發(fā)人員使用該程序包或者類型時(shí)查看需要的信息,而不能查看具體的實(shí)現(xiàn)。
只加密已經(jīng)開發(fā)完成的源代碼文件。加密文件不能進(jìn)行編輯,如果想要修改加密后的 PL/SQL 代碼,必須編輯未加密的原始文件并再次進(jìn)行加密。
發(fā)布加密文件之前使用文本編輯器查看并確認(rèn)所有重要的內(nèi)容都進(jìn)行了加密。
加密局限性
PL/SQL 源代碼的加密功能存在以下局限性,使用時(shí)需要注意:
加密文件不支持 Oracle 數(shù)據(jù)庫(kù)的向下兼容。例如,版本 n.1 的 PL/SQL 加密工具生成的文件無(wú)法導(dǎo)入版本
(n-1).2 的 Oracle 數(shù)據(jù)庫(kù)中,甚至版本 n.2 的 PL/SQL 加密工具生成的文件無(wú)法導(dǎo)入版本 n.1 的 Oracle
數(shù)據(jù)庫(kù)中。加密文件支持向上兼容,以及同一版本中的不同補(bǔ)丁包之間兼容。
加密 PL/SQL 源代碼不適合作為一個(gè)隱藏密碼或者表名的安全方法。對(duì)于更高級(jí)別的安全需求,可以考慮使用 Oracle Database Vault。
加密工具無(wú)法加密觸發(fā)器的源代碼。如果想要隱藏觸發(fā)器的實(shí)現(xiàn)細(xì)節(jié),可以將具體實(shí)現(xiàn)放入一個(gè)存儲(chǔ)程序,然后加密該程序,最后編寫一個(gè)調(diào)用加密程序的觸發(fā)器。
使用 wrap 工具加密 PL/SQL 代碼
wrap 工具接收一個(gè) SQL 文件作為輸入,加密該文件中可加密的 PL/SQL 對(duì)象(不會(huì)加密匿名塊、觸發(fā)器或者非 PL/SQL 代碼),然后輸出一個(gè)對(duì)應(yīng)的加密文件。
wrap 工具位于 $ORACLE_HOME/bin/ 目錄下,在操作系統(tǒng)提示符中輸入以下命令:
wrap iname=input_file [ oname=output_file ] [ keep_comments=yes ]
其中,input_file 是包含 SQL 語(yǔ)句的文件;如果忽略文件擴(kuò)展名,默認(rèn)使用 .sql。output_file 是創(chuàng)建的加密文件;oname 可選,輸出文件名默認(rèn)為輸入文件名加上擴(kuò)展名 .plb。wrap 工具默認(rèn)會(huì)刪除所有的注釋,除非指定了 keep_comments=yes;此時(shí),加密文件中會(huì)保留源碼之外的所有注釋。注意,等號(hào)兩邊不能包含任何空格。
??如果 input_file 是已經(jīng)加密的文件,不會(huì)進(jìn)行任何處理,output_file 文件的內(nèi)容和 input_file 完全相同。input_file 文件中不能包含任何使用 SQLPlus DEFINE 定義的替換變量,因?yàn)?output_file 通過(guò) PL/SQL 編譯器進(jìn)行解析,而不是 SQLPlus。
例如,以下命令的效果等價(jià):
wrap iname=/mydir/myfile
wrap iname=/mydir/myfile.sql oname=/mydir/myfile.plb
以下命令為 input_file 指定了非默認(rèn)的擴(kuò)展名,為 output_file 指定了非默認(rèn)的文件名,同時(shí)保留了注釋信息:
wrap iname=/mydir/myfile.src oname=/yourdir/yourfile.out keep_comments=yes
加密之后的 output_file 文件可以通過(guò) SQL*Plus 直接執(zhí)行,創(chuàng)建 PL/SQL 對(duì)象:
SQL> @myfile.plb;
接下來(lái)看一個(gè)示例,假設(shè) wraptest2.sql 文件包含以下內(nèi)容:
-- The following statement will not change.
SELECT COUNT(*) FROM EMPLOYEES
/
/* The PL/SQL source text of the following two CREATE statements will be wrapped. */
CREATE PROCEDURE wraptest AUTHID CURRENT_USER /* C style comment in procedure declaration */ IS
TYPE emp_tab IS TABLE OF employees%ROWTYPE INDEX BY PLS_INTEGER;
all_emps emp_tab;
BEGIN
SELECT * BULK COLLECT INTO all_emps FROM employees;
FOR i IN 1..10 LOOP /* C style in pl/sql source */
DBMS_OUTPUT.PUT_LINE('Emp Id: ' || all_emps(i).employee_id);
END LOOP;
END;
/
CREATE OR REPLACE FUNCTION fibonacci (
n PLS_INTEGER
) RETURN PLS_INTEGER
AUTHID CURRENT_USER -- PL/SQL style comment inside fibonacci function spec
IS
fib_1 PLS_INTEGER := 0;
fib_2 PLS_INTEGER := 1;
BEGIN
IF n = 1 THEN -- terminating condition
RETURN fib_1;
ELSIF n = 2 THEN
RETURN fib_2; -- terminating condition
ELSE
RETURN fibonacci(n-2) + fibonacci(n-1); -- recursive invocations
END IF;
END;
/
其中,wraptest 過(guò)程和 fibonacci 函數(shù)是可加密的 PL/SQL 單元;另外該文件中還包含了一些注釋以及一個(gè) SELECT 語(yǔ)句。
從操作系統(tǒng)提示符中運(yùn)行以下命令進(jìn)行加密:
> wrap keep_comments=yes iname=wraptest2.sql
1
執(zhí)行成功后輸出的信息如下:
Processing wraptest2.sql to wraptest2.plb
1
加密后的 wraptest2.plb 文件內(nèi)容如下:
-- The following statement will not change.
SELECT COUNT(*) FROM EMPLOYEES
/
/* The PL/SQL source text of the following two CREATE statements will be wrapped. */
CREATE OR REPLACE PROCEDURE wraptest wrapped
a000000
1
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
7
129 138
qf4HggDBeNMPlWAsPn6pGf+2LGwwg+nwJK5qZ3SVWE4+GayDZaL1bF7RwYm2/zr1qjZY3FrN
48M1bKc/MG5aY9YB+DrtT4SJN370Rpq7ck5D0sc1D5sKAwTyX13HYvRmjwkdXa0vEZ4q/mCU
EQusX23UZbZjxha7CtlCDCx8guGw/M/oHZXc8wDHXL8V8OsqQMv/Hj7z68gINl7OstalRScr
uSZ/l/W1YaaA9Lj8Fbx5/nJw96ZNy1SCY8VsB/G6O5f/65+EDxdThpnfU4e1vrrE9iB3/IpI
+7fE1Tv29fwc+aZq3S7O
/
CREATE OR REPLACE FUNCTION fibonacci wrapped
a000000
1
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
8
150 ff
BFDvTL9OR04SJbx+qOy5H/h8IcwwgxDcAJnWZ3TNz51mjAmegdQcpNJfq8hUuQtv1Y5xg7Wd
KqMH/HBANhnZ+E1mBWekavYjPxlqV9zIFqZAgB4SBqkqe42sai9Vb0cLEU02/ZCEyxDSfWf3
H1Lp6U9ztRXNy+oDZSNykWCUVLaZro0UmeFrNUBqzE6j9mI3AyRhPw1QbZX5oRMLgLOG3OtS
SGJsz7M+bnhnp+xP4ww+SIlxx5LhDtnyPw==
/
加密文件中刪除了代碼內(nèi)部的注釋并加密了 wraptest 和 fibonacci 的源代碼,同時(shí)保留了加密對(duì)象外部的注釋。
然后,我們可以在 SQL*Plus 中運(yùn)行 wraptest2.plb 文件創(chuàng)建對(duì)象、查看子程序的內(nèi)容并且調(diào)用子程序:
SQL> -- Run wrapped file:
SQL>
SQL> @wraptest2.plb
SQL> -- The following statement will not change.
SQL>
SQL> SELECT COUNT(*) FROM EMPLOYEES
2 /
COUNT(*)
----------
107
1 row selected.
SQL> /* The PL/SQL source text of the following two CREATE statements will be wrapped. */
SQL> CREATE PROCEDURE wraptest wrapped
2 a000000
3 1
4 abcd
5 abcd
6 abcd
7 abcd
8 abcd
9 abcd
10 abcd
11 abcd
12 abcd
13 abcd
14 abcd
15 abcd
16 abcd
17 abcd
18 abcd
19 7
20 129 138
21 qf4HggDBeNMPlWAsPn6pGf+2LGwwg+nwJK5qZ3SVWE4+GayDZaL1bF7RwYm2/zr1qjZY3FrN
22 48M1bKc/MG5aY9YB+DrtT4SJN370Rpq7ck5D0sc1D5sKAwTyX13HYvRmjwkdXa0vEZ4q/mCU
23 EQusX23UZbZjxha7CtlCDCx8guGw/M/oHZXc8wDHXL8V8OsqQMv/Hj7z68gINl7OstalRScr
24 uSZ/l/W1YaaA9Lj8Fbx5/nJw96ZNy1SCY8VsB/G6O5f/65+EDxdThpnfU4e1vrrE9iB3/IpI
25 +7fE1Tv29fwc+aZq3S7O
26
27 /
Procedure created.
SQL> CREATE OR REPLACE FUNCTION fibonacci wrapped
2 a000000
3 1
4 abcd
5 abcd
6 abcd
7 abcd
8 abcd
9 abcd
10 abcd
11 abcd
12 abcd
13 abcd
14 abcd
15 abcd
16 abcd
17 abcd
18 abcd
19 8
20 150 ff
21 BFDvTL9OR04SJbx+qOy5H/h8IcwwgxDcAJnWZ3TNz51mjAmegdQcpNJfq8hUuQtv1Y5xg7Wd
22 KqMH/HBANhnZ+E1mBWekavYjPxlqV9zIFqZAgB4SBqkqe42sai9Vb0cLEU02/ZCEyxDSfWf3
23 H1Lp6U9ztRXNy+oDZSNykWCUVLaZro0UmeFrNUBqzE6j9mI3AyRhPw1QbZX5oRMLgLOG3OtS
24 SGJsz7M+bnhnp+xP4ww+SIlxx5LhDtnyPw==
25
26 /
Function created.
SQL>
SQL> -- Try to display procedure source text:
SQL>
SQL> SELECT text FROM USER_SOURCE WHERE name='WRAPTEST';
TEXT
--------------------------------------------------------------------------------
PROCEDURE wraptest wrapped
a000000
1
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
7
129 138
qf4HggDBeNMPlWAsPn6pGf+2LGwwg+nwJK5qZ3SVWE4+GayDZaL1bF7RwYm2/zr1qjZY3FrN
48M1bKc/MG5aY9YB+DrtT4SJN370Rpq7ck5D0sc1D5sKAwTyX13HYvRmjwkdXa0vEZ4q/mCU
EQusX23UZbZjxha7CtlCDCx8guGw/M/oHZXc8wDHXL8V8OsqQMv/Hj7z68gINl7OstalRScr
uSZ/l/W1YaaA9Lj8Fbx5/nJw96ZNy1SCY8VsB/G6O5f/65+EDxdThpnfU4e1vrrE9iB3/IpI
+7fE1Tv29fwc+aZq3S7O
1 row selected.
SQL>
SQL> -- Try to display function source text:
SQL>
SQL> SELECT text FROM USER_SOURCE WHERE name='FIBONACCI';
TEXT
--------------------------------------------------------------------------------
FUNCTION fibonacci wrapped
a000000
1
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
8
150 ff
BFDvTL9OR04SJbx+qOy5H/h8IcwwgxDcAJnWZ3TNz51mjAmegdQcpNJfq8hUuQtv1Y5xg7Wd
KqMH/HBANhnZ+E1mBWekavYjPxlqV9zIFqZAgB4SBqkqe42sai9Vb0cLEU02/ZCEyxDSfWf3
H1Lp6U9ztRXNy+oDZSNykWCUVLaZro0UmeFrNUBqzE6j9mI3AyRhPw1QbZX5oRMLgLOG3OtS
SGJsz7M+bnhnp+xP4ww+SIlxx5LhDtnyPw==
1 row selected.
SQL>
SQL> BEGIN
2 wraptest; -- invoke procedure
3 DBMS_OUTPUT.PUT_LINE('fibonacci(5) = ' || fibonacci(5));
4 END;
5 /
Emp Id: 100
Emp Id: 101
Emp Id: 102
Emp Id: 103
Emp Id: 104
Emp Id: 105
Emp Id: 106
Emp Id: 107
Emp Id: 108
Emp Id: 109
fibonacci(5) = 3
PL/SQL procedure successfully completed.
SQL>
使用 DBMS_DDL 加密 PL/SQL 代碼
DBMS_DDL 程序包提供了 WRAP 函數(shù)和 CREATE_WRAPPED 過(guò)程,可以用于加密單個(gè)動(dòng)態(tài)創(chuàng)建的可加密 PL/SQL 對(duì)象。同時(shí)還提供了一個(gè)依次 MALFORMED_WRAP_INPUT(ORA-24230),當(dāng)輸入?yún)?shù)不是一個(gè)可加密的 PL/SQL 對(duì)象 DDL 語(yǔ)句時(shí)拋出。
WRAP 函數(shù)接收一個(gè) CREATE 語(yǔ)句作為輸入,返回一個(gè)加密后的 CREATE 語(yǔ)句;CREATE_WRAPPED 過(guò)程相當(dāng)于執(zhí)行 WRAP 函數(shù)后再執(zhí)行返回的 CREATE 語(yǔ)句創(chuàng)建相應(yīng)的 PL/SQL 對(duì)象。輸入?yún)?shù)中的 CREATE 語(yǔ)句具有調(diào)用者權(quán)限執(zhí)行。
以下示例通過(guò) EXECUTE IMMEDIATE 語(yǔ)句動(dòng)態(tài)創(chuàng)建了一個(gè)程序包規(guī)范和加密的程序包體(通過(guò) CREATE_WRAPPED 語(yǔ)句):
DECLARE
package_text VARCHAR2(32767); -- text for creating package spec and body
FUNCTION generate_spec (pkgname VARCHAR2) RETURN VARCHAR2 AS
BEGIN
RETURN 'CREATE PACKAGE ' || pkgname || ' AUTHID CURRENT_USER AS
PROCEDURE raise_salary (emp_id NUMBER, amount NUMBER);
PROCEDURE fire_employee (emp_id NUMBER);
END ' || pkgname || ';';
END generate_spec;
FUNCTION generate_body (pkgname VARCHAR2) RETURN VARCHAR2 AS
BEGIN
RETURN 'CREATE PACKAGE BODY ' || pkgname || ' AS
PROCEDURE raise_salary (emp_id NUMBER, amount NUMBER) IS
BEGIN
UPDATE employees
SET salary = salary + amount WHERE employee_id = emp_id;
END raise_salary;
PROCEDURE fire_employee (emp_id NUMBER) IS
BEGIN
DELETE FROM employees WHERE employee_id = emp_id;
END fire_employee;
END ' || pkgname || ';';
END generate_body;
BEGIN
package_text := generate_spec('emp_actions'); -- Generate package spec
EXECUTE IMMEDIATE package_text; -- Create package spec
package_text := generate_body('emp_actions'); -- Generate package body
SYS.DBMS_DDL.CREATE_WRAPPED(package_text); -- Create wrapped package body
END;
/
以下語(yǔ)句查看程序包 emp_actions 的內(nèi)容:
SELECT text FROM USER_SOURCE WHERE name = 'EMP_ACTIONS';
TEXT
------------------------------------------------------------------------
PACKAGE emp_actions AUTHID CURRENT_USER AS
PROCEDURE raise_salary (emp_id NUMBER, amount NUMBER);
PROCEDURE fire_employee (emp_id NUMBER);
END emp_actions;
PACKAGE BODY emp_actions wrapped
a000000
1f
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
b
180 113
1fOVodewm7j9dBOmBsiEQz0BKCgwg/BKoZ4VZy/pTBIYo8Uj1sjpbEz08Ck3HMjYq/Mf0XZn
u9D0Kd+i89g9ZO61I6vZYjw2AuBidnLESyR63LHZpFD/7lyDTfF1eDY5vmNwLTXrFaxGy243
0lHKAzmOlwwfBWylkZZNi2UnpmSIe6z/BU2nhbwfpqd224p69FwYVXmFX2H5IMsdZ2/vWsK9
cDMCD1KEqOnPpbU2yXdpW3GIbGD8JFIbKAfpJLkoLfVxoRPXQfj0h1k=
如果程序包的規(guī)范也進(jìn)行了加密,就無(wú)法讀取調(diào)用子程序的接口信息。執(zhí)行以下命令調(diào)用 emp_actions.raise_salary 過(guò)程:
DECLARE
s employees.salary%TYPE;
BEGIN
SELECT salary INTO s FROM employees WHERE employee_id=130;
DBMS_OUTPUT.PUT_LINE('Old salary: ' || s);
emp_actions.raise_salary(130, 100);
SELECT salary INTO s FROM employees WHERE employee_id=130;
DBMS_OUTPUT.PUT_LINE('New salary: ' || s);
END;
/
Old salary: 3557.4
New salary: 3657.4
PL/SQL procedure successfully completed.
??如果將 DBMS_DDL.WRAP 返回的語(yǔ)句作為 statement 參數(shù)(VARCHAR2A)傳給
DBMS_SQL.PARSE 過(guò)程,必須將DBMS_SQL.PARSE 的參數(shù) lfflg 設(shè)置為
FALSE;否則,DBMS_SQL.PARSE 將會(huì)增加一些內(nèi)容到加密后的 PL/SQL 對(duì)象中,從而破壞對(duì)象的定義。