Пароли в Oracle

Сама идея написания данной статьи возникла при изучении алгоритмов шифрования пользовательских паролей в СУБД Oracle и проведения аутентификации и идентификации при удаленном подключении к серверу, по причине отсутствия достаточной информации (по крайней мере на русском языке) по этой тематике, а также из-за желания осветить эти вопросы для искушенных пользователей.

Информация, предлагаемая Вашему вниманию дает обобщенное представление по указанной теме. Отдельные детали, алгоритмические решения выходят за рамки этой статьи. Клиентские исследования производились на платформе i86 под управлением ОС WindowsNT 4.0. Серверная часть представлена СУБД Oracle 8i на платформе SPARC Sun Solaris.

Итак начнем пожалуй.

Для начала заглянем в словарь данных Oracle. Отыщем в нем таблицу dba_users. Доступ к этой таблице обусловлен наличием администраторских полномочий или объектными привилегиями пользователя. Вот список полей, содержащихся в ней:

USERNAME

USER_ID

PASSWORD

ACCOUNT_STATUS

LOCK_DATE

EXPIRY_DATE

DEFAULT_TABLESPACE

TEMPORARY_TABLESPACE

CREATED

PROFILE

EXTERNAL_NAME

Из всего этого многообразия нас интересуют всего два:

USERNAME - имя зарегестрированного пользователя;

PASSWORD - его пароль, представленный шестнадцатью символами.

Именно эти символы нас и интересуют.

Для начала заведем пользователя с именем "logon" и паролем "password". Его запись в таблице dba_users (рассматриваем только два поля) будет выглядить следующим образом:

USERNAME - LOGON

PASSWORD - D927474809C8875F

Оба параметра являются строковыми значениями или попросту varchar2(30), т.е. строковая переменная с максимальной длиной в тридцать символов.

Значение представленное в поле PASSWORD представляет собой ничто иное как шестнадцатиричное числовое значение с длиной в восемь байт или 64 бита. Именно это значение и является зашифрованным паролем пользователя.

Теперь об используемом алгоритме. Понадобились некоторое время и довольно кропотливая работа для того, чтобы выяснить какие функции ядра Oracle задействованы в этом преобразовании. Есть такая динамическая библиотека в Oracle как core35.dll. Так вот именно в ней были найдены эти загадочные функции. Вот их список:

lmxehtl, lmxegks, lmxechk, lmxeecb. Конечно это всего лишь малая часть того, что задейстовано, но именно они помогли достичь нужного результата. А вот порядок их вызова и краткое описание передаваемых параметров:

lmxehtl("0123456789abcdef", start_code) - на вход этой функции передается строковое значение (заметьте - тоже шестнадцать символов, т.е. 8 байт или 64 бита), а на выходе, в параметр <start_code> помещается его числовое значение т.е. 0x0123456789ABCDEF, которое представляет собой 64-х разрядное число. Проще говоря функция преобразует строковое представление числа в его двоичный образ.

lmxegks(start_code, key_code, direct) - на вход поступает 64-х разрядное число из предыдущей функции и параметр, который я условно назвал "direct" (о нем чуть позже), содержащий значение равное "1". На выходе в параметр "key_code" помещается адрес массива, содержащего ключ шифрования.

lmxechk(key_code, buffer, lenbuf, out_dw, codechk) - на вход поступает адрес ключа шифрования "key_code", шифруемые данные "buffer", их длина "lenbuf". На выходе "codechk" получаем зашифрованное значение. Параметр "out_dw" - для хранения промежуточных данных. Но для получения конечного результата необходимо еще раз повторить функции lmxegks и lmxechk, но в lmxegks в качестве параметра start_code необходимо передать уже параметр "codechk", т.е. шифрованные данные.

Конечно несколько сумбурно и сложно, но поверьте, что это не суть. А вот когда я разбирался с этими функциями, используемыми таблицами констант, стеком, передаваемыми параметрами, их размерностью, то натолкнулся на простое решение - все, что используется в шифровании пароля это просто DES!!! Пришлось изучить стандарт. Все совпадает. Конечно в реализации Oracle есть некоторые отступления от стандарта, но алгоритм тот же.

Теперь о некторых деталях.

Как известно, пароль в Oracle может быть задан как обычной строкой, так и строкой в кавычках (непечатные символы). Я исследовал обычные пароли, т.е. латинский алфавит без каких либо спецсимволов.

Для начала строка имени пользователя и пароля объединяются вместе и преобразуется в верхний регистр:

[[logon] [password] ] -> [LOGONPASSWORD]

и шестнадцатиричное значение:

[4C 4F 47 4F 4E 50 41 53 53 57 4F 52 44] длиной 13 байт.

Затем в ходе преобразования получаем такое значение:

[4F 00 4C 00 4F 00 47 00 50 00 4E 00 53 00 41 00 57 00 53 00 52 00 4F 00 00 00 44 00 00 00 00 00 ] длиной 32 байта.

Идея преобразования заключается в преобразовании исходной строки в UNICODE, а затем переменой местами соседних двухбайтовых величини. Общая длина в байтах должна быть выравнена на границу кратную восьми. Таким образом имеем 13 байт исходной строки, умножаем на 2 (UNICODE) - 26, и добавляем нулями до длины 32 кратной восьми.

Далее разбиваем полученную байтовую строку на блоки по 8 байт и шифруем с помощью DES. Начальный вектор инициализации для генерации набора из 16-ти 48-ми разрядных ключей 0x0123456789ABCDEF. Берем следующий открытый блок, преобразуем его с помощью операции "исключающее-ИЛИ" с уже зашифрованным и результат опять шифруем DESом. И так далее…

Клиент-Сервер

Алгоритм таков (все тот же DES):

  1. Клиент вводит логин, пароль, строку подключения.
  2. Сервер выдает клиенту случайное (возможно нет) 64-х разрядное число (возможно идентификатор сессии) ID.
  3. Клиент шифрует пароль пользователя. Получаем 64-х разрядное число H(cl).
  4. На основе H(cl) генерируется набор из 16-ти 48-ми разрядных ключей K1(cl) (кстати, помните про третий параметр "direct", так вот здесь он равено "0". От этого зависит в каком порядке формируются шестнадцать 48-ми разрядных ключей. Если это "1" - то с первого до шестнадцатого, если "0" - то наоборот с шестнадцатого до первого).
  5. Шифруем ID c помощью K(cl) и получаем зашифрованный 64-х разрядный блок E(ID, K(cl)).
  6. На основе K1(cl) генерируется набор из 16-ти 48-ми разрядных ключей K2(cl) (здесь "direct" снова равен "1").
  7. Шифруем открытый пароль, введенный пользователем с помощью K2(cl) и получаем целевой зашифрованный блок который вместе с открытым логином пользователя отправляется на сервер.

На стороне сервера вероятно происходит обратное преобразование и проверка. Также используется несколько другой алгоритм преобразования открытой строки пароля на этапе 7 в результате которого к целевому блоку может дописываться полубайт (какие-то манипуляции с длиной - пока не разобрался).

Вот и все. Но, главный вывод (теоретический):

Если получить файл содержащий табличное пространство SYSTEM (там содержатся логины и зашифрованные пароли пользователей) или просто информацию из таблицы DBA_USERS, получить сетевой трафик обмена клиент-сервер, мы получим данные необходимые и достаточные для расшифровки пароля.

Кучеренко А.

2005/07/07

e-mail: shadow_moon@list.ru

vsb@canopus.ukrtel.net