manage-oracle-encryption-keys-for-isms-compliance.md
devcondasecuritymanage-oracle-encryption-keys-for-isms-compliance.md

Manage Oracle Encryption Keys for ISMS Compliance

Written by

in

Earlier I covered managing encryption keys on the WAS with a JAR module. Here’s how to manage keys when encryption and decryption happen inside procedures called by the Oracle Scheduler.

Design

  • Store the encryption key encrypted in a separate file on the server.
  • Have Oracle read the file and load the encryption key.
  • Use a package function to decrypt that value and return the encryption key.
  • Wrap the package so only the key administrator can see the source.
  • Wrap every procedure that calls that function.

Store the encryption key

Encrypt the encryption key and save it to a file on the server. Create the folder as the Oracle install account, or create it as root and grant folder permissions to the Oracle account so Oracle can access it.

Create the directory and file:

mkdir /home/oracle/enc_key
vi /home/oracle/enc_key/enc_key.txt

Enter:

ED6CF66DAF90E7D9C6935E1B47643210791603D8B6745F0E31B2C14CFA31A3C7
cat /home/oracle/enc_key/enc_key.txt
ED6CF66DAF90E7D9C6935E1B47643210791603D8B6745F0E31B2C14CFA31A3C7

Read the file with UTL_FILE

UTL_FILE is a package that provides functions and procedures for accessing files managed by the operating system. File paths are accessed through a DIRECTORY object, so first confirm that access is allowed.

Create and query the Oracle directory:

CREATE DIRECTORY ENC_KEY AS '/home/oracle/enc_key';

SELECT * FROM ALL_DIRECTORIES;

Confirm that the created file can be accessed:

DECLARE
  FHANDLE UTL_FILE.FILE_TYPE;
  FBUFFER VARCHAR2(64);
BEGIN
  FHANDLE := UTL_FILE.FOPEN('ENC_KEY', 'enc_key.txt', 'R');
  UTL_FILE.GET_LINE(FHANDLE, FBUFFER);
  DBMS_OUTPUT.PUT_LINE(FBUFFER);
  UTL_FILE.FCLOSE(FHANDLE);
END;

Result:

ED6CF66DAF90E7D9C6935E1B47643210791603D8B6745F0E31B2C14CFA31A3C7

Create the package

Create an ENCKEY package with a GET_ENC_KEY function that returns the encryption key.

Package specification

CREATE OR REPLACE PACKAGE TEST.ENCKEY IS
  FUNCTION GET_ENC_KEY RETURN VARCHAR2;
END ENCKEY;

Package body

CREATE OR REPLACE PACKAGE BODY TEST.ENCKEY IS
  FUNCTION GET_ENC_KEY RETURN VARCHAR2 IS
    FHANDLE UTL_FILE.FILE_TYPE;
    FBUFFER VARCHAR2(64);
    INPUT_STRING VARCHAR2(64);
    KEY_RAW RAW(16) := UTL_RAW.CAST_TO_RAW('test1234test1234');
    OUTPUT_RAW RAW(1024);
    V_OUT_STRING VARCHAR2(1024);
    AES_CBC_PKCS5 CONSTANT PLS_INTEGER :=
      DBMS_CRYPTO.ENCRYPT_AES128
      + DBMS_CRYPTO.CHAIN_CBC
      + DBMS_CRYPTO.PAD_PKCS5;
  BEGIN
    -- CREATE DIRECTORY ENC_KEY AS '/home/oracle/enc_key'
    -- SELECT * FROM ALL_DIRECTORIES
    FHANDLE := UTL_FILE.FOPEN('ENC_KEY', 'enc_key.txt', 'R');
    UTL_FILE.GET_LINE(FHANDLE, FBUFFER, 64);
    INPUT_STRING := FBUFFER;
    UTL_FILE.FCLOSE(FHANDLE);

    IF INPUT_STRING IS NULL THEN
      RETURN NULL;
    END IF;

    OUTPUT_RAW := DBMS_CRYPTO.DECRYPT(
      src => INPUT_STRING,
      typ => AES_CBC_PKCS5,
      key => KEY_RAW
    );
    V_OUT_STRING := UTL_I18N.RAW_TO_CHAR(OUTPUT_RAW, 'AL32UTF8');
    RETURN V_OUT_STRING;
  END GET_ENC_KEY;
END ENCKEY;

After creating the package function, confirm that it returns the key correctly:

SELECT ENCKEY.GET_ENC_KEY FROM dual;

Wrap the package

DBMS_DDL provides wrapping for DDL statements that start with CREATE OR REPLACE PROCEDURE or PACKAGE, along with other DDL-related features. Using the CREATE_WRAPPED procedure both hides the package source and compiles it.

Looking at the package content, calling GET_ENC_KEY returns the encryption key, and the key used to encrypt/decrypt that encryption key is also visible in the source. For that reason, wrap both the specification and the body, and keep the plaintext source separately.

VARCHAR2 has a maximum length of 4,000 bytes, so for longer content use the CLOB type (CLOB max length is 4GB).

Wrap the package specification

DECLARE
  src CLOB;
BEGIN
  src := '
CREATE OR REPLACE PACKAGE TEST.ENCKEY IS
  FUNCTION GET_ENC_KEY RETURN VARCHAR2;
END ENCKEY;
';
  DBMS_DDL.CREATE_WRAPPED(src);
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;

Wrap the package body

DECLARE
  src CLOB;
BEGIN
  src := '
CREATE OR REPLACE PACKAGE BODY TEST.ENCKEY IS
  FUNCTION GET_ENC_KEY RETURN VARCHAR2 IS
    FHANDLE UTL_FILE.FILE_TYPE;
    FBUFFER VARCHAR2(64);
    INPUT_STRING VARCHAR2(64);
    KEY_RAW RAW(16) := UTL_RAW.CAST_TO_RAW(''test1234test1234'');
    OUTPUT_RAW RAW(1024);
    V_OUT_STRING VARCHAR2(1024);
    AES_CBC_PKCS5 CONSTANT PLS_INTEGER :=
      DBMS_CRYPTO.ENCRYPT_AES128
      + DBMS_CRYPTO.CHAIN_CBC
      + DBMS_CRYPTO.PAD_PKCS5;
  BEGIN
    -- CREATE DIRECTORY ENC_KEY AS ''/home/oracle/enc_key''
    -- SELECT * FROM ALL_DIRECTORIES
    FHANDLE := UTL_FILE.FOPEN(''ENC_KEY'', ''enc_key.txt'', ''R'');
    UTL_FILE.GET_LINE(FHANDLE, FBUFFER, 64);
    INPUT_STRING := FBUFFER;
    UTL_FILE.FCLOSE(FHANDLE);

    IF INPUT_STRING IS NULL THEN
      RETURN NULL;
    END IF;

    OUTPUT_RAW := DBMS_CRYPTO.DECRYPT(
      src => INPUT_STRING,
      typ => AES_CBC_PKCS5,
      key => KEY_RAW
    );
    V_OUT_STRING := UTL_I18N.RAW_TO_CHAR(OUTPUT_RAW, ''AL32UTF8'');
    RETURN V_OUT_STRING;
  END GET_ENC_KEY;
END ENCKEY;
';
  DBMS_DDL.CREATE_WRAPPED(src);
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;

Confirm the wrapped package. It is stored in wrapped form like this:

CREATE OR REPLACE PACKAGE BODY TEST.ENCKEY wrapped
a000000
369
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
b
346 258
d7fOkKhxMcBsoykWCbGefn3PBr0wg3lezPYFfC8ZCk7Vud6fEXwuYY1LJTdWT3bzLmXxaPdJ
f/K0ymMojCgFP3O5fsYK6+Z2Gvi5vHL+Vb6UwJarRpc9W2fV/jBUfUuFwxcBd//m1n/MevOm
HAvBYmiZlr8VIG+IF8knSX55L0UR8SU5DPkMCFAGYoxcsI6SJk1TOlNdU2F5H17jhwWLlFrg
+m0gZ64tvJMVwZhBkOJjjqjofc5xXbGwuyQ/+RDkgdLOuPK70LA0ZJ9Mkbn0jyOCSxlJ/kBd
Npl0oStlDRKIIjf136I476AbFHnv33c27YBMEGMhqhGM1fofIhTlcSBhuCod9Ey6GzTjxFEc
FFoDFWC68Rw/F+/GSFuWTG1hhca9XdWF0K4oKfXkXiK1P4sKLCNBnjCHYLpWYGGUX7ZUr7r/
tzzjclSJIdFZdoRYrJNjy3DaC8q5yLpXllvGV6R5sqNMX4YVnav4hDWhk280RpiWgvBcKFAb
f3z9cqml3NNlSNmb35h2m30ahyB5oYg0eHrSBrXV99YVz7T1zHkS73LV+awCUWhHbSg3Rldl
z9H1Qv4o30538w==

Verify the function call

Confirm that the function still returns the key correctly after wrapping. crypto.encrypt is the function from the earlier Oracle DBMS_CRYPTO post.

SELECT crypto.encrypt('personal_info', ENCKEY.GET_ENC_KEY)
FROM dual;

Closing notes

Because calling ENCKEY.GET_ENC_KEY reveals the encryption key, every procedure that calls this function should also be wrapped. I handled those the same way with the CLOB type.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *