An example using the AES_ENCRYPT and AES_DECRYPT functions, written in commented SQL.
-- This example of aes_encrypt and aes_decrypt function use is presented in three parts:
--
-- Part I: Preliminary description of target tables and users as DDL
-- Part II: Example schema changes motivated by introduction of encryption
-- Part III: Use of views and stored procedures to protect encryption keys
--
-- Part I: Define target tables and users
-- Assume two classes of user, represented here by the instances
-- PrivUser and NonPrivUser, assigned to groups reflecting differing
-- privileges.
-- The initial state reflects the schema prior to the introduction
-- of encryption.
-- Set up the starting context: There are two tables with a common key.
-- Some columns contain sensitive data, the remaining columns do not.
-- The usual join column for these tables is sensitiveA.
-- There is a key and a unique index.
grant connect to PrivUser identified by 'verytrusted' ;
grant connect to NonPrivUser identified by 'lesstrusted' ;
grant connect to high_privileges_group ;
grant group to high_privileges_group ;
grant membership in group high_privileges_group to PrivUser ;
grant connect to low_privileges_group ;
grant group to low_privileges_group ;
grant membership in group low_privileges_group to NonPrivUser ;
create table DBA.first_table
(sensitiveA char(16) primary key
,sensitiveB numeric(10,0)
,publicC varchar(255)
,publicD date
) ;
-- There is an implicit unique HG (HighGroup) index enforcing the primary key.
create table second_table
(sensitiveA char(16)
,publicP integer
,publicQ tinyint
,publicR varchar(64)
) ;
create hg index second_A_HG on second_table ( sensitiveA ) ;
-- TRUSTED users can see the sensitive columns.
grant select ( sensitiveA, sensitiveB, publicC, publicD )
on DBA.first_table to PrivUser ;
grant select ( sensitiveA, publicP, publicQ, publicR )
on DBA.second_table to PrivUser ;
-- Non-TRUSTED users in existing schema need to see sensitiveA to be
-- able to do joins, even though they should not see sensitiveB.
grant select ( sensitiveA, publicC, publicD )
on DBA.first_table to NonPrivUser ;
grant select ( sensitiveA, publicP, publicQ, publicR )
on DBA.second_table to NonPrivUser ;
-- Non-TRUSTED users can execute queries such as
select I.publicC, 3*II.publicQ+1
from DBA.first_table I, DBA.second_table II
where I.sensitiveA = II.sensitiveA and I.publicD IN ( '2006-01-11' ) ;
-- and
select count(*)
from DBA.first_table I, DBA.second_table II
where I.sensitiveA = II.sensitiveA and SUBSTR(I.sensitiveA,4,3)
BETWEEN '345' AND '456' ;
-- But only TRUSTED users can execute the query
select I.sensitiveB, 3*II.publicQ+1
from DBA.first_table I, DBA.second_table II
where I.sensitiveA = II.sensitiveA and I.publicD IN ( '2006-01-11' ) ;
-- Part II: Change the schema in preparation for encryption
--
-- The DBA introduces encryption as follows:
--
-- For applicable tables, the DBA changes the schema, adjusts access
-- permissions, and updates existing data. The encryption
-- keys used are hidden in a subsequent step.
-- DataLength comparison for length of varbinary encryption result
-- (units are Bytes):
--
-- PlainText CipherText Corresponding Numeric Precisions
--
-- 0 16
-- 1 - 16 32 numeric(1,0) - numeric(20,0)
-- 17 - 32 48 numeric(21,0) - numeric(52,0)
-- 33 - 48 64 numeric(53,0) - numeric(84,0)
-- 49 - 64 80 numeric(85,0) - numeric(116,0)
-- 65 - 80 96 numeric(117,0) - numeric(128,0)
-- 81 - 96 112
-- 97 - 112 128
-- 113 - 128 144
-- 129 - 144 160
-- 145 - 160 176
-- 161 - 176 192
-- 177 - 192 208
-- 193 - 208 224
-- 209 - 224 240
-- The integer data types tinyint, small int, integer, and bigint
-- are varbinary(32) ciphertext.
-- The exact relationship is
-- DATALENGTH(ciphertext) =
-- (((DATALENGTH(plaintext)+ 15) / 16) + 1) * 16
-- For the first table, the DBA chooses to preserve both the plaintext and
-- ciphertext forms. This is not typical and should only be done if the
-- database files are also encrypted.
-- Take away NonPrivUser's access to column sensitiveA and transfer
-- access to the ciphertext version.
-- Put a unique index on the ciphertext column. The ciphertext
-- itself is indexed.
-- NonPrivUser can select the ciphertext and use it.
-- PrivUser can still select either form (without paying decrypt costs).
revoke select ( sensitiveA ) on DBA.first_table from NonPrivUser ;
alter table DBA.first_table add encryptedA varbinary(32) ;
grant select ( encryptedA ) on DBA.first_table to PrivUser ;
grant select ( encryptedA ) on DBA.first_table to NonPrivUser ;
create unique hg index first_A_unique on first_table ( encryptedA ) ;
update DBA.first_table
set encryptedA = aes_encrypt(sensitiveA, 'seCr3t')
where encryptedA is null ;
commit
-- Now change column sensitiveB.
alter table DBA.first_table add encryptedB varbinary(32) ;
grant select ( encryptedB ) on DBA.first_table to PrivUser ;
create unique hg index first_B_unique on first_table ( encryptedB ) ;
update DBA.first_table
set encryptedB = aes_encrypt(sensitiveB,
'givethiskeytonoone') where encryptedB is null ;
commit
-- For the second table, the DBA chooses to keep only the ciphertext.
-- This is more typical and encrypting the database files is not required.
revoke select ( sensitiveA ) on DBA.second_table from NonPrivUser ;
revoke select ( sensitiveA ) on DBA.second_table from PrivUser ;
alter table DBA.second_table add encryptedA varbinary(32) ;
grant select ( encryptedA ) on DBA.second_table to PrivUser ;
grant select ( encryptedA ) on DBA.second_table to NonPrivUser ;
create unique hg index second_A_unique on second_table ( encryptedA ) ;
update DBA.second_table
set encryptedA = aes_encrypt(sensitiveA, 'seCr3t')
where encryptedA is null ;
commit
alter table DBA.second_table drop sensitiveA ;
-- The following types of queries are permitted at this point, before
-- changes are made for key protection:
-- Non-TRUSTED users can equi-join on ciphertext; they can also select
-- the binary, but have no way to interpret it.
select I.publicC, 3*II.publicQ+1
from DBA.first_table I, DBA.second_table II
where I.encryptedA = II.encryptedA and I.publicD IN ( '2006-01-11' ) ;
-- Ciphertext-only access rules out general predicates and expressions.
-- The following query does not return meaningful results.
--
-- NOTE: These four predicates can be used on the varbinary containing
-- ciphertext:
-- = (equality)
-- <> (inequality)
-- IS NULL
-- IS NOT NULL
select count(*)
from DBA.first_table I, DBA.second_table II
where I.encryptedA = II.encryptedA and SUBSTR(I.encryptedA,4,3)
BETWEEN '345' AND '456' ;
-- The TRUSTED user still has access to the plaintext columns that
-- were retained. Therefore, this user does not need to call
-- aes_decrypt and does not need the key.
select count(*)
from DBA.first_table I, DBA.second_table II
where I.encryptedA = II.encryptedA and SUBSTR(I.sensitiveA,4,3)
BETWEEN '345' AND '456' ;
-- Part III: Protect the encryption keys
-- This section illustrates how to grant access to the plaintext, but
-- still protect the keys.
-- For the first table, the DBA elected to retain the plaintext columns.
-- Therefore, the following view has the same capabilities as the trusted
-- user above.
-- Assume group_member is being used for additional access control.
-- NOTE: In this example, NonPrivUser still has access to the ciphertext
-- encrypted in the base table.
create view DBA.a_first_view (sensitiveA, publicC, publicD)
as
select
IF group_member('high_privileges_group',user_name()) = 1
THEN sensitiveA
ELSE NULL
ENDIF,
publicC,
publicD
from first_table ;
grant select on DBA.a_first_view to PrivUser ;
grant select on DBA.a_first_view to NonPrivUser ;
-- For the second table, the DBA did not keep the plaintext.
-- Therefore, aes_decrypt calls must be used in the view.
-- IMPORTANT: Hide the view definition with ALTER VIEW, so that no one
-- can discover the key.
create view DBA.a_second_view (sensitiveA,publicP,publicQ,publicR)
as
select
IF group_member('high_privileges_group',user_name()) = 1
THEN aes_decrypt(encryptedA,'seCr3t', char(16))
ELSE NULL
ENDIF,
publicP,
publicQ,
publicR
from second_table ;
alter view DBA.a_second_view set hidden ;
grant select on DBA.a_second_view to PrivUser ;
grant select on DBA.a_second_view to NonPrivUser ;
-- Likewise, the key used for loading can be protected in a stored procedure.
-- By hiding the procedure (just as the view is hidden), no-one can see
-- the keys.
create procedure load_first_proc(@inputFileName varchar(255),
@colDelim varchar(4) default '$',
@rowDelim varchar(4) default '\n')
begin
execute immediate with quotes
'load table DBA.second_table
(encryptedA encrypted(char(16),' ||
'''' || 'seCr3t' || '''' || '),publicP,publicQ,publicR) ' ||
' from ' || '''' || @inputFileName || '''' ||
' delimited by ' || '''' || @colDelim || '''' ||
' row delimited by ' || '''' || @rowDelim || '''' ||
' quotes off escapes off' ;
end
;
alter procedure DBA.load_first_proc set hidden ;
-- Call the load procedure using the following syntax:
call load_first_proc('/dev/null', '$', '\n') ;
-- Below is a comparison of several techniques for protecting the
-- encryption keys by using user-defined functions (UDFs), other views,
-- or both. The first and the last alternatives offer maximum performance.
-- The second_table is secured as defined earlier.
-- Alternative 1:
-- This baseline approach relies on restricting access to the entire view.
create view
DBA.second_baseline_view(sensitiveA,publicP,publicQ,publicR)
as
select
IF group_member('high_privileges_group',user_name()) = 1
THEN aes_decrypt(encryptedA,'seCr3t', char(16))
ELSE NULL
ENDIF,
publicP,
publicQ,
publicR
from DBA.second_table ;
alter view DBA.second_baseline_view set hidden ;
grant select on DBA.second_baseline_view to NonPrivUser ;
grant select on DBA.second_baseline_view to PrivUser ;
-- Alternative 2:
-- Place the encryption function invocation within a user-defined
-- function (UDF).
-- Hide the definition of the UDF. Restrict the UDF permissions.
-- Use the UDF in a view that handles the remainder of the security
-- and business logic.
-- Note: The view itself does not need to be hidden.
create function DBA.second_decrypt_function(IN datum varbinary(32))
RETURNS char(16) DETERMINISTIC
BEGIN
RETURN aes_decrypt(datum,'seCr3t', char(16));
END ;
grant execute on DBA.second_decrypt_function to PrivUser ;
alter function DBA.second_decrypt_function set hidden ;
create view
DBA.second_decrypt_view(sensitiveA,publicP,publicQ,publicR)
as
select
IF group_member('high_privileges_group',user_name()) = 1
THEN second_decrypt_function(encryptedA)
ELSE NULL
ENDIF,
publicP,
publicQ,
publicR
from DBA.second_table ;
grant select on DBA.second_decrypt_view to NonPrivUser ;
grant select on DBA.second_decrypt_view to PrivUser ;
-- Alternative 3:
-- Sequester only the key selection in a user-defined function.
-- This function could be extended to support selection of any
-- number of keys.
-- This UDF is also hidden and has restricted execute privileges.
-- Note: Any view that uses this UDF therefore does not compromise
-- the key values.
create function DBA.second_key_function()
RETURNS varchar(32) DETERMINISTIC
BEGIN
return 'seCr3t' ;
END
grant execute on DBA.second_key_function to PrivUser ;
alter function DBA.second_key_function set hidden ;
create view DBA.second_key_view(sensitiveA,publicP,publicQ,publicR)
as
select
IF group_member('high_privileges_group',user_name()) = 1
THEN aes_decrypt(encryptedA,second_key_function(),
char(16))
ELSE NULL
ENDIF,
publicP,
publicQ,
publicR
from DBA.second_table ;
grant select on DBA.second_key_view to NonPrivUser ;
grant select on DBA.second_key_view to PrivUser ;
-- Alternative 4:
-- The recommended alternative is to separate the security logic
-- from the business logic by dividing the concerns into two views.
-- Only the security logic view needs to be hidden.
-- Note: The performance of this approach is similar to that of the first
-- alternative.
create view
DBA.second_SecurityLogic_view(sensitiveA,publicP,publicQ,publicR)
as
select
IF group_member('high_privileges_group',user_name()) = 1
THEN aes_decrypt(encryptedA,'seCr3t', char(16))
ELSE NULL
ENDIF,
publicP,
publicQ,
publicR
from DBA.second_table ;
alter view DBA.second_SecurityLogic_view set hidden ;
create view
DBA.second_BusinessLogic_view(sensitiveA,publicP,publicQ,publicR)
as
select
sensitiveA,
publicP,
publicQ,
publicR
from DBA.second_SecurityLogic_view ;
grant select on DBA.second_BusinessLogic_view to NonPrivUser ;
grant select on DBA.second_BusinessLogic_view to PrivUser ;
-- End of encryption example