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ì)象的定義。