Benutzer-Werkzeuge

Webseiten-Werkzeuge


dba:passwort_in_psql_schuetzen

Passwörter und ähnliche Schlüssel in PL/SQL Packages schützen

Um zum Beispiel auf ein LDAP Verzeichnis in PL/SQL zuzugreifen, müssen oft Passwörter in PL/SQL Programmen hinterlegt werden, oder bei einer Kreditkartenverschlüsselung der Master Key.

Die einfachste Möglichkeit ist das Schreiben in eine Tabelle.

Für das Deployment einer Applikation ist es aber oft besser, das ganze über den eigentlichen Source Code komplett abzuwickeln als umständliche wieder ein DML Statement irgendwo mit einzubauen, eine Tabelle anzulegen und das dann auch noch zu pflegen.

Das Password darf aber im Source Code, in Backups/Exports und ähnlichen nicht mehr lesbar sein.

Im ersten Schritt heißt das wohl, das wir das Passwort nur verschlüsselt speichern dürfen.

Aber wohin dann mit dem Schlüssel?

Der Algorithmus für die Verschlüsselung kann so aufwendig und ausgefeilt sein wie es nur geht, wenn der Schlüssel und der verschlüsselte Wert letztendlich zusammen gespeichert werden, ist alles hinfällig.

Da aber ein 100% Schutz doch sehr aufwendig werden kann, ist mein Ziel hier nun um einiges niedrier.

Ich will erreichen:

  • Kein Klarschrift Passwort im Source Code in Git/SVN oder auf der USB Platte des Entwicklers
  • Keine oder geringe Chance das verschlüsselte Kennwort auf einen anderen System wie der USB Platte des Entwicklers wieder einfach nachträglich auszulesen
  • Keine festen Schlüssel, die wiederum irgendwo hinterlegt sind, die Schlüssel müssen sich aus der Laufzeitumgebung ergeben - Ändert sich diese, muss das Passwort dann eben neu hinterlegt werden
  • Die Möglichkeit in einer Routine diesen dynamischen zentralen Schlüssel nochmals zu verändern um die Logik hinter dem Schlüssel in Source Code möglichst zu verteilen

Zusätzlicher Schutz:

  • Das zu schützende Package liegt in einem gesonderten Schema
  • ⇒ 12c Möglichkeit nützen das nur das Package explizit auf die eine Routine zu granten die das Package dann auch benötigt, nicht an den eigentlichen Applikationsuser
  • ⇒ 12c Feature „accessible by“ ausnützen (d.h. die Routine bzw der Type/die Function kann nur in diesem Package verwandt werden!)

Die alles bietet keine Schutz vor einen „echten“ Angriff!

ABER es erfüllt viele Regularien Buchstaben getreu und verhindert das „versehentliche“ verteilen von Passwörtern und macht den Feierabend Hackern das Leben schwerer (oder interessanter je nach dem .-) )!

Dies einfachen Maßnahmen schützen damit zwar nur zu 90%, sind immer noch besser als der leichtsinnige Umgang mit Passwörtern im Quellcode in Klarschrift!

Bezgl. Scripting siehe auch diesen Ideen dazu Datenbank User Passwörter in Shell und SQL Skripten und um in einen Linux Script das Password zu verschlüsseln, siehe auch Bash Snippets - Passwörter in Configurationen verschlüsseln.

Eine PL/SQL Lösung

Hier wird eine Lösung vorgeschlagen, bei der bewust auf den Einsatz einer Tabelle verzichtet wird, das läßt sich aber trivial einfach in den Code an den entsprechenden Stellen einbauen.

Vorbereitung

Dem Package Owner muss das Rechte gebenen werden:

  • grant create procedure to <package_owner>
  • grant create type to <package_owner>

Die Spezifikation:

