Oracle PL/SQL 源代码加密实战

作者: 不剪发的Tony老师
毕业于北京航空航天大学,十多年数据库管理与开发经验,目前在一家全球性的金融公司从事数据库架构设计。CSDN学院签约讲师以及GitChat专栏作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net


文章目录

        PL/SQL 源代码加密概述
            加密的原则
            加密局限性
        使用 wrap 工具加密 PL/SQL 代码
        使用 DBMS_DDL 加密 PL/SQL 代码

大家好,我是只谈技术不剪发的 Tony 老师。

对 PL/SQL 源代码进行加密可以在交付应用时隐藏源码和实现细节,同时也可以防止发布出去的代码被篡改;Oracle 数据库系统内置的PL/SQL 程序包和类型的代码绝大部分经过了加密处理。Oracle 为我们提供了两种加密 PL/SQL 源代码的方法:wrap 实用工具和 DBMS_DDL 子程序。本文就给大家介绍一下如何利用这些方法提高 PL/SQL 代码的安全性。

如果觉得文章有用,欢迎评论📝、点赞👍、推荐🎁
PL/SQL 源代码加密概述

加密(wrap) PL/SQL 源代码就是通过混淆隐藏 PL/SQL 内容的过程。包含加密后内容的文件被称为加密文件(wrapped file),加密文件可以被 SQL*Plus 或者导入/导出工具移动、备份以及处理,但是内容无法通过数据字典视图 *_SOURCE 进行查看。

以下 PL/SQL 对象的源代码可以进行加密:

    程序包规范
    程序包体
    类型规范
    类型体
    函数
    过程

PL/SQL 加密可以使用 wrap 实用工具或者 DBMS_DDL 子程序实现。wrap 工具从命令行运行,可以加密 SQL 脚本文件中的任何可加密的 PL/SQL 对象,例如一个 SQL*Plus 安装脚本。DBMS_DDL 子程序可以加密单个动态生成的 PL/SQL 单元,例如 CREATE PROCEDURE 命令。

两种加密方法都可以检查标记化错误(例如字符串超长),但是不会检查语法或者语义错误(例如不存在的表或视图)。
加密的原则

加密PL/SQL 源代码时,建议遵循以下规则:

    加密程序包或者对象类型时只加密包体,不加密包规范。这样可以允许其他开发人员使用该程序包或者类型时查看需要的信息,而不能查看具体的实现。
    只加密已经开发完成的源代码文件。加密文件不能进行编辑,如果想要修改加密后的 PL/SQL 代码,必须编辑未加密的原始文件并再次进行加密。
    发布加密文件之前使用文本编辑器查看并确认所有重要的内容都进行了加密。

加密局限性

PL/SQL 源代码的加密功能存在以下局限性,使用时需要注意:

    加密文件不支持 Oracle 数据库的向下兼容。例如,版本 n.1 的 PL/SQL 加密工具生成的文件无法导入版本 (n-1).2 的 Oracle 数据库中,甚至版本 n.2 的 PL/SQL 加密工具生成的文件无法导入版本 n.1 的 Oracle 数据库中。加密文件支持向上兼容,以及同一版本中的不同补丁包之间兼容。
    加密 PL/SQL 源代码不适合作为一个隐藏密码或者表名的安全方法。对于更高级别的安全需求,可以考虑使用 Oracle Database Vault。
    加密工具无法加密触发器的源代码。如果想要隐藏触发器的实现细节,可以将具体实现放入一个存储程序,然后加密该程序,最后编写一个调用加密程序的触发器。

使用 wrap 工具加密 PL/SQL 代码

wrap 工具接收一个 SQL 文件作为输入,加密该文件中可加密的 PL/SQL 对象(不会加密匿名块、触发器或者非 PL/SQL 代码),然后输出一个对应的加密文件。

wrap 工具位于 $ORACLE_HOME/bin/ 目录下,在操作系统提示符中输入以下命令:

wrap iname=input_file [ oname=output_file ] [ keep_comments=yes ]

 

其中,input_file 是包含 SQL 语句的文件;如果忽略文件扩展名,默认使用 .sql。output_file 是创建的加密文件;oname 可选,输出文件名默认为输入文件名加上扩展名 .plb。wrap 工具默认会删除所有的注释,除非指定了 keep_comments=yes;此时,加密文件中会保留源码之外的所有注释。注意,等号两边不能包含任何空格。

    📝如果 input_file 是已经加密的文件,不会进行任何处理,output_file 文件的内容和 input_file 完全相同。input_file 文件中不能包含任何使用 SQLPlus DEFINE 定义的替换变量,因为 output_file 通过 PL/SQL 编译器进行解析,而不是 SQLPlus。

例如,以下命令的效果等价:

wrap iname=/mydir/myfile
wrap iname=/mydir/myfile.sql oname=/mydir/myfile.plb

 

以下命令为 input_file 指定了非默认的扩展名,为 output_file 指定了非默认的文件名,同时保留了注释信息:

wrap iname=/mydir/myfile.src oname=/yourdir/yourfile.out keep_comments=yes

 

加密之后的 output_file 文件可以通过 SQL*Plus 直接执行,创建 PL/SQL 对象:

SQL> @myfile.plb;

 

接下来看一个示例,假设 wraptest2.sql 文件包含以下内容:

-- 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 过程和 fibonacci 函数是可加密的 PL/SQL 单元;另外该文件中还包含了一些注释以及一个 SELECT 语句。

从操作系统提示符中运行以下命令进行加密:

> wrap keep_comments=yes iname=wraptest2.sql

    1

执行成功后输出的信息如下:

 Processing wraptest2.sql to wraptest2.plb

    1

加密后的 wraptest2.plb 文件内容如下:

-- 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==

/

 

加密文件中删除了代码内部的注释并加密了 wraptest 和 fibonacci 的源代码,同时保留了加密对象外部的注释。

然后,我们可以在 SQL*Plus 中运行 wraptest2.plb 文件创建对象、查看子程序的内容并且调用子程序:

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 函数和 CREATE_WRAPPED 过程,可以用于加密单个动态创建的可加密 PL/SQL 对象。同时还提供了一个依次 MALFORMED_WRAP_INPUT(ORA-24230),当输入参数不是一个可加密的 PL/SQL 对象 DDL 语句时抛出。

WRAP 函数接收一个 CREATE 语句作为输入,返回一个加密后的 CREATE 语句;CREATE_WRAPPED 过程相当于执行 WRAP 函数后再执行返回的 CREATE 语句创建相应的 PL/SQL 对象。输入参数中的 CREATE 语句具有调用者权限执行。

以下示例通过 EXECUTE IMMEDIATE 语句动态创建了一个程序包规范和加密的程序包体(通过 CREATE_WRAPPED 语句):

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;
/

 

以下语句查看程序包 emp_actions 的内容:

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=

 

如果程序包的规范也进行了加密,就无法读取调用子程序的接口信息。执行以下命令调用 emp_actions.raise_salary 过程:

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 返回的语句作为 statement 参数(VARCHAR2A)传给 DBMS_SQL.PARSE 过程,必须将DBMS_SQL.PARSE 的参数 lfflg 设置为 FALSE;否则,DBMS_SQL.PARSE 将会增加一些内容到加密后的 PL/SQL 对象中,从而破坏对象的定义。