Oracle FAQ Your Portal to the Oracle Knowledge Grid
HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US
 

Home -> Community -> Mailing Lists -> Oracle-L -> RE: using obfuscation

RE: using obfuscation

From: Aponte, Tony <AponteT_at_hsn.net>
Date: Tue, 17 Sep 2002 16:08:19 -0800
Message-ID: <F001.004D1E3B.20020917160819@fatcity.com>


Content-Type: text/plain;
 charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

I've been developing a solution for a similar requirement. Although I = reached a dead-end with this thread I think it solves your problem.

I'm picking it up from the point where the data in encrypted_data of = sensitive_table needs encryption. I did that with an anonymous PL/SQL = block:

CREATE TABLE sensitive_table (encrypted_data VARCHAR2(30), clear_text = VARCHAR2(30)); I'll populate the table with text 16 characters long. I used 16 to = simplify the example. DBMS_OBFUSCATION_TOOLKIT needs the data length to = be a multiple of 8:

INSERT INTO sensitive_table=20
SELECT =
TO_CHAR(ROWNUM,'fm0000000000000009'),TO_CHAR(ROWNUM,'fm0000000000000009')=  FROM DBA_OBJECTS WHERE ROWNUM < 1000;

COMMIT; Now run the anonymous block to encrypt the data in the encrypted_data = column:

--Encrypt data
DECLARE
CURSOR xtab IS SELECT encrypted_data FROM sensitive_table FOR UPDATE;

input_string        VARCHAR2(16) ;
raw_input           RAW(400) ;
key_string          VARCHAR2(8):=3D 'abcde123';
raw_key             RAW(400) :=3D UTL_RAW.CAST_TO_RAW(key_string);
encrypted_raw       RAW(2048);

error_in_input_buffer_length EXCEPTION;
PRAGMA EXCEPTION_INIT(error_in_input_buffer_length, -28232); INPUT_BUFFER_LENGTH_ERR_MSG VARCHAR2(100) :=3D     '*** DES INPUT BUFFER NOT A MULTIPLE OF 8 BYTES - IGNORING EXCEPTION = ***';
double_encrypt_not_permitted EXCEPTION;
PRAGMA EXCEPTION_INIT(double_encrypt_not_permitted, -28233); DOUBLE_ENCRYPTION_ERR_MSG VARCHAR2(100) :=3D     '*** CANNOT DOUBLE ENCRYPT DATA - IGNORING EXCEPTION ***'; BEGIN
FOR xrec IN xtab LOOP
	input_string:=3Dxrec.encrypted_data;
	raw_input:=3D UTL_RAW.CAST_TO_RAW(input_string);
	DBMS_OBFUSCATION_TOOLKIT.DESENCRYPT(input =3D> raw_input,key =3D> =
raw_key, encrypted_data =3D> encrypted_raw );

    UPDATE sensitive_table=20

	   SET encrypted_data =3D UTL_RAW.CAST_TO_VARCHAR2(encrypted_raw)
	 WHERE CURRENT OF xtab;=20

END LOOP;
COMMIT;
EXCEPTION
  WHEN error_in_input_buffer_length THEN

       dbms_output.put_line('> ' || INPUT_BUFFER_LENGTH_ERR_MSG); END;
/

I want to create a package (spec only) to contain global variables to be = used by my encrypt/decrypt functions:

CREATE OR REPLACE PACKAGE cc_security_pkg IS

g_KeyString_txt	    VARCHAR2(8):=3D 'abcde123';
g_Key_raw           RAW(400) :=3D UTL_RAW.CAST_TO_RAW(g_KeyString_txt);
g_Input_raw         RAW(400) ;
g_Decrypted_raw     RAW(2048);
g_Encrypted_raw     RAW(2048);
g_ErrorInInputBufferLength_exc EXCEPTION;
PRAGMA EXCEPTION_INIT(g_ErrorInInputBufferLength_exc, -28232); g_InputBufferLengthErrMsg_txt VARCHAR2(100) :=3D

    '*** DES INPUT BUFFER NOT A MULTIPLE OF 8 BYTES - IGNORING EXCEPTION = ***';
g_DoubleEncrypt_exc EXCEPTION;
PRAGMA EXCEPTION_INIT(g_DoubleEncrypt_exc, -28233); g_DoubleEncryptionErrMsg_txt VARCHAR2(100) :=3D

    '*** CANNOT DOUBLE ENCRYPT DATA - IGNORING EXCEPTION ***';

--
END;

/
Here is the decrypt function. Not much to explain: CREATE OR REPLACE cc_decrypt(p_Input_txt VARCHAR2) RETURN VARCHAR2 IS BEGIN DBMS_OBFUSCATION_TOOLKIT.DESDECRYPT(INPUT =3D> = UTL_RAW.CAST_TO_RAW(p_Input_txt) ,KEY =3D> cc_security_pkg.g_Key_raw ,DECRYPTED_DATA =3D> cc_security_pkg.g_Decrypted_raw ); RETURN(UTL_RAW.CAST_TO_VARCHAR2(cc_security_pkg.g_Decrypted_raw)); END cc_decrypt;
/
This is the corresponding encryption function. Two things to note, the = use of the DETERMINISTIC pragma and the conflicting use of the value of = CLIENT_INFO to determine the return value (encrypted or not.) I am = deliberately (mis?)using DETERMINISTIC to trick Oracle into trusting = that I will always return the same value for p_Input_txt. I'll explain = why later on when I get to the function-based index. But for now all = you need to know is that when the value of CLIENT_INFO equals = 'buildingindex' I simply return the original value passed to the = function. Otherwise the parameter is decrypted and returned: CREATE OR REPLACE FUNCTION cc_encrypt(p_Input_txt VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS BEGIN IF USERENV('CLIENT_INFO') =3D 'buildingindex' THEN RETURN(p_Input_txt); ELSE DBMS_OBFUSCATION_TOOLKIT.DESENCRYPT(INPUT =3D> = UTL_RAW.CAST_TO_RAW(p_Input_txt) ,KEY =3D> cc_security_pkg.g_Key_raw ,ENCRYPTED_DATA =3D> cc_security_pkg.g_Encrypted_raw ); RETURN(UTL_RAW.CAST_TO_VARCHAR2(cc_security_pkg.g_Encrypted_raw)); END IF; END cc_encrypt;
/
Now I want to be able to use and index when the query includes = sensitive_table.encrypteed_data in the predicate. I believe this is = where you are stuck. I want to execute this query that doesn't require = pre-encryption of 0000000000000010: SELECT cc_decrypt(encrypted_data),clear_text FROM sensitive_table WHERE = encrypted_data =3D cc_encrypt('0000000000000010'); But the use of a function (cc_encrypt in this case) causes the optimizer = to ignore an index on encrypted_data. This is where the function-based = index comes in. I will build the index on cc_encrypt(encrypted_data). = But you say "this will cause double-encryption and it will bomb out!" = With that though in mind it should be clear why I coded the cc_encrypt = function to break the trust given to me by the use of the DETERMINISTIC = pragma. I need to build the index using cc_encrypt so that my query can = use it. But I need it to do nothing when building the index because the = data is already encrypted. To do my trickery I will use = DMBS_APPLICATION_INFO to set a magic value in my session context that is = used in the IF statement of cc_encrypt. =20 --Set up session context for building the index BEGIN DBMS_APPLICATION_INFO.SET_CLIENT_INFO('buildingindex'); DBMS_OUTPUT.PUT_LINE(USERENV('CLIENT_INFO')); END;
/
I'm know ready to create the index. I learned from Expert One-on-One = Oracle that the index column length needs to be constrained. See the = section on function-based indexes: CREATE INDEX encrypted_data_idx ON=20 sensitive_table(SUBSTR(cc_encrypt(encrypted_data),1,16)); I'll reset CLIENT_INFO so that cc_encrypt does it's normal work: =20 BEGIN DBMS_APPLICATION_INFO.SET_CLIENT_INFO(''); DBMS_OUTPUT.PUT_LINE(USERENV('CLIENT_INFO')); END;
/
Function-based indexes have certain requirements that you will need to = bone-up on: ALTER SESSION SET QUERY_REWRITE_ENABLED=3DTRUE; ALTER SESSION SET QUERY_REWRITE_INTEGRITY=3DTRUSTED; ALTER SESSION SET OPTIMIZER_GOAL=3DFIRST_ROWS; ANALYZE INDEX encrypted_data_idx COMPUTE STATISTICS; Ok. Now I'm ready to test the use of the index. =20 SELECT cc_decrypt(encrypted_data),clear_text FROM sensitive_table WHERE = SUBSTR(cc_encrypt(encrypted_data),1,16) =3D = cc_encrypt('0000000000000010'); Operation Object Name Rows Bytes Cost TQ In/Out PStart PStop SELECT STATEMENT 6 1 =20 TABLE ACCESS BY INDEX ROWID SENSITIVE_TABLE 6 204 1 = =20 INDEX RANGE SCAN ENCRYPTED_DATA_IDX 6 1 = =20 My statement is too complicated so I will hide some of the complexity = behind a view: CREATE OR REPLACE VIEW exposed_view=20 AS SELECT SUBSTR(cc_encrypt(encrypted_data),1,16) encrypted_data, = clear_text FROM sensitive_table; Here it is a little cleaner: SELECT cc_decrypt(encrypted_data),clear_text FROM exposed_view WHERE = encrypted_data =3D cc_encrypt('0000000000000010'); Operation Object Name Rows Bytes Cost TQ In/Out PStart PStop SELECT STATEMENT 6 1 =20 TABLE ACCESS BY INDEX ROWID SENSITIVE_TABLE 6 204 1 = =20 INDEX RANGE SCAN ENCRYPTED_DATA_IDX 6 1 = =20 A little more cleansing: CREATE OR REPLACE VIEW final_view AS SELECT cc_decrypt(encrypted_data) = decrypted_data,encrypted_data,clear_text FROM exposed_view; SELECT decrypted_data,clear_text FROM final_view WHERE encrypted_data = =3D cc_encrypt('0000000000000010'); Operation Object Name Rows Bytes Cost TQ In/Out PStart PStop SELECT STATEMENT 6 1 =20 TABLE ACCESS BY INDEX ROWID SENSITIVE_TABLE 6 204 1 = =20 INDEX RANGE SCAN ENCRYPTED_DATA_IDX 6 1 = =20 Tada! Encrypted data without sacrificing speed and security. Actually, = it's not ready for prime time. You need to handle insert and update = operations on the table and column (a before .. for each row trigger = should work, or maybe an instead-of trigger.) You also need certain = privileges to create function-based indexes. I haven't considered the = implications for index rebuilds. And finally, you're going to have to = deal with the purists that will choke on the violation of the = DETERMINISTIC requirement to return the same value for a given input. I wasn't able to make use of this method but I hope someone gets some = value out of my work. I welcome any feedback from you or anyone else = that tries it. Tony Aponte -----Original Message----- Sent: Monday, September 09, 2002 5:43 PM To: Multiple recipients of list ORACLE-L Hi all, I have downloaded the Metalink Notes on implementing dbms_obfuscation. I = am using multiple front ends on the database, so the way I plan to = implement the de-encryption is with a de-encrypt function in a view.=20 Create View my_data AS Select de_encrypt(sensitive_data) AS sensitive_data ,other_data FROM original_table ; If I select from the view with a where clause on other_data, the = response time is fine. If I select from the view with a where clause on sensitive_data, I do a full table scan and which takes about 15 minutes. The de-encrypt function is copied from a Metalink note, nothing fancy.=20 Since I have various front ends, I can not de-encrypt the data in the = front end. The only way I can think of is with the function in a view, but = the response time is unacceptable. Does anyone have any thoughts on this? Thanks Randy --=20 Please see the official ORACLE-L FAQ: http://www.orafaq.com --=20 Author: Steiner, Randy INET: RASTEIN_at_NYCT.com Fat City Network Services -- (858) 538-5051 FAX: (858) 538-5051 San Diego, California -- Public Internet access / Mailing Lists -------------------------------------------------------------------- To REMOVE yourself from this mailing list, send an E-Mail message to: ListGuru_at_fatcity.com (note EXACT spelling of 'ListGuru') and in the message BODY, include a line containing: UNSUB ORACLE-L (or the name of mailing list you want to be removed from). You may also send the HELP command for other information (like subscribing). ------_=_NextPart_001_01C25E9E.91DD5D04 Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"> <HTML> <HEAD> <META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; = charset=3Diso-8859-1"> <META NAME=3D"Generator" CONTENT=3D"MS Exchange Server version = 6.0.6249.1"> <TITLE>RE: using obfuscation</TITLE> </HEAD> <BODY> <!-- Converted from text/plain format --> <P><FONT SIZE=3D2>I've been developing a solution for a similar = requirement.&nbsp; Although I reached a dead-end with this thread I = think it solves your problem.</FONT></P> <P><FONT SIZE=3D2>I'm picking it up from the point where the data in = encrypted_data of sensitive_table needs encryption. I did that with an = anonymous PL/SQL block:</FONT></P> <P><FONT SIZE=3D2>CREATE TABLE sensitive_table (encrypted_data = VARCHAR2(30), clear_text VARCHAR2(30));</FONT> </P> <P><FONT SIZE=3D2>I'll populate the table with text 16 characters = long.&nbsp; I used 16 to simplify the example.&nbsp; = DBMS_OBFUSCATION_TOOLKIT needs the data length to be a multiple of = 8:</FONT></P> <P><FONT SIZE=3D2>INSERT INTO sensitive_table </FONT> <BR><FONT SIZE=3D2>SELECT = TO_CHAR(ROWNUM,'fm0000000000000009'),TO_CHAR(ROWNUM,'fm0000000000000009')= FROM DBA_OBJECTS WHERE ROWNUM &lt; 1000;</FONT> </P> <P><FONT SIZE=3D2>COMMIT;</FONT> </P> <P><FONT SIZE=3D2>Now run the anonymous block to encrypt the data in the = encrypted_data column:</FONT> </P> <P><FONT SIZE=3D2>--Encrypt data</FONT> <BR><FONT SIZE=3D2>DECLARE</FONT> <BR><FONT SIZE=3D2>CURSOR xtab IS SELECT encrypted_data FROM = sensitive_table FOR UPDATE;</FONT> <BR><FONT = SIZE=3D2>input_string&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = VARCHAR2(16) ;</FONT> <BR><FONT = SIZE=3D2>raw_input&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&= nbsp; RAW(400) ;</FONT> <BR><FONT = SIZE=3D2>key_string&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= VARCHAR2(8):=3D 'abcde123';</FONT> <BR><FONT = SIZE=3D2>raw_key&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb= sp;&nbsp;&nbsp; RAW(400) :=3D UTL_RAW.CAST_TO_RAW(key_string);</FONT> <BR><FONT SIZE=3D2>encrypted_raw&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = RAW(2048);</FONT> <BR><FONT SIZE=3D2>error_in_input_buffer_length EXCEPTION;</FONT> <BR><FONT SIZE=3D2>PRAGMA EXCEPTION_INIT(error_in_input_buffer_length, = -28232);</FONT> <BR><FONT SIZE=3D2>INPUT_BUFFER_LENGTH_ERR_MSG VARCHAR2(100) :=3D</FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; '*** DES INPUT BUFFER NOT A = MULTIPLE OF 8 BYTES - IGNORING EXCEPTION ***';</FONT> <BR><FONT SIZE=3D2>double_encrypt_not_permitted EXCEPTION;</FONT> <BR><FONT SIZE=3D2>PRAGMA EXCEPTION_INIT(double_encrypt_not_permitted, = -28233);</FONT> <BR><FONT SIZE=3D2>DOUBLE_ENCRYPTION_ERR_MSG VARCHAR2(100) :=3D</FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; '*** CANNOT DOUBLE ENCRYPT DATA - = IGNORING EXCEPTION ***';</FONT> <BR><FONT SIZE=3D2>BEGIN</FONT> <BR><FONT SIZE=3D2>FOR xrec IN xtab LOOP</FONT> <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT = SIZE=3D2>input_string:=3Dxrec.encrypted_data;</FONT> <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT = SIZE=3D2>raw_input:=3D UTL_RAW.CAST_TO_RAW(input_string);</FONT> <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT = SIZE=3D2>DBMS_OBFUSCATION_TOOLKIT.DESENCRYPT(input =3D&gt; raw_input,key = =3D&gt; raw_key, encrypted_data =3D&gt; encrypted_raw );</FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; UPDATE sensitive_table </FONT> <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT = SIZE=3D2>&nbsp;&nbsp; SET encrypted_data =3D = UTL_RAW.CAST_TO_VARCHAR2(encrypted_raw)</FONT> <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT SIZE=3D2> = WHERE CURRENT OF xtab; </FONT> <BR><FONT SIZE=3D2>END LOOP;</FONT> <BR><FONT SIZE=3D2>COMMIT;</FONT> <BR><FONT SIZE=3D2>EXCEPTION</FONT> <BR><FONT SIZE=3D2>&nbsp; WHEN error_in_input_buffer_length THEN</FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = dbms_output.put_line('&gt; ' || INPUT_BUFFER_LENGTH_ERR_MSG);</FONT> <BR><FONT SIZE=3D2>END;</FONT> <BR><FONT SIZE=3D2>/</FONT> </P> <P><FONT SIZE=3D2>I want to create a package (spec only) to contain = global variables to be used by my encrypt/decrypt functions:</FONT> </P> <P><FONT SIZE=3D2>CREATE OR REPLACE PACKAGE cc_security_pkg</FONT> <BR><FONT SIZE=3D2>IS</FONT> <BR><FONT SIZE=3D2>g_KeyString_txt &nbsp;&nbsp;&nbsp; VARCHAR2(8):=3D = 'abcde123';</FONT> <BR><FONT = SIZE=3D2>g_Key_raw&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&= nbsp; RAW(400) :=3D UTL_RAW.CAST_TO_RAW(g_KeyString_txt);</FONT> <BR><FONT = SIZE=3D2>g_Input_raw&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = RAW(400) ;</FONT> <BR><FONT SIZE=3D2>g_Decrypted_raw&nbsp;&nbsp;&nbsp;&nbsp; = RAW(2048);</FONT> <BR><FONT SIZE=3D2>g_Encrypted_raw&nbsp;&nbsp;&nbsp;&nbsp; = RAW(2048);</FONT> <BR><FONT SIZE=3D2>g_ErrorInInputBufferLength_exc EXCEPTION;</FONT> <BR><FONT SIZE=3D2>PRAGMA EXCEPTION_INIT(g_ErrorInInputBufferLength_exc, = -28232);</FONT> <BR><FONT SIZE=3D2>g_InputBufferLengthErrMsg_txt VARCHAR2(100) = :=3D</FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; '*** DES INPUT BUFFER NOT A = MULTIPLE OF 8 BYTES - IGNORING EXCEPTION ***';</FONT> <BR><FONT SIZE=3D2>g_DoubleEncrypt_exc EXCEPTION;</FONT> <BR><FONT SIZE=3D2>PRAGMA EXCEPTION_INIT(g_DoubleEncrypt_exc, = -28233);</FONT> <BR><FONT SIZE=3D2>g_DoubleEncryptionErrMsg_txt VARCHAR2(100) = :=3D</FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; '*** CANNOT DOUBLE ENCRYPT DATA - = IGNORING EXCEPTION ***';</FONT> <BR><FONT SIZE=3D2>--</FONT> <BR><FONT SIZE=3D2>END;</FONT> <BR><FONT SIZE=3D2>/</FONT> </P> <P><FONT SIZE=3D2>Here is the decrypt function.&nbsp; Not much to = explain:</FONT> </P> <P><FONT SIZE=3D2>CREATE OR REPLACE cc_decrypt(p_Input_txt = VARCHAR2)</FONT> <BR><FONT SIZE=3D2>RETURN VARCHAR2</FONT> <BR><FONT SIZE=3D2>IS</FONT> <BR><FONT SIZE=3D2>BEGIN</FONT> <BR><FONT SIZE=3D2>&nbsp; DBMS_OBFUSCATION_TOOLKIT.DESDECRYPT(INPUT = =3D&gt; UTL_RAW.CAST_TO_RAW(p_Input_txt)</FONT> <BR><FONT SIZE=3D2>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ,KEY =3D&gt; = cc_security_pkg.g_Key_raw</FONT> <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT SIZE=3D2> = ,DECRYPTED_DATA =3D&gt; cc_security_pkg.g_Decrypted_raw );</FONT></P> <P><FONT SIZE=3D2>&nbsp; = RETURN(UTL_RAW.CAST_TO_VARCHAR2(cc_security_pkg.g_Decrypted_raw));</FONT>= <BR><FONT SIZE=3D2>END cc_decrypt;</FONT> <BR><FONT SIZE=3D2>/</FONT> </P> <P><FONT SIZE=3D2>This is the corresponding encryption function.&nbsp; = Two things to note, the use of the DETERMINISTIC pragma and the = conflicting use of the value of CLIENT_INFO to determine the return = value (encrypted or not.)&nbsp; I am deliberately (mis?)using = DETERMINISTIC to trick Oracle into trusting that I will always return = the same value for p_Input_txt.&nbsp; I'll explain why later on when I = get to the function-based index.&nbsp; But for now all you need to know = is that when the value of CLIENT_INFO equals 'buildingindex' I simply = return the original value passed to the function.&nbsp; Otherwise the = parameter is decrypted and returned:</FONT></P> <P><FONT SIZE=3D2>CREATE OR REPLACE FUNCTION cc_encrypt(p_Input_txt = VARCHAR2)</FONT> <BR><FONT SIZE=3D2>RETURN VARCHAR2</FONT> <BR><FONT SIZE=3D2>DETERMINISTIC</FONT> <BR><FONT SIZE=3D2>IS</FONT> <BR><FONT SIZE=3D2>BEGIN</FONT> <BR><FONT SIZE=3D2>&nbsp; IF USERENV('CLIENT_INFO') =3D 'buildingindex' = THEN</FONT> <BR><FONT SIZE=3D2>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = RETURN(p_Input_txt);</FONT> <BR><FONT SIZE=3D2>&nbsp; ELSE</FONT> <BR><FONT SIZE=3D2>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = DBMS_OBFUSCATION_TOOLKIT.DESENCRYPT(INPUT =3D&gt; = UTL_RAW.CAST_TO_RAW(p_Input_txt)</FONT> <BR><FONT SIZE=3D2>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ,KEY =3D&gt; = cc_security_pkg.g_Key_raw</FONT> <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT SIZE=3D2> = ,ENCRYPTED_DATA =3D&gt; cc_security_pkg.g_Encrypted_raw );</FONT></P> <P><FONT SIZE=3D2>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = RETURN(UTL_RAW.CAST_TO_VARCHAR2(cc_security_pkg.g_Encrypted_raw));</FONT>= <BR><FONT SIZE=3D2>&nbsp; END IF;</FONT> <BR><FONT SIZE=3D2>END cc_encrypt;</FONT> <BR><FONT SIZE=3D2>/</FONT> </P> <P><FONT SIZE=3D2>Now I want to be able to use and index when the query = includes sensitive_table.encrypteed_data in the predicate.&nbsp; I = believe this is where you are stuck.&nbsp; I want to execute this query = that doesn't require pre-encryption of 0000000000000010:</FONT></P> <P><FONT SIZE=3D2>SELECT cc_decrypt(encrypted_data),clear_text FROM = sensitive_table WHERE encrypted_data =3D = cc_encrypt('0000000000000010');</FONT></P> <P><FONT SIZE=3D2>But the use of a function (cc_encrypt in this case) = causes the optimizer to ignore an index on encrypted_data.&nbsp; This is = where the function-based index comes in.&nbsp; I will build the index on = cc_encrypt(encrypted_data).&nbsp; But you say &quot;this will cause = double-encryption and it will bomb out!&quot;&nbsp; With that though in = mind it should be clear why I coded the cc_encrypt function to break the = trust given to me by the use of the DETERMINISTIC pragma.&nbsp; I need = to build the index using cc_encrypt so that my query can use it. But I = need it to do nothing when building the index because the data is = already encrypted.&nbsp; To do my trickery I will use = DMBS_APPLICATION_INFO to set a magic value in my session context that is = used in the IF statement of cc_encrypt.&nbsp; </FONT></P> <P><FONT SIZE=3D2>--Set up session context for building the index</FONT> <BR><FONT SIZE=3D2>BEGIN</FONT> <BR><FONT = SIZE=3D2>DBMS_APPLICATION_INFO.SET_CLIENT_INFO('buildingindex');</FONT> <BR><FONT SIZE=3D2>DBMS_OUTPUT.PUT_LINE(USERENV('CLIENT_INFO'));</FONT> <BR><FONT SIZE=3D2>END;</FONT> <BR><FONT SIZE=3D2>/</FONT> </P> <P><FONT SIZE=3D2>I'm know ready to create the index.&nbsp; I learned = from Expert One-on-One Oracle that the index column length needs to be = constrained.&nbsp; See the section on function-based indexes:</FONT></P> <P><FONT SIZE=3D2>CREATE INDEX encrypted_data_idx ON </FONT> <BR><FONT SIZE=3D2>&nbsp; = sensitive_table(SUBSTR(cc_encrypt(encrypted_data),1,16));</FONT> </P> <P><FONT SIZE=3D2>I'll reset CLIENT_INFO so that cc_encrypt does it's = normal work:</FONT> <BR><FONT SIZE=3D2>&nbsp; </FONT> <BR><FONT SIZE=3D2>BEGIN</FONT> <BR><FONT SIZE=3D2>DBMS_APPLICATION_INFO.SET_CLIENT_INFO('');</FONT> <BR><FONT SIZE=3D2>DBMS_OUTPUT.PUT_LINE(USERENV('CLIENT_INFO'));</FONT> <BR><FONT SIZE=3D2>END;</FONT> <BR><FONT SIZE=3D2>/</FONT> </P> <P><FONT SIZE=3D2>Function-based indexes have certain requirements that = you will need to bone-up on:</FONT> </P> <P><FONT SIZE=3D2>ALTER SESSION SET QUERY_REWRITE_ENABLED=3DTRUE;</FONT> <BR><FONT SIZE=3D2>ALTER SESSION SET = QUERY_REWRITE_INTEGRITY=3DTRUSTED;</FONT> <BR><FONT SIZE=3D2>ALTER SESSION SET OPTIMIZER_GOAL=3DFIRST_ROWS;</FONT> <BR><FONT SIZE=3D2>ANALYZE INDEX encrypted_data_idx COMPUTE = STATISTICS;</FONT> </P> <P><FONT SIZE=3D2>Ok.&nbsp; Now I'm ready to test the use of the = index.&nbsp; </FONT> </P> <P><FONT SIZE=3D2>SELECT cc_decrypt(encrypted_data),clear_text FROM = sensitive_table WHERE SUBSTR(cc_encrypt(encrypted_data),1,16) =3D = cc_encrypt('0000000000000010');</FONT></P> <P><FONT SIZE=3D2>Operation&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Object = Name&nbsp;&nbsp;&nbsp;&nbsp; Rows&nbsp;&nbsp;&nbsp; Bytes&nbsp;&nbsp; = Cost&nbsp;&nbsp;&nbsp; TQ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; In/Out&nbsp; = PStart&nbsp; PStop</FONT> </P> <P><FONT SIZE=3D2>SELECT = STATEMENT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> <BR><FONT SIZE=3D2>&nbsp; TABLE ACCESS BY INDEX ROWID&nbsp;&nbsp; = SENSITIVE_TABLE 6&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 204&nbsp; &nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; INDEX RANGE SCAN&nbsp;&nbsp;&nbsp; = ENCRYPTED_DATA_IDX&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> </P> <P><FONT SIZE=3D2>My statement is too complicated so I will hide some of = the complexity behind a view:</FONT> </P> <P><FONT SIZE=3D2>CREATE OR REPLACE VIEW exposed_view </FONT> <BR><FONT SIZE=3D2>AS</FONT> <BR><FONT SIZE=3D2>SELECT SUBSTR(cc_encrypt(encrypted_data),1,16) = encrypted_data, clear_text FROM sensitive_table;</FONT> </P> <P><FONT SIZE=3D2>Here it is a little cleaner:</FONT> </P> <P><FONT SIZE=3D2>SELECT cc_decrypt(encrypted_data),clear_text FROM = exposed_view WHERE encrypted_data =3D = cc_encrypt('0000000000000010');</FONT> </P> <P><FONT SIZE=3D2>Operation&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Object = Name&nbsp;&nbsp;&nbsp;&nbsp; Rows&nbsp;&nbsp;&nbsp; Bytes&nbsp;&nbsp; = Cost&nbsp;&nbsp;&nbsp; TQ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; In/Out&nbsp; = PStart&nbsp; PStop</FONT> </P> <P><FONT SIZE=3D2>SELECT = STATEMENT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> <BR><FONT SIZE=3D2>&nbsp; TABLE ACCESS BY INDEX ROWID&nbsp;&nbsp; = SENSITIVE_TABLE 6&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 204&nbsp; &nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; INDEX RANGE SCAN&nbsp;&nbsp;&nbsp; = ENCRYPTED_DATA_IDX&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> </P> <P><FONT SIZE=3D2>A little more cleansing:</FONT> </P> <P><FONT SIZE=3D2>CREATE OR REPLACE VIEW final_view</FONT> <BR><FONT SIZE=3D2>AS</FONT> <BR><FONT SIZE=3D2>SELECT cc_decrypt(encrypted_data) = decrypted_data,encrypted_data,clear_text FROM exposed_view;</FONT> </P> <P><FONT SIZE=3D2>SELECT decrypted_data,clear_text FROM final_view WHERE = encrypted_data =3D cc_encrypt('0000000000000010');</FONT> <BR><FONT SIZE=3D2>Operation&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Object = Name&nbsp;&nbsp;&nbsp;&nbsp; Rows&nbsp;&nbsp;&nbsp; Bytes&nbsp;&nbsp; = Cost&nbsp;&nbsp;&nbsp; TQ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; In/Out&nbsp; = PStart&nbsp; PStop</FONT> </P> <P><FONT SIZE=3D2>SELECT = STATEMENT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> <BR><FONT SIZE=3D2>&nbsp; TABLE ACCESS BY INDEX ROWID&nbsp;&nbsp; = SENSITIVE_TABLE 6&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 204&nbsp; &nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> <BR><FONT SIZE=3D2>&nbsp;&nbsp;&nbsp; INDEX RANGE SCAN&nbsp;&nbsp;&nbsp; = ENCRYPTED_DATA_IDX&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp; = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &nbsp;&nbsp;&nbsp; </FONT> </P> <BR> <P><FONT SIZE=3D2>Tada!&nbsp; Encrypted data without sacrificing speed = and security.&nbsp; Actually, it's not ready for prime time.&nbsp; You = need to handle insert and update operations on the table and column (a = before .. for each row trigger should work, or maybe an instead-of = trigger.)&nbsp; You also need certain privileges to create = function-based indexes.&nbsp; I haven't considered the implications for = index rebuilds.&nbsp; And finally, you're going to have to deal with the = purists that will choke on the violation of the DETERMINISTIC = requirement to return the same value for a given input.</FONT></P> <P><FONT SIZE=3D2>I wasn't able to make use of this method but I hope = someone gets some value out of my work.&nbsp; I welcome any feedback = from you or anyone else that tries it.</FONT></P> <P><FONT SIZE=3D2>Tony Aponte</FONT> </P> <P><FONT SIZE=3D2>-----Original Message-----</FONT> <BR><FONT SIZE=3D2>From: Steiner, Randy [<A = HREF=3D"mailto:RASTEIN_at_NYCT.com">mailto:RASTEIN_at_NYCT.com</A>]</FONT> <BR><FONT SIZE=3D2>Sent: Monday, September 09, 2002 5:43 PM</FONT> <BR><FONT SIZE=3D2>To: Multiple recipients of list ORACLE-L</FONT> <BR><FONT SIZE=3D2>Subject: using obfuscation</FONT> </P> <BR> <P><FONT SIZE=3D2>Hi all,</FONT> </P> <P><FONT SIZE=3D2>I have downloaded the Metalink Notes on implementing = dbms_obfuscation. I am</FONT> <BR><FONT SIZE=3D2>using multiple front ends on the database, so the way = I plan to implement</FONT> <BR><FONT SIZE=3D2>the de-encryption is with a de-encrypt function in a = view. </FONT> </P> <P><FONT SIZE=3D2>Create View my_data</FONT> <BR><FONT SIZE=3D2>AS</FONT> <BR><FONT SIZE=3D2>Select de_encrypt(sensitive_data)&nbsp; AS = sensitive_data</FONT> <BR><FONT SIZE=3D2>,other_data</FONT> <BR><FONT SIZE=3D2>FROM original_table</FONT> <BR><FONT SIZE=3D2>;</FONT> </P> <P><FONT SIZE=3D2>If I select from the view with a where clause on = other_data, the response</FONT> <BR><FONT SIZE=3D2>time is fine. If I select from the view with a where = clause on</FONT> <BR><FONT SIZE=3D2>sensitive_data, I do a full table scan and which = takes about 15 minutes.</FONT> <BR><FONT SIZE=3D2>The de-encrypt function is copied from a Metalink = note, nothing fancy. </FONT> </P> <P><FONT SIZE=3D2>Since I have various front ends, I can not de-encrypt = the data in the front</FONT> <BR><FONT SIZE=3D2>end.&nbsp; The only way I can think of is with the = function in a view, but the</FONT> <BR><FONT SIZE=3D2>response time is unacceptable.&nbsp; Does anyone have = any thoughts on this?</FONT> </P> <P><FONT SIZE=3D2>Thanks</FONT> <BR><FONT SIZE=3D2>Randy</FONT> <BR><FONT SIZE=3D2>-- </FONT> <BR><FONT SIZE=3D2>Please see the official ORACLE-L FAQ: <A = HREF=3D"http://www.orafaq.com">http://www.orafaq.com</A></FONT> <BR><FONT SIZE=3D2>-- </FONT> <BR><FONT SIZE=3D2>Author: Steiner, Randy</FONT> <BR><FONT SIZE=3D2>&nbsp; INET: RASTEIN_at_NYCT.com</FONT> </P> <P><FONT SIZE=3D2>Fat City Network Services&nbsp;&nbsp;&nbsp; -- (858) = 538-5051&nbsp; FAX: (858) 538-5051</FONT> <BR><FONT SIZE=3D2>San Diego, = California&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- Public Internet = access / Mailing Lists</FONT> <BR><FONT = SIZE=3D2>----------------------------------------------------------------= ----</FONT> <BR><FONT SIZE=3D2>To REMOVE yourself from this mailing list, send an = E-Mail message</FONT> <BR><FONT SIZE=3D2>to: ListGuru_at_fatcity.com (note EXACT spelling of = 'ListGuru') and in</FONT> <BR><FONT SIZE=3D2>the message BODY, include a line containing: UNSUB = ORACLE-L</FONT> <BR><FONT SIZE=3D2>(or the name of mailing list you want to be removed = from).&nbsp; You may</FONT> <BR><FONT SIZE=3D2>also send the HELP command for other information = (like subscribing).</FONT> </P> </BODY> </HTML> ------_=_NextPart_001_01C25E9E.91DD5D04-- -- Please see the official ORACLE-L FAQ: http://www.orafaq.com -- Author: Aponte, Tony INET: AponteT_at_hsn.net Fat City Network Services -- 858-538-5051 http://www.fatcity.com San Diego, California -- Mailing list and web hosting services --------------------------------------------------------------------- To REMOVE yourself from this mailing list, send an E-Mail message to: ListGuru_at_fatcity.com (note EXACT spelling of 'ListGuru') and in the message BODY, include a line containing: UNSUB ORACLE-L (or the name of mailing list you want to be removed from). You may also send the HELP command for other information (like subscribing).
Received on Tue Sep 17 2002 - 19:08:19 CDT

Original text of this message

HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US