Friday, May 13, 2011

Column Masking

One of the issues that we are facing is identity theft.
We store lot of sensitive information in the database  and expect ( or pray ) for it to be secure .

The data  can be stored in encrypted tablespace .
TDE works at the tablespace level  , ie , if someone steals the datafile ,
they can't see the data in the raw datafile  if it is encrypted .

But from sqlplus ( any other app )  , the columns will be visible .
Here is one way to mask the sensitive columns .


SQL> show user
USER is "ZAHIR"

SQL> create table employee as select object_id ssn , object_name ename from all_objects ;

Table created.

SQL> desc employee
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SSN                                       NOT NULL NUMBER
 ENAME                                     NOT NULL VARCHAR2(30)

SQL> CREATE OR REPLACE
PACKAGE sec_data_pkg
AS
FUNCTION mask_ssn(
    p_owner IN VARCHAR2,
    p_name  IN VARCHAR2 )
  RETURN VARCHAR2;
END sec_data_pkg;


Package created.

SQL>
CREATE OR REPLACE
  2  PACKAGE BODY sec_data_pkg
  3  AS
  4  FUNCTION mask_ssn(
  5      p_owner IN VARCHAR2,
  6      p_name  IN VARCHAR2 )
  7    RETURN VARCHAR2
  8  AS
  9  BEGIN
 10    IF sys_context( 'userenv', 'session_user' ) = 'ZAHIR' THEN
 11      RETURN NULL;
 12    ELSE
 13      RETURN '1=0';
 14    END IF;
 15  END;
 16    END sec_data_pkg;
 17
 18  / 

Package body created.

SQL>   BEGIN
  2           DBMS_RLS.ADD_POLICY(object_schema=>user, object_name=>'EMPLOYEE',
  3                               policy_name=>'SECURE_SSN',
  4                               function_schema=>user,
  5                               policy_function=>'sec_data_pkg.mask_ssn',
  6                               sec_relevant_cols=>'ssn',
  7                               sec_relevant_cols_opt=>dbms_rls.ALL_ROWS);
  8     END;
  9     /
                            
PL/SQL procedure successfully completed.

SQL>  create  synonym empl for employee;

Synonym created.

SQL> grant select on empl to public;

Grant succeeded.

SQL> show user
USER is "ZAHIR"

SQL>  select * from zahir.empl where rownum <= 10;

       SSN ENAME
---------- ------------------------------
       100 ORA$BASE
       116 DUAL
       117 DUAL
       280 MAP_OBJECT
       365 SYSTEM_PRIVILEGE_MAP
       367 SYSTEM_PRIVILEGE_MAP
       368 TABLE_PRIVILEGE_MAP
       370 TABLE_PRIVILEGE_MAP
       371 STMT_AUDIT_OPTION_MAP
       373 STMT_AUDIT_OPTION_MAP

10 rows selected.


Let us see , if any other signs on to the database and issues the same SELECT .
The values in the column SSN will not be visible , as per our policy (  sec_relevant_cols=>'ssn')


SQL> conn scott/tiger
Connected.

SQL> select * from zahir.empl where rownum <= 10;

       SSN ENAME
---------- ------------------------------
           ORA$BASE
           DUAL
           DUAL
           MAP_OBJECT
           SYSTEM_PRIVILEGE_MAP
           SYSTEM_PRIVILEGE_MAP
           TABLE_PRIVILEGE_MAP
           TABLE_PRIVILEGE_MAP
           STMT_AUDIT_OPTION_MAP
           STMT_AUDIT_OPTION_MAP

10 rows selected.

Please send your comments / suggestions .


Update :

I have updated this post for this question 

Column Masking was interesting, however how do we show only the last 4 digit of SSN if we want to. Can you please brief something on PCI compliance

We would use a view to display  the last 4 digits of SSN.

SQL> show user
USER is "ZAHIR"

SQL> create or replace view emp_v as
select ssn , 'xxx-xxx-'||substr(ssn,-4) formatted_ssn ,
ename from employee ; 

View created.

SQL> select * from emp_v where rownum < 5;

       SSN SSN_FORMATTE ENAME
---------- ------------ ------------------------------
 123456889 xxx-xxx-6889 ORA$BASE
 123456905 xxx-xxx-6905 DUAL
 123456906 xxx-xxx-6906 DUAL
 123457069 xxx-xxx-7069 MAP_OBJECT


SQL> exec DBMS_RLS.DROP_POLICY(object_schema=>user,object_name=>'EMPLOYEE' ,policy_name=>'SECURE_SSN') ;

PL/SQL procedure successfully completed.

SQL>
BEGIN
             DBMS_RLS.ADD_POLICY(object_schema=>user, object_name=>'EMP_V',
                                 policy_name=>'SECURE_SSN',
                                 function_schema=>user,
                                 policy_function=>'sec_data_pkg.mask_ssn',
                                 sec_relevant_cols=>'ssn',
                                 sec_relevant_cols_opt=>dbms_rls.ALL_ROWS);
       END;
       /
SQL> grant select on emp_v to scott;

Grant succeeded.


SQL> BEGIN
  2               DBMS_RLS.ADD_POLICY(object_schema=>user, object_name=>'EMP_V',
  3                                   policy_name=>'SECURE_SSN',
  4                                   function_schema=>user,
  5                                   policy_function=>'sec_data_pkg.mask_ssn',
  6                                   sec_relevant_cols=>'ssn',
  7                                   sec_relevant_cols_opt=>dbms_rls.ALL_ROWS);
  8         END;
  9         /

PL/SQL procedure successfully completed.

SQL> select * from zahir.emp_v where rownum < 5;

       SSN SSN_FORMATTE ENAME
---------- ------------ ------------------------------
 123456889 xxx-xxx-6889 ORA$BASE
 123456905 xxx-xxx-6905 DUAL
 123456906 xxx-xxx-6906 DUAL
 123457069 xxx-xxx-7069 MAP_OBJECT

SQL> conn scott/tiger
Connected.
SQL>  select * from zahir.emp_v where rownum < 5;

       SSN SSN_FORMATTE ENAME
---------- ------------ ------------------------------
           xxx-xxx-6889 ORA$BASE
           xxx-xxx-6905 DUAL
           xxx-xxx-6906 DUAL
           xxx-xxx-7069 MAP_OBJECT
 

SQL> select COALESCE (to_char(ssn) , formatted_ssn  ) ssn ,ename from zahir.emp_v where rownum < 5;

SSN                                      ENAME
---------------------------------------- ------------------------------
xxx-xxx-6889                             ORA$BASE
xxx-xxx-6905                             DUAL
xxx-xxx-6906                             DUAL
xxx-xxx-7069                             MAP_OBJECT