понедельник, 6 июня 2011 г.

Хранение паролей в БД Oracle

Когда безопасность обеспечивается на уровне тонкого клиента возникает необходимость хранения паролей в БД. Это может привести к проблемам безопасности, поскольку люди с соответствующими привилегиями смогут прочитать содержимое таблиц с паролями. Простой способ решения - зашифровывать пароли перед тем, как записывать их в БД. Проблемой в этом случае является то, что остается возможность расшифровки при нахождении механизма шифрования. Безопасная альтернатива - хранить хэш имени пользователя и пароля.

В данном примере мы будем использовать пакет DBMS_OBFUSCATION_TOOLKIT, который доступен, начиная с Oracle8i:

Таблицы с секретными данными

Для начала мы должны создать таблицы для хранения секретной информации.

CREATE TABLE app_users (
  id        NUMBER(10)    NOT NULL,
  username  VARCHAR2(30)  NOT NULL,
  password  VARCHAR2(16)  NOT NULL
)
/

ALTER TABLE app_users ADD (
  CONSTRAINT app_users_pk PRIMARY KEY (id)
)
/

ALTER TABLE app_users ADD (
  CONSTRAINT app_users_uk UNIQUE (username)
)
/

CREATE SEQUENCE app_users_seq
/


Пакет для работы с шифрованием

Cоздадим спецификацию пакета шифрования:

CREATE OR REPLACE PACKAGE app_user_security AS

  FUNCTION get_hash (p_username  IN  VARCHAR2,
                     p_password  IN  VARCHAR2)
    RETURN VARCHAR2;
    
  PROCEDURE add_user (p_username  IN  VARCHAR2,
                      p_password  IN  VARCHAR2);

  PROCEDURE change_password (p_username      IN  VARCHAR2,
                             p_old_password  IN  VARCHAR2,
                             p_new_password  IN  VARCHAR2);

  PROCEDURE valid_user (p_username  IN  VARCHAR2,
                        p_password  IN  VARCHAR2);

  FUNCTION valid_user (p_username  IN  VARCHAR2,
                       p_password  IN  VARCHAR2)
    RETURN BOOLEAN;

END;
/
Затем создадим тело, определив актуальные операции:
CREATE OR REPLACE PACKAGE BODY app_user_security AS

  FUNCTION get_hash (p_username  IN  VARCHAR2,
                     p_password  IN  VARCHAR2)
    RETURN VARCHAR2 AS
  BEGIN
    RETURN DBMS_OBFUSCATION_TOOLKIT.MD5(
      input_string => UPPER(p_username) || '/' || UPPER(p_password));
  END;

  PROCEDURE add_user (p_username  IN  VARCHAR2,
                      p_password  IN  VARCHAR2) AS
  BEGIN
    INSERT INTO app_users (
      id,
      username,
      password
    )
    VALUES (
      app_users_seq.NEXTVAL,
      UPPER(p_username),
      get_hash(p_username, p_password)
    );
    
    COMMIT;
  END;
   
  PROCEDURE change_password (p_username      IN  VARCHAR2,
                             p_old_password  IN  VARCHAR2,
                             p_new_password  IN  VARCHAR2) AS
    v_rowid  ROWID;
  BEGIN
    SELECT rowid
    INTO   v_rowid
    FROM   app_users
    WHERE  username = UPPER(p_username)
    AND    password = get_hash(p_username, p_old_password)
    FOR UPDATE;
    
    UPDATE app_users
    SET    password = get_hash(p_username, p_new_password)
    WHERE  rowid    = v_rowid;
    
    COMMIT;
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      RAISE_APPLICATION_ERROR(-20000, 'Invalid username/password.');
  END;

  PROCEDURE valid_user (p_username  IN  VARCHAR2,
                        p_password  IN  VARCHAR2) AS
    v_dummy  VARCHAR2(1);
  BEGIN
    SELECT '1'
    INTO   v_dummy
    FROM   app_users
    WHERE  username = UPPER(p_username)
    AND    password = get_hash(p_username, p_password);
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      RAISE_APPLICATION_ERROR(-20000, 'Invalid username/password.');
  END;
  
  FUNCTION valid_user (p_username  IN  VARCHAR2,
                       p_password  IN  VARCHAR2) 
    RETURN BOOLEAN AS
  BEGIN
    valid_user(p_username, p_password);
    RETURN TRUE;
  EXCEPTION
    WHEN OTHERS THEN
      RETURN FALSE;
  END;
  
END;
/
Перегрузка VALID_USER позволяет проверить правильность пары логин/пароль разными способами.
Функция GET_HASH используется для получения хэша для пары логин/пароль. Она всегда возвращает результат равный VARCHAR2(16) вне зависимости от входных данных. Этот уровень сжатия означает, что хэш-значение не может быть уникальным, поэтому требуется ограничение уникальности по стобцу с именем пользователя.

Функция DBMS_UTILITY.GET_HASH_VALUE может быть использована вместо функции DBMS_OFUSCATION_TOOLKIT.MD5, но алгоритм хэширования у первой может отличаться в разных версиях БД.


Тестирование

Сначала мы создадим нового пользователя:

SQL> exec app_user_security.add_user('tim','hall');

PL/SQL procedure successfully completed.

SQL> select * from app_users;

        ID USERNAME                       PASSWORD
---------- ------------------------------ ----------------
         1 TIM                            [w?44Z??8fE??
Проверим процедуру VALID_USER:
SQL> EXEC app_user_security.valid_user('tim','hall');

PL/SQL procedure successfully completed.

SQL> EXEC app_user_security.valid_user('tim','abcd');
BEGIN app_user_security.valid_user('tim','hall1'); END;

*
ERROR at line 1:
ORA-20000: Invalid username/password.
ORA-06512: at "W2K1.APP_USER_SECURITY", line 37
ORA-06512: at line 1
Проверим функцию VALID_USER:
SQL> SET SERVEROUTPUT ON
SQL> BEGIN
  2    IF app_user_security.valid_user('tim','hall') TH
  3      DBMS_OUTPUT.PUT_LINE('TRUE');
  4    ELSE
  5      DBMS_OUTPUT.PUT_LINE('FALSE');
  6    END IF;
  7  END;
  8  /
TRUE

PL/SQL procedure successfully completed.

SQL> BEGIN
  2    IF app_user_security.valid_user('tim','abcd') T
  3      DBMS_OUTPUT.PUT_LINE('TRUE');
  4    ELSE
  5      DBMS_OUTPUT.PUT_LINE('FALSE');
  6    END IF;
  7  END;
  8  /
FALSE

PL/SQL procedure successfully completed.

SQL>
В заключении мы проверим процедуру CHANGE_PASSWORD:
SQL> exec app_user_security.change_password('tim','hall','hall1');

PL/SQL procedure successfully completed.

SQL> exec app_user_security.change_password('tim','abcd','abcd1');
BEGIN app_user_security.change_password('tim','abcd','abcd1'); END;

*
ERROR at line 1:
ORA-20000: Invalid username/password.
ORA-06512: at "W2K1.APP_USER_SECURITY", line 47
ORA-06512: at line 1

SQL>



Отсюда.

Комментариев нет:

Отправить комментарий