recently a customer had the request to export a column to a flat file. nothing special, but the request was to encrypt the column as the data needs to be delivered to a third site. as you might know oracle provides the dbms_obfuscation_toolkit package which might be used for requests like this. so, here is an example ( as wordpress does not allow to upload scripts, sorry for the long pasting :) ):
/** Export a column for a given table and schema in encrypted format to csv PARAMETERS: OWNER TABLE COLUMN ENRYPTION-KEY **/ SET LINES 164 VERIFY OFF PAGES 999 SERVEROUT ON FEEDBACK OFF VARIABLE owner VARCHAR2(30) VARIABLE table_name VARCHAR2(30) VARIABLE column_name VARCHAR2(30) VARIABLE encryption_key VARCHAR2(100) VARIABLE continue VARCHAR2(1); EXEC dbms_output.put_line ( chr(13) ); EXEC dbms_output.put_line ( '------------------------------------------------------' ); EXEC dbms_output.put_line ( '-- INFO: Starting encrypted column export '); EXEC dbms_output.put_line ( chr(13) ); /** do some sanity checks **/ DECLARE lv_owner all_users.username%TYPE; lv_directory all_directories.directory_path%TYPE; FUNCTION schema_exists ( pv_schema IN all_users.username%TYPE ) RETURN BOOLEAN IS CURSOR cur_schema IS SELECT 'ok' FROM all_users WHERE username = pv_schema ; lv_cursor_result VARCHAR2(2); BEGIN OPEN cur_schema; FETCH cur_schema INTO lv_cursor_result; CLOSE cur_schema; RETURN NVL ( lv_cursor_result, 'xx' ) = 'ok'; END schema_exists; FUNCTION table_exists ( pv_schema IN all_users.username%TYPE , pv_table IN all_tables.table_name%TYPE ) RETURN BOOLEAN IS CURSOR cur_table IS SELECT 'ok' FROM all_tables WHERE owner = pv_schema AND table_name = pv_table ; lv_cursor_result VARCHAR2(2); BEGIN OPEN cur_table; FETCH cur_table INTO lv_cursor_result; CLOSE cur_table; RETURN NVL ( lv_cursor_result, 'xx' ) = 'ok'; END table_exists; FUNCTION column_exists ( pv_schema IN all_users.username%TYPE , pv_table IN all_tables.table_name%TYPE , pv_column IN all_tab_columns.column_name%TYPE ) RETURN BOOLEAN IS CURSOR cur_column IS SELECT 'ok' FROM all_tab_columns WHERE owner = pv_schema AND table_name = pv_table AND column_name = pv_column ; lv_cursor_result VARCHAR2(2); BEGIN OPEN cur_column; FETCH cur_column INTO lv_cursor_result; CLOSE cur_column; RETURN NVL ( lv_cursor_result, 'xx' ) = 'ok'; END column_exists; FUNCTION exp_directory_exists RETURN BOOLEAN IS CURSOR cur_directory IS SELECT directory_path FROM all_directories WHERE directory_name = 'DATA_PUMP_DIR' ; BEGIN OPEN cur_directory; FETCH cur_directory INTO lv_directory; CLOSE cur_directory; RETURN lv_directory IS NOT NULL; END exp_directory_exists; BEGIN :owner := UPPER('&1'); :table_name := UPPER('&2'); :column_name := UPPER('&3'); :encryption_key := '&4'; IF :encryption_key IS NULL OR -- encryption key must be at least 80 characters length ( :encryption_key ) < 80 THEN raise_application_error ( -20001 , '--- CHECK INPUT: The encryption key "'||:encryption_key||'" is too short or empty ! '); :continue := 'N'; END IF; IF NOT ( schema_exists ( pv_schema => :owner ) ) THEN raise_application_error ( -20001 , '--- CHECK INPUT: The schema '||:owner||' does not exist in this database ! '); :continue := 'N'; END IF; IF NOT ( table_exists ( pv_schema => :owner , pv_table => :table_name ) ) THEN raise_application_error ( -20001 , '--- CHECK INPUT: The table '||:table_name||' does not exist for the schema '||:owner||' ! '); :continue := 'N'; END IF; IF NOT ( column_exists ( pv_schema => :owner , pv_table => :table_name , pv_column => :column_name ) ) THEN raise_application_error ( -20001 , '--- CHECK INPUT: The column '||:column_name||' does not exist for the table '||:table_name||' in schema '||:owner||'! '); :continue := 'N'; END IF; IF NOT ( exp_directory_exists ) THEN raise_application_error ( -20001 , '--- CHECK DIRECTORY: Can not find directory DATA_PUMP_DIR ! '); :continue := 'N'; ELSE dbms_output.put_line (' --- INFO: File will be located here : '||lv_directory ); END IF; :continue := 'Y'; END; / DECLARE lv_owner all_users.username%TYPE := UPPER(:owner); lv_table all_tables.table_name%TYPE := UPPER(:table_name); lv_column all_tab_columns.column_name%TYPE := UPPER(:column_name); lt_file_type utl_file.file_type; lv_statement VARCHAR2(4000); ln_cursor NUMBER; ln_amount_of_rows INTEGER := 0; lv_value VARCHAR2(4000); lv_seed VARCHAR2(4000) := :encryption_key; BEGIN IF ( :continue = 'Y' ) THEN dbms_output.put_line (' --- INFO: selected schema is : '||lv_owner ); dbms_output.put_line (' --- INFO: selected table is : '||lv_table ); dbms_output.put_line (' --- INFO: selected column is : '||lv_column ); dbms_output.put_line (' --- INFO: exporting to : DATA_PUMP_DIR '); -- open file for writing lt_file_type := utl_file.fopen ( location => 'DATA_PUMP_DIR' , filename => 'exp_'||lv_owner||'_'||lv_table||'_'||lv_column||'_' ||TO_CHAR(sysdate,'DD.MM.YYYY HH24:MI:SS')||'.csv' , open_mode => 'w' ); lv_statement := 'SELECT '||lv_column||' FROM '||lv_owner||'.'||lv_table; dbms_output.put_line (' --- INFO: statement is: '||lv_statement ); -- prepare statement ln_cursor := dbms_sql.open_cursor; dbms_sql.parse ( c => ln_cursor , statement => lv_statement , language_flag => dbms_sql.v7 ); dbms_sql.define_column ( c => ln_cursor , position => 1 , column => lv_column , column_size => 4000 ); ln_amount_of_rows := dbms_sql.execute ( c => ln_cursor ); LOOP -- fetch all the rows IF dbms_sql.fetch_rows ( c => ln_cursor ) = 0 THEN EXIT; ELSE ln_amount_of_rows := ln_amount_of_rows + 1; -- get the column value dbms_sql.column_value ( c => ln_cursor , position => 1 , value => lv_value ); -- input must be a multiple of 8 bytes, so rpadding with blanks dbms_obfuscation_toolkit.desencrypt ( input_string => rpad ( lv_value , ( trunc ( length ( lv_value ) / 8 ) + 1 ) * 8 , chr(0) ) , key_string => lv_seed , encrypted_string => lv_value ); -- write to output file utl_file.put_line ( file => lt_file_type , buffer => utl_raw.cast_to_raw ( lv_value ) ); END IF; END LOOP; -- close cursor dbms_sql.close_cursor ( c => ln_cursor ); -- close file handle utl_file.fclose ( file => lt_file_type ); dbms_output.put_line (' --- Exported '||ln_amount_of_rows||' rows !!! '); END IF; END; /
for decrypting one could use this snippet:
/** Decrypt the encrypted data in the exported csv files **/ SET LINES 164 VERIFY OFF PAGES 999 SERVEROUT ON FEEDBACK OFF VARIABLE filename VARCHAR2(4000) VARIABLE encryption_key VARCHAR2(100) VARIABLE continue VARCHAR2(1); EXEC dbms_output.put_line ( chr(13) ); EXEC dbms_output.put_line ( '------------------------------------------------------' ); EXEC dbms_output.put_line ( '-- INFO: Starting decryption '); EXEC dbms_output.put_line ( chr(13) ); /** do some sanity checks **/ DECLARE lv_directory all_directories.directory_path%TYPE; FUNCTION exp_directory_exists RETURN BOOLEAN IS CURSOR cur_directory IS SELECT directory_path FROM all_directories WHERE directory_name = 'DATA_PUMP_DIR' ; BEGIN OPEN cur_directory; FETCH cur_directory INTO lv_directory; CLOSE cur_directory; RETURN lv_directory IS NOT NULL; END exp_directory_exists; BEGIN :filename := '&1'; :encryption_key := '&2'; IF ( length ( :encryption_key ) < 80 ) THEN raise_application_error ( -20001 , '--- CHECK INPUT: The encryption key "'||:encryption_key||'" is too short or empty ! '); :continue := 'N'; END IF; IF NOT ( exp_directory_exists ) THEN raise_application_error ( -20001 , '--- CHECK DIRECTORY: Can not find directory DATA_PUMP_DIR ! '); :continue := 'N'; ELSE dbms_output.put_line (' --- INFO: File to be loaded : '||lv_directory ); END IF; :continue := 'Y'; END; / DECLARE lt_file_type utl_file.file_type; lv_buffer VARCHAR2(4000); lv_buffer_raw RAW(4000); lraw_decrypted_value RAW(4000); lv_value RAW(32767); BEGIN IF ( :continue = 'Y' ) THEN -- open file handle lt_file_type := utl_file.fopen ( location => 'DATA_PUMP_DIR' , filename => :filename , open_mode => 'r' -- , max_linesize => 4000 ); -- read through the file line by line LOOP BEGIN utl_file.get_line ( file => lt_file_type , buffer => lv_buffer ); lv_buffer_raw := lv_buffer; dbms_obfuscation_toolkit.desdecrypt ( input => lv_buffer_raw , key => utl_raw.cast_to_raw ( :encryption_key ) , decrypted_data => lraw_decrypted_value ); dbms_output.put_line ( 'encrypted value: '||utl_raw.cast_to_varchar2(lraw_decrypted_value) ); EXCEPTION WHEN no_data_found THEN EXIT; END; END LOOP; -- close file handle utl_file.fclose ( file => lt_file_type ); ELSE NULL; END IF; END; /
for encrypting and decrypting two simple calls now do the trick:
# to encrypt: @encrypted_column_export.sql USER TABLE_NAME COLUMN_NAME ENCRYPTION_KEY # to decrypt @decrypt_exported_csv.sql FILE_NAME ENCRYPTION_KEY