encrypt_util_spec.sql
CREATE OR REPLACE PACKAGE encrypt_util
AS
   crypt_value_error EXCEPTION;
   PRAGMA exception_init(crypt_value_error, -06502);
 
   crypt_key_error EXCEPTION;
   PRAGMA exception_init(crypt_key_error, -28817);
 
 
  --+ -----------------------------------------------------------------------
  -- encrypt text
  --+ -----------------------------------------------------------------------
  FUNCTION encrypt (p_plaintext VARCHAR2)
    RETURN RAW
    deterministic;
 
  --+ -----------------------------------------------------------------------
  -- decrypt text
  --+ -----------------------------------------------------------------------
  FUNCTION decrypt (p_encryptedtext RAW)
    RETURN VARCHAR2
    deterministic;
 
  --+ -----------------------------------------------------------------------
  -- store passwords in to a object
  --+ -----------------------------------------------------------------------
  PROCEDURE storepwd (p_pwd VARCHAR2
              , p_slot NUMBER
              , p_store VARCHAR2 DEFAULT 'OBJECT'
              , p_private_key VARCHAR2 DEFAULT 'NO'
            );
 
  --+ -----------------------------------------------------------------------
  -- get User password
  --+ -----------------------------------------------------------------------
  FUNCTION getuserpwd (p_slot NUMBER,p_private_key VARCHAR2 DEFAULT 'NO')
    RETURN VARCHAR2;
 
END encrypt_util;
/

Body:

encrypt_util_body.sql
CREATE OR REPLACE PACKAGE BODY encrypt_util
AS
  --+ ----------------------------------------------------------------------------
  -- thankt to:
  -- see http://www.oracleflash.com/41/Encrypt-or-Decrypt-sensitive-data-using-PLSQL---DBMS_CRYPTO.html
  --+ --------------------------------------------------------------------------
  g_crypt_clear_key         VARCHAR2 (256) := '';
  g_encryption_key          RAW (32) := '';
 
 
  --+ ----------------------------------------------------------------------------
  -- ENCRYPT_DES is the encryption algorithem.
  -- Data Encryption Standard. Block cipher.
  -- Uses key length of 56 bits.
  --
  -- CHAIN_CBC Cipher Block Chaining. Plaintext is XORed with the previous ciphertext
  -- block before it is encrypted.
  --
  -- PAD_PKCS5 Provides padding which complies with the PKCS #5: Password-Based
  -- Cryptography Standard
  --+ ----------------------------------------------------------------------------
  g_encryption_type         PLS_INTEGER := dbms_crypto.encrypt_des + dbms_crypto.chain_cbc + dbms_crypto.pad_pkcs5;
 
  --+ ----------------------------------------------------------------------------
  --  encrypt a text
  --+ ----------------------------------------------------------------------------
  FUNCTION encrypt (p_plaintext VARCHAR2)
    RETURN RAW
    deterministic
  IS
    encrypted_raw            RAW (2000);
  BEGIN
    encrypted_raw :=
      dbms_crypto.encrypt (src  => UTL_RAW.cast_to_raw (p_plaintext)
                   , typ  => g_encryption_type
                   , key  => g_encryption_key
                    );
    RETURN encrypted_raw;
  END encrypt;
 
 
  --+ ----------------------------------------------------------------------------
  --  decrypt a text
  --+ ----------------------------------------------------------------------------
  FUNCTION decrypt (p_encryptedtext RAW)
    RETURN VARCHAR2
    deterministic
  IS
    decrypted_raw            RAW (2000);
  BEGIN
    decrypted_raw :=
      dbms_crypto.decrypt (src  => p_encryptedtext
                   , typ  => g_encryption_type
                   , key  => g_encryption_key
                    );
    RETURN (UTL_RAW.cast_to_varchar2 (decrypted_raw));
  END decrypt;
 
 
  --+ ----------------------------------------------------------------------------
  --  clean string from whitespaces to avoid sql injection
  --+ ----------------------------------------------------------------------------
  FUNCTION cleantext (p_text VARCHAR2)
    RETURN VARCHAR2
  IS
    v_text                VARCHAR2 (32000);
  BEGIN
    v_text := REPLACE (p_text, CHR (10), '');
    v_text := REPLACE (v_text, CHR (13), '');
    v_text := REPLACE (v_text, ' ', '');
    v_text := regexp_replace (v_text , '[[:space:]]' , '');
    RETURN v_text;
  END cleantext;
 
  --+ ----------------------------------------------------------------------------
  -- get a key from the local db enviroment
  -- The encryption key for DES algorithem, should be 8 bytes or more.
  --+ ----------------------------------------------------------------------------
  FUNCTION getkeyfromlocal(p_private_key VARCHAR2 DEFAULT NULL)
    RETURN VARCHAR2
  IS
    v_obj_id1              VARCHAR2 (12) := '00000';
    v_obj_id2              VARCHAR2 (12) := '00000';
    v_key                 VARCHAR2 (2000) := 'PWD';
  BEGIN
    -- get some ids from default objects, that not chaning so often .-)
    SELECT TO_CHAR (object_id)
      INTO v_obj_id1
      FROM user_objects
     WHERE object_name = 'ENCRYPT_UTIL'
         AND object_type = 'PACKAGE BODY';
 
    SELECT TO_CHAR (SUM (object_id))
      INTO v_obj_id2
      FROM all_objects
     WHERE object_name = 'DBMS_STANDARD'
         AND owner IN ('SYS', 'PUBLIC');
 
    v_key :=
        INITCAP (SYS_CONTEXT ('USERENV','CURRENT_SCHEMA'))
      || '#'
      || LOWER (SYS_CONTEXT ('USERENV', 'CURRENT_SCHEMAID'))
      || '*'
      || NVL (v_obj_id1, '00000')
      || '$'
      || INITCAP (SYS_CONTEXT ('USERENV', 'DB_NAME'))
      || '+'
      || NVL (v_obj_id2, '00000');
 
    IF    p_private_key IS NOT NULL THEN
        v_key := p_private_key||v_key;
    END IF;
 
    RETURN SUBSTR(v_key,0,32);
 
  END getkeyfromlocal;
 
 
  --+ ----------------------------------------------------------------------------
  -- store passwords in to a object
  -- need create function right
  -- grant create procedure to <package_owner>
  -- grant create type to <package_owner>
    -- debug exec encrypt_util.storepwd('ABCDE',1,'FUNCTION','1234')
  --+ ----------------------------------------------------------------------------
 
  PROCEDURE storepwd (p_pwd VARCHAR2
              , p_slot NUMBER
              , p_store VARCHAR2 DEFAULT 'OBJECT'
                            , p_private_key VARCHAR2 DEFAULT 'NO'
               )
  IS
    CURSOR c_read_type
    IS
      SELECT text
        FROM user_source
       WHERE name = 'PWD_WALLET'
           AND TYPE = 'TYPE BODY';
 
    v_f_template            VARCHAR2 (2000)
      := 'create or replace function getSecretUserPwd##Slot## return raw is begin return (''##CRYPT_KEY##''); end;';
 
    v_o_template_s           VARCHAR2 (4000)
      := 'CREATE or replace TYPE pwd_wallet AS OBJECT (
                                                  password varchar2(56)
                                                  ,MEMBER FUNCTION getUserPWD1 RETURN raw
                                                  ,MEMBER FUNCTION getUserPWD2 RETURN raw
                                                  ,MEMBER FUNCTION getUserPWD3 RETURN raw
                                                  ,MEMBER FUNCTION getUserPWD4 RETURN raw
                                                  ,MEMBER FUNCTION getUserPWD5 RETURN raw
                                                )';
 
    v_o_template_b           VARCHAR2 (4000)
      := 'CREATE or replace  TYPE BODY pwd_wallet AS 
                                        MEMBER 
                                        FUNCTION getUserPWD1 RETURN raw 
                                             IS
                                           BEGIN
                                             RETURN (''##CRYPT_KEY1##''); -- Slot1 not remove 
                                          END;
                                        MEMBER 
                                        FUNCTION getUserPWD2 RETURN raw 
                                             IS
                                           BEGIN
                                             RETURN (''##CRYPT_KEY2##''); -- Slot2 not remove 
                                          END;
                                        MEMBER 
                                        FUNCTION getUserPWD3 RETURN raw 
                                             IS
                                           BEGIN
                                             RETURN (''##CRYPT_KEY3##''); -- Slot3  not remove 
                                          END;
                                        MEMBER 
                                        FUNCTION getUserPWD4 RETURN raw 
                                             IS
                                           BEGIN
                                             RETURN (''##CRYPT_KEY4##''); -- Slot4  not remove 
                                          END;
                                        MEMBER 
                                        FUNCTION getUserPWD5 RETURN raw 
                                             IS
                                           BEGIN
                                             RETURN (''##CRYPT_KEY5##''); -- Slot5  not remove 
                                         END;
                                    end;
                                    ';
 
    v_template            VARCHAR2 (32000) := '';
    v_key                 VARCHAR2 (2000);
    v_slot                VARCHAR2 (2);
    v_count               PLS_INTEGER;
  BEGIN
 
     -- set your private key to harden the safe
    IF p_private_key != 'NO' THEN
        g_crypt_clear_key := getkeyfromlocal(p_private_key);
    ELSE 
       g_crypt_clear_key := getkeyfromlocal;        
    END IF;
 
    --dbms_output.put_line ('-- Info :: set New Key ::'||g_crypt_clear_key);
    g_encryption_key := UTL_RAW.cast_to_raw (g_crypt_clear_key);
 
    -- prevent for SQLInjection
    v_key := cleantext (p_text => p_pwd);
    v_slot := cleantext (p_text => TO_CHAR (p_slot));
 
    DBMS_OUTPUT.put_line ('--Info :: store in Slot : ' || v_slot || ' PWD :: ' || v_key);
 
    IF p_store = 'FUNCTION'
    THEN
      v_template :=REPLACE (v_f_template , '##CRYPT_KEY##' , (encrypt (v_key)) );
 
      v_template :=REPLACE (v_template, '##Slot##', v_slot);
    ELSIF p_store = 'TABLE'
    THEN
      NULL;
    -- implement here your setup table to store the pwd
    --
    ELSIF p_store = 'OBJECT'
    THEN
      SELECT COUNT (*)
        INTO v_count
        FROM user_types
       WHERE type_name = 'PWD_WALLET';
 
      IF v_count < 1
      THEN
        -- create the object spec
        EXECUTE IMMEDIATE v_o_template_s;
 
        v_template :=REPLACE (v_o_template_b , '##CRYPT_KEY' || v_slot || '##', (encrypt (v_key)));
      ELSE
        -- get the code of the object
        -- check if object exists
        v_template := 'create or replace ';
 
        FOR rec IN c_read_type
        LOOP
          IF INSTR (rec.text
                , 'Slot' || v_slot
                ) > 1
          THEN
            -- correct line
            v_template :=
                v_template
              || regexp_replace (rec.text
                          , '[RETURN (''].*['');]'
                          , 'RETURN (''' || (encrypt (v_key)) || ''');'
                          );
          ELSE
            v_template := v_template || rec.text;
          END IF;
        END LOOP;
      END IF;
    END IF;
 
    --dbms_output.put_line('--Info :: try to execute : '|| v_template);
 
    EXECUTE IMMEDIATE v_template;
  END storepwd;
 
  --+ -----------------------------------------------------------------------
  -- get User password
    -- select encrypt_util.getuserpwd(2) from dual;
  --+ -----------------------------------------------------------------------
 
  FUNCTION getuserpwd (p_slot NUMBER, p_private_key VARCHAR2 DEFAULT 'NO')
    RETURN VARCHAR2
  IS
    v_count                PLS_INTEGER;
 
    v_template_0            VARCHAR2 (2000) := 'declare
        v_pwd pwd_wallet;
        begin
         v_pwd:=pwd_wallet(''xxx'');
         :val:=encrypt_util.decrypt(v_pwd.getUserPWD##Slot##);
        end;
        ';
    v_template_f            VARCHAR2 (2000) := ' begin
          :val:=encrypt_util.decrypt(getSecretUserPwd##Slot##);
        end;
        ';
 
    v_pwd                 VARCHAR2 (2000);
    v_slot                VARCHAR2 (2);
    v_template              VARCHAR2 (2000);
  BEGIN
 
        -- set your private key to harden the safe      
        IF p_private_key != 'NO' THEN
            g_crypt_clear_key := getkeyfromlocal(p_private_key);
        ELSE 
            g_crypt_clear_key := getkeyfromlocal;   
        END IF;
 
        --dbms_output.put_line ('-- Info :: set New Key ::'||g_crypt_clear_key);
 
        g_encryption_key := UTL_RAW.cast_to_raw (g_crypt_clear_key);
 
    v_slot := cleantext (p_text => TO_CHAR (p_slot));
 
    -- if object exitst use object
    SELECT COUNT (*)
      INTO v_count
      FROM user_types
     WHERE type_name = 'PWD_WALLET';
 
    IF v_count > 0
    THEN
      v_template :=REPLACE (v_template_0 , '##Slot##' , v_slot);
    ELSE
      -- must be a function or a table
      SELECT COUNT (*)
        INTO v_count
        FROM user_objects
       WHERE object_name = UPPER ('getSecretUserPwd' || v_slot);
 
      IF v_count > 0
      THEN
        v_template :=REPLACE (v_template_f, '##Slot##', v_slot);
      ELSE
        -- implment your table data store here
        raise_application_error (-20001
                        , 'No PWD Function found for the slot::' || v_slot
                        );
      END IF;
    END IF;
 
    BEGIN
      EXECUTE IMMEDIATE v_template using OUT v_pwd;
    EXCEPTION
      WHEN crypt_key_error THEN
                 v_pwd := 'NO_PWD_KEY_WRONG_SLOT' || v_slot;
                 DBMS_OUTPUT.put_line ('-- Error :: KEY Error for PWD in Slot ' || v_slot || ' set! Error: '||SQLERRM);
            WHEN crypt_value_error
            THEN
                v_pwd := 'NO_PWD_SET_SLOT' || v_slot;
                DBMS_OUTPUT.put_line ('-- Error :: No PWD in Slot ' || v_slot || ' set! Error:'||SQLERRM);
            WHEN OTHERS
      THEN
        raise_application_error (-20000, 'Error read pwd from pwd_wallet:: Error' || SQLERRM);
    END;
 
    RETURN v_pwd;
  END getuserpwd;
BEGIN
   g_crypt_clear_key := getkeyfromlocal;   
   g_encryption_key := UTL_RAW.cast_to_raw (g_crypt_clear_key);
END;
/

Verwenden

Das Passwrd wird mit einem eigenen Key (auch dieser private Schlüssel kommt aus den Kontext der Umgebung gesetzt.

In diesem Beispiel wollen wir das Passwort im Package ldpa_util verwenden und im ersten Passwort Slot speichern:

 
variable g_my_package_id NUMBER
 
SELECT object_id INTO :g_my_package_id FROM user_objects WHERE object_name='LDAP_UTIL' AND object_type LIKE '%BODY' ;
 
 
EXEC encrypt_util.storepwd('to_secret_pwd',1,'OBJECT',:g_my_package_id);

Im Package dann auch eine Abfrage auf die Package ID einbauen und dann diese ID der Entschlüsselung übergeben:

..
-- im allgemeine Teil des Packages
BEGIN
 
 SELECT object_id INTO g_my_package_id 
  FROM user_objects 
 WHERE object_name='LDAP_UTIL' AND object_type LIKE '%BODY' ;
 
 g_ldap_passwd :=encrypt_util.getuserpwd(1,g_my_package_id)
 
END lpda_util;

Soviel als Anregung zu dem Thema.

Wie immer Sie das auch umsetzen, keine Klarschrift Passwörter in PL/SQL Code!

Quellen

Cookies helfen bei der Bereitstellung von Inhalten. Durch die Nutzung dieser Seiten erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Rechner gespeichert werden. Weitere Information
"Autor: Gunther Pipperr"
dba/passwort_in_psql_schuetzen.txt · Zuletzt geändert: 2016/05/20 15:20 von Gunther Pippèrr