Шифрование и дешифрование в PL/SQL для БД Oracle на примере

Илья Дергунов

Илья Дергунов

Автор статьи. ИТ-специалист с 20 летним стажем, автор большого количества публикаций на профильную тематику (разработка ПО, администрирование, новостные заметки). Подробнее.

Программирование PL/SQL: шифрование и дешифрование в OracleВ простейшем определении шифрование представляет собой «маскировку» данных, или их преобразование, при котором данные не могут использоваться посторонними. Рассмотрим очень простой пример: я ежедневно снимаю деньги со своего счета при помощи кредитной карты. Каждый раз я должен ввести в банкомате свой PIN-код. К сожалению, я от природы весьма забывчив, поэтому я решаю записать PIN-код на предмете, который всегда под рукой при использовании карты, — то есть на самой карте. При этом я отлично понимаю, что PIN-код, написанный на карте, существенно ослаб­ляет защиту; если карту украдут, то похититель сразу увидит написанный на ней код. Прощайте, сбережения! Что делать, чтобы вор не смог узнать PIN-код, но при этом не забыть его самому?



Через пару минут меня озаряет гениальная идея: цифры надо изменить заранее опреде­ленным образом. Я прибавляю к PIN-коду число из одной цифры и записываю результат на карте. Допустим, к PIN-коду 6523 прибавляется число 6; в сумме получается 6529, и я записываю это число на карте. Если карту украдут, то вор увидит код 6529, кото­рый будет совершенно бесполезен, потому что для восстановления PIN-кода нужно знать способ изменения исходного числа. Даже если он будет знать, что результат был получен сложением, ему нужно будет угадать число (6 в нашем случае). Иначе говоря, я зашифровал PIN-код и затруднил определение фактического значения.

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

Первая часть называется алгоритмом, а вторая — ключом. Это основные компоненты любой системы шифрования, схема которой изображена на рис. 1. Вы можете сохра­нять один компонент неизменным и изменять другой для получения другого экземпляра зашифрованных данных.

 

Основная схема шифрования

Рис. 1. Основная схема шифрования

 

В действительно безопасных системах используются чрезвычайно сложные алгорит­мы. Вообще говоря, для построения системы шифрования вам не обязательно хорошо разбираться в них, однако вы должны знать основные типы алгоритмов, их сильные и слабые стороны. Впрочем, один лишь выбор алгоритма еще не гарантирует безопас­ности. Она обеспечивается изменением другой переменной, находящейся под вашим ведением, — ключа шифрования.

Одной из важнейших проблем при построении инфраструктуры шифрования является построение эффективной системы управления ключами. Если злоумышленник полу­чит доступ к ключам шифрования, то зашифрованные данные окажутся под угрозой независимо от сложности алгоритма. С другой стороны, некоторые пользователи (на­пример, приложения) должны иметь доступ к ключам для своей работы, и он должен быть достаточно простым для нормальной работы приложения. Проблема заключается в определении оптимального соотношения между простотой доступа и безопасностью ключей. Позднее в этой статье будет рассмотрен пример создания эффективной системы управления ключами.

 

Длина ключа

У приведенного выше примера с шифрованием PIN-кода имеется серьезный недостаток. Вор может догадаться, что я просто прибавил число к PIN-коду, чтобы зашифровать его. Конечно, он не знает, что это за число, но он может перебирать возможные значения. Если я использую число из одной цифры, ему понадобится не более 10 попыток. Но предположим, я использую число из двух цифр — теперь придется угадывать число от 0 до 99, а число попыток увеличивается до 100. Увеличение количества цифр в ключе усложняет «взлом» шифра. Таким образом, длина ключа играет исключительно важную роль в повышении безопасности любой системы шифрования.

Конечно, в реальных схемах шифрования длина ключа не ограничивается одной-дву­мя цифрами. Более того, ключи вообще не состоят из одних цифр. Их длина обычно составляет не менее 56 бит, но может доходить и до 256 бит. Длина ключа зависит от выбора алгоритма (см. следующий раздел).

Чем длиннее ключ, тем сложнее взломать шифр. С другой стороны, длинные ключи увеличивают затраты времени на шифрование и дешифрование, потому что про­цессору приходится выполнять больший объем работы.

 

Алгоритмы

Существует немало распространенных, пользующихся коммерческой поддержкой ал­горитмов шифрования. Впрочем, нас интересуют алгоритмы, поддерживаемые Oracle для приложений PL/SQL. Все эти алгоритмы относятся к категории алгоритмов с за­крытым ключом (иногда называемых симметричными алгоритмами); основные их отличия от алгоритмов с открытым ключом (иногда называемых асимметричными) описаны во врезке.

В Oracle чаще всего используются следующие алгоритмы:

 

открытый или ЗАКРЫТЫЙ ключ?

 

 

Заполнение и сцепление

Блок данных обычно не шифруется как единое целое. Чаще всего он разбивается на фрагменты по 8 байт, после чего каждый фрагмент шифруется независимо от других. Конечно, длина данных может быть не кратной 8 — в этом случае алгоритм добавля­ет символы в последний фрагмент до 8 байт. Этот процесс называется заполнением (padding). Если злоумышленник угадает, какие данные использовались для заполнения, это может упростить подбор ключа. Для безопасного заполнения следует использо­вать метод, реализованный в Oracle, называемый PKCS#5 (Public Key Cryptography System #5). Другие режимы (с заполнением нулями и вообще без заполнения) также будут представлены ниже.

Если данные делятся на фрагменты, также должен существовать способ объединения смежных фрагментов; этот процесс называется сцеплением (chaining). Безопасность системы шифрования также зависит от того, как происходит соединение и шифрование фрагментов (независимо или в сочетании со смежными фрагментами). Самым рас­пространенным форматом сцепления является форматCBC (Cipher Block Chaining);

в Oracle он выбирается при помощи константы, определенной во встроенном пакетеCHAIN_CBC. Также используются режимы сцепления Electronic Code Book (CHAIN_ ECB), Cipher Feedback (CHAIN_CFB) и Output Feedback (CHAIN_OFB). Они тоже будут представлены позднее в этом блоге.

 

Пакет DBMS_CRYPTO

Итак, мы познакомились с основными структурными элементами схем шифрования. Давайте посмотрим, как создать инфраструктуру шифрования в PL/SQL с использо­ванием встроенного пакета Oracle DBMS_CRYPTO.

Пакет DBMS_CRYPTO появился в Oracle10g. В более ранних версиях пакет DBMS_OBFUSCATION_TOOLKIT предоставлял похожую (но не идентичную) функциональность. Старый пакет все еще остается доступным, но сейчас он счи­тается устаревшим, и вместо него рекомендуется использовать новый пакет.

Вспомните, что для выполнения шифрования кроме входных данных необходимы еще четыре компонента:

Ключ шифрования предоставляете вы, а остальные компоненты предоставляет Oracle. Выбор осуществляется при помощи соответствующих констант пакета DBMS_CRYPTO.

 

Алгоритмы

В табл. 1 перечислены константы DBMS_CRYPTO, позволяющие выбрать конкретный алгоритм и длину ключа. Ссылки на эти константы должны задаваться в формате имя_пакета .имя_константы — например, DBMS_CRYPTO.ENCRYPT_DES для выбора ал­горитма DES.

Таблица 1. Константы алгоритмов пакета DBMS_CRYPTO

Константа Фактическая длина ключа Описание
ENCRYPT_DES 56 DES (по аналогии с DBMS_OBFUSCATION_TOOLKIT)
ENCRYPT_3DES_2KEY 112 Модифицированный DES3; блок шифруется в три прохода
с двумя ключами
ENCRYPT_3DES 156 DES3; блок шифруется в три прохода
ENCRYPT_AES128 128 AES
ENCRYPT_AES192 192 AES
ENCRYPT_AES256 256 AES
ENCRYPT_RC4 - Единственный потоковый шифр, поддерживаемый пакетом
(предназначен для шифрования потоковых, а не дискретных
данных)

 

 

Заполнение и сцепление

Режимы заполнения и сцепления задаются при помощи констант пакета DBMS_CRYPTO, перечисленных в табл. 2.

Таблица 2. Методы заполнения и сцепления пакетаDBMS_CRYPTO

Константа Метод заполнения/сцепления
PAD_PCKS5 Заполнение PKCS#5
PAD_ZERO Заполнение нулями
PAD_NONE Заполнение не используется; метод выбирается для данных, длина которых кратна
8 байтам
CHAIN_CBC Cipher Block (самый распространенный метод)
CHAIN_CFB Cipher Feedback
CHAIN_ECB Electronic Code Book
CHAIN_OFB Output Feedback


Вам почти не придется беспокоиться о выборе конкретного метода заполнения или сцепления; как правило, разные методы предлагают специфические возможности, редко необходимые при разработке типичных систем.

Чаще всего на практике выбирается метод заполнения PKCS#5 с методом сцепления CBC. В этой статье они будут использоваться во всех случаях, кроме тех, где обратное явно указано в тексте.

 

Шифрование данных

Начнем с очень простого примера шифрования строки «Confidential Data» функцией DBMS_CRYPTO.ENCRYPT. Функция получает четыре аргумента:

В следующих примерах будут использоваться:

Они задаются следующим значением параметра typ при вызове функции:

DBMS_CRYPTO.ENCRYPT_AES128
  + DBMS_CRYPTO.CHAIN_CBC
  + DBMS_CRYPTO.PAD_PKCS5;

Если бы вместо PKCS#5 был выбран режим без заполнения, значение выглядело бы так:

DBMS_CRYPTO.ENCRYPT_AES128
  + DBMS_CRYPTO.CHAIN_CBC
  + DBMS_CRYPTO.PAD_NONE;

Аналогичным образом задается любая другая комбинация алгоритма и метода сцепления.

Затем необходимо выбрать ключ. Предположим, в качестве ключа будет использоваться строка «1234567890123456». Это значение относится к типу данных VARCHAR2. Чтобы использовать его в функции ENCRYPT, необходимо сначала преобразовать его к типу RAW. Для этого мы воспользуемся функцией STRING_T0_RAW встроенного пакета UTL_I18N (этот пакет упоминается далее в моем блоге):

DECLARE
   l_raw    RAW (200);
   l_in_val VARCHAR2 (200) := 'Confidential Data';
BEGIN
   l_raw := utl_i18n.string_to_raw (l_in_val, 'AL32UTF8');
END;

Переменная l_in_val типа VARCHAR2 преобразована к типу RAW. Теперь можно переходить непосредственно к шифрованию входных данных:

/* File on web: enc.sql */
 1   DECLARE
 2       l_key      VARCHAR2 (2000) := '1234567890123456';
 3       l_in_val   VARCHAR2 (2000) := 'Confidential Data';
 4       l_mod      NUMBER
 5          :=   DBMS_CRYPTO.encrypt_aes128
 6             + DBMS_CRYPTO.chain_cbc
 7             + DBMS_CRYPTO.pad_pkcs5;
 8       l_enc      RAW (2000);
 9   BEGIN
10       l_enc :=
11          DBMS_CRYPTO.encrypt (utl_i18n.string_to_raw (l_in_val, 'AL32UTF8'),
12                               l_mod,
13                               utl_i18n.string_to_raw (l_key, 'AL32UTF8')
14                              );
15       DBMS_OUTPUT.put_line ('Encrypted=' || l_enc);
16   END;

Результат:

Encrypted=C0777257DFBF8BA9A4C1F724F921C43C70D0C0A94E2950BBB6BA2FE78695A6FC

Давайте проанализируем этот код, строку за строкой.

Строки Описание
2 Определение ключа. Длина ключа составляет ровно 16 символов (128 бит), в соответствии с требованиями AES. Если бы был выбран алгоритм AES192, я задал бы длину ключа равной 192/8=24. При неверной длине ключа выдается исключение
3 Входные данные для шифрования. Длина данных не ограничивается; допускается использование значения произвольной длины. Если длина не кратна 8 байтам, входные данные автоматически дополняются до нужной длины в соответствии с выбранным алгоритмом
4-7 Определение алгоритма, метода заполнения и метода сцепления
8 Определение переменной для хранения зашифрованного значения. Обратите внимание: выходные данные имеют тип RAW
11 Входные данные преобразуются из типа VARCHAR2 в тип RAW
13 Ключ тоже должен передаваться функции в формате RAW
15 Вывод зашифрованного значения (тоже в формате RAW) в виде шестнадцатеричной строки. В реальной системе выводить значение бессмысленно; вероятно, с ним будет выполнена другая операция: сохранение в таблице, передача вызывающей процедуре для использования в другом месте и т. д.

 

На базе ENCRYPT можно построить обобщенную функцию шифрования данных. В этой функции будет использоваться алгоритмAES с 128-разрядным ключом, метод заполне­ния PCKS#5 и метод сцепления CBC. Таким образом, при вызове функции пользователь должен предоставить только шифруемые данные и ключ.

/* File on web: get_enc_eval.sql */
FUNCTION get_enc_val (p_in_val IN VARCHAR2, p_key IN VARCHAR2)
   RETURN VARCHAR2
IS
   l_enc_val   RAW (4000);
BEGIN
   l_enc_val :=
      DBMS_CRYPTO.encrypt (src      => utl_i18n.string_to_raw (p_in_val,
                                                               'AL32UTF8'
                                                              ),
                           key      => utl_i18n.string_to_raw (p_key,
                                                               'AL32UTF8'
                                                              ),
                           typ      =>   DBMS_CRYPTO.encrypt_aes128
                                       + DBMS_CRYPTO.chain_cbc
                                       + DBMS_CRYPTO.pad_pkcs5
                          );
   RETURN l_enc_val;
END;

 

Напоследок осталось упомянуть еще об одном обстоятельстве. Для преобразования данных VARCHAR2 в RAW используется функция UTL_I18N.STRING_TO_RAW вместо UTL_RAW. CAST_TO_RAW. Почему?

Входные данные функции ENCRYPT должны иметь тип RAW и при этом использовать кон­кретный набор символов AL32UTF8, который может и не совпадать с набором символов базы данных. Следовательно, при проектировании строки VARCHAR2 в RAW для шифрования необходимо выполнить два преобразования:

Оба преобразования выполняются функцией STRING_T0_RAW встроенного пакета UTL_IL8N; функция CAST_T0_RAW не изменяет набор символов.

Пакет UTL_IL8N является частью архитектуры Oracle Globalization Support и пред­назначен для глобализации (или интернализации) приложений.

 

Шифрование LOB

Большие объектные типы данных — такие, как CL0B и BLOB, — тоже могут шифроваться. Например, в данных BLOB могут храниться файлы сигнатур и копии юридических до­кументов. Содержимое таких файлов конфиденциально, поэтому при хранении в базе данных их желательно зашифровать. Вместо того чтобы вызывать функцию ENCRYPT, как это делалось в предыдущих примерах, я воспользуюсь перегруженной процедурной версией ENCRYPT:

/* File on web: enc_lob.sql */
DECLARE
   l_enc_val   BLOB;
   l_in_val    CLOB;
   l_key       VARCHAR2 (16) := '1234567890123456';
BEGIN
   DBMS_CRYPTO.encrypt (dst      => l_enc_val,
                        src      => l_in_val,
                        key      => utl_i18n.string_to_raw (l_key, 'AL32UTF8'),
                        typ      =>   DBMS_CRYPTO.encrypt_aes128
                                    + DBMS_CRYPTO.chain_cbc
                                    + DBMS_CRYPTO.pad_pkcs5
                       );
END;

Результат сохраняется в переменной l_enc_val, которая затем может передаваться другим программам или сохраняться в таблице.

Для данныхLOB используйте процедурную версию ENCRYPT; для всех осталь­ных типов данных используйте функцию. Не забудьте преобразовать значения в формат RAWCLOB — вBLOB), прежде чем передавать их функцииENCRYPT.

 

SecureFiles

Большие объекты (LOB) были значительно переработаны в Oracle Database 11g; теперь для их обозначения используется термин SecureFiles. Традиционные объекты LOB (теперь называемые BasicFiles), такие как CLOB и BLOB, по-прежнему доступны, но я не рекомендую их использовать. В любых ситуациях, в которых в прошлом использовалисьLOB, теперь следует использовать SecureFiles. Технология SecureFiles предоставляет ту же функциональность, что и LOB, а также ряд дополнительных возможностей — таких, как сжатие, устранение дубликатов, кэширование, возможность прекращения ведения журнала и т. д.

 

Дешифрование данных

Шифрование данных имеет смысл только в том случае, если зашифрованные данные в какой-то момент будут прочитаны и использованы в приложении. Эта задача решается при помощи функции DECRYPT. По структуре вызова она идентична функцииENCRYPT и получает те же четыре аргумента:

Функция DECRYPT тоже возвращает дешифрованные данные в форматеRAW; для нормаль­ного просмотра их необходимо преобразовать в другой формат.

Давайте посмотрим, как работает дешифрование. В следующем примере зашифрованное значение сохраняется в переменнойSQL*Plus, а затем используется в качестве исходных данных для функции DECRYPT.

/* File on the web decval.sql */
 1    REM Define a variable to hold the encrypted value
 2    VARIABLE enc_val varchar2(2000);
 3    DECLARE
 4       l_key      VARCHAR2 (2000) := '1234567890123456';
 5       l_in_val   VARCHAR2 (2000) := 'Confidential Data';
 6       l_mod      NUMBER
 7          :=   DBMS_CRYPTO.encrypt_aes128
 8             + DBMS_CRYPTO.chain_cbc
 9             + DBMS_CRYPTO.pad_pkcs5;
10       l_enc      RAW (2000);
11    BEGIN
12       l_enc :=
13          DBMS_CRYPTO.encrypt (utl_i18n.string_to_raw (l_in_val, 'AL32UTF8'),
14                               l_mod,
15                               utl_i18n.string_to_raw (l_key, 'AL32UTF8')
16                              );
17       DBMS_OUTPUT.put_line ('Encrypted=' || l_enc);
18       :enc_val := RAWTOHEX (l_enc);
19    END;
20    /
21    DECLARE
22       l_key      VARCHAR2 (2000) := '1234567890123456';
23       l_in_val   RAW (2000)      := HEXTORAW (:enc_val);
24       l_mod      NUMBER
25          :=   DBMS_CRYPTO.encrypt_aes128
26             + DBMS_CRYPTO.chain_cbc
27             + DBMS_CRYPTO.pad_pkcs5;
28       l_dec      RAW (2000);
29    BEGIN
30       l_dec :=
31          DBMS_CRYPTO.decrypt (l_in_val,
32                               l_mod,
33                               utl_i18n.string_to_raw (l_key, 'AL32UTF8')
34                              );
35       DBMS_OUTPUT.put_line ('Decrypted=' || utl_i18n.raw_to_char (l_dec));
36    END;

Основные моменты этого кода разъясняются в следующей таблице.

  

Строки Описание
22 Объявление ключа для дешифрования. Обратите внимание: он должен совпадать с ключом, использованным при шифровании
23 Так как переменная enc_val содержит шестнадцатеричное значение, мы преобразуем ее к типу RAW
25-27 Как и при шифровании, необходимо задать алгоритм, методы заполнения и сцепления в одном параметре. Это значение должно совпадать с тем, которое использовалось при шифровании
33 Как и при шифровании, ключ должен храниться в переменной типа RAW, поэтому он преобразуется из VARCHAR2 в RAW


Код выводит строку «Confidential Data» — ту, которая была изначально зашифрована.

Для расшифровки зашифрованного объекта LOB необходимо использовать перегруженную процедурную версиюDECRYPT, потому что для шифрования использовалась процедурная версия ENCRYPT.

 

Генерирование ключей

До настоящего момента все наше внимание было сосредоточено на операциях шифрования и дешифрования, и в примерах использовался очень простой ключ «1234567890123456». Безопасность системы шифрования полностью зависит от без­опасности ключа, то есть от тех трудностей, с которыми столкнется потенциальный злоумышленник при подборе значения ключа. Следовательно, ключ должен быть до­статочно случайным, чтобы его нельзя было просто угадать.

В стандарте ANSI X9.31: Pseudo-Random Number Generator (PRNG) определяется стандартный алгоритм создания случайных чисел. Oracle реализует этот алгоритм в функции RANDOMBYTES пакета DBMS_CRYPTO. Функция получает один аргумент с длиной генерируемой случайной строки и возвращает значение заданной длины в формате RAW. Пример ее использования для создания 16-байтового значения:

DECLARE
   l_key   RAW (16);
BEGIN
   l_key := DBMS_CRYPTO.randombytes (16);
END;

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

  

Управление ключами

Мы рассмотрели азы шифрования/дешфирования, научились генерировать ключи. Но это была самая простая часть; в основном я просто показывал, как пользоваться готовыми средствами Oracle для достижения желаемой цели. Пора переходить к самой сложной части инфраструктуры шифрования — управлению ключами. Ключ должен быть досту­пен для наших приложений, чтобы они могли дешифровать зашифрованные значения, и механизм обращения к ключу должен быть по возможности простым. С другой стороны, поскольку ключ буквально является «ключом», защищающим зашифрованные данные, он не должен быть слишком доступным. Качественная схема управления ключами со­вмещает доступность ключей с предотвращением несанкционированного доступа к ним. Известны три основных схемы управления ключами:

Ниже приводятся краткие описания этих разных подходов к управлению ключами.

 

Один ключ для всей базы данных


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

Схема с одним ключом

рис. 2. Схема с одним ключом

 

Ключ может храниться в разных местах:

Главный недостаток этого способа — его зависимость от единой точки отказа. Если злоумышленник взломает базу данных и определит ключ, то вся база данных немед­ленно оказывается под его контролем. Кроме того, если вы захотите сменить ключ, вам придется изменять все строки всех таблиц; в большой базе данных объем работы может оказаться весьма значительным.

 

Один ключ для каждой строки

В этом варианте ключ связывается с каждой строкой таблицы, как показано на рис. 3. В базе данных создается отдельная таблица для хранения ключей. Исходная таблица и таблица ключей связываются через первичный ключ исходной таблицы.

Главное преимущество этого подхода заключается в том, что каждая запись защищается собственным ключом. Если один ключ будет подобран, то под контролем злоумышленни­ка окажется только одна строка, а не вся база данных. Изменение ключа распространяется не на всю базу данных, а только на одну строку, которая достаточно легко изменяется. С другой стороны, при таком подходе ключ всегда должен храниться в базе данных. Хранение ключей в файловой системе так, чтобы они были доступны для базы данных, может оказаться неприемлемым. Кроме того, усложняется защита файла базы данных в случае похищения как ключей, так и зашифрованных данных.

 

Комбинированная схема

Комбинированная схема стремится объединить высокую степень безопасности с мак­симальной гибкостью. Для каждой строки создается свой ключ, но в базе данных также имеется главный ключ (рис. 4). Процесс шифрования не сводится к простому ис­пользованию ключа, хранимого для каждой записи. Вместо этого ключ строки объ­единяется с главным ключом поразрядной операцией XOR, а полученное значение используется в качестве ключа строки. Чтобы расшифровать значение, необходимо знать как ключ строки (хранящийся в базе данных), так и главный ключ (хранящийся в другом месте). Раздельное хранение этих ключей повышает уровень безопасности схемы шифрования.

Схема с одним ключом для каждой строки

Рис. 3. Схема с одним ключом для каждой строки

 

 

Комбинированная схема

Рис. 4. Комбинированная схема

 

Недостаток комбинированной схемы тот же, что и у стратегии с одним ключом: при потере главного ключа данные будут потеряны. Впрочем, этот риск до определенной степени компенсируется резервным копированием главного ключа.

Эта схема не эквивалентна повторному шифрованию зашифрованного значения с другим ключом. ПакетDBMS_CRYPTO не позволяет повторно шифровать зашифрованные данные, если вы попытаетесь это сделать, Oracle выдаст ошибку ORA-28233.

Рассмотрим пример использования этой схемы в реальном приложении. В код, уже приводившийся ранее при описании дешифрования, добавляется новая переменнаяl_master_key (строка 6), которая получает значение от пользователя (подстановочная переменная &master_key). В строках 14-18 ключ объединяется операцией XOR с главным ключом, который использовался при шифровании в строке 22, вместо переменнойl_key.

/* File on web: combined_master_key.sql */
1    REM Define a variable to hold the encrypted value
2    VARIABLE enc_val varchar2(2000);
3    DECLARE
4       l_key          VARCHAR2 (2000) := '1234567890123456';
5       l_master_key   VARCHAR2 (2000) := '&master_key';
6       l_in_val       VARCHAR2 (2000) := 'Confidential Data';
7       l_mod          NUMBER
8          :=   DBMS_CRYPTO.encrypt_aes128
9             + DBMS_CRYPTO.chain_cbc
10             + DBMS_CRYPTO.pad_pkcs5;
11       l_enc          RAW (2000);
12       l_enc_key      RAW (2000);
13    BEGIN
14       l_enc_key :=
15          UTL_RAW.bit_xor (utl_i18n.string_to_raw (l_key, 'AL32UTF8'),
16                           utl_i18n.string_to_raw (l_master_key, 'AL32UTF8')
17                          );
18       l_enc :=
19          DBMS_CRYPTO.encrypt (utl_i18n.string_to_raw (l_in_val, 'AL32UTF8'),
20                               l_mod,
21                               l_enc_key
22                              );
23       DBMS_OUTPUT.put_line ('Encrypted=' || l_enc);
24       :enc_val := RAWTOHEX (l_enc);
25    END;
26    /
27    DECLARE
28       l_key          VARCHAR2 (2000) := '1234567890123456';
29       l_master_key   VARCHAR2 (2000) := '&master_key';
30       l_in_val       RAW (2000)      := HEXTORAW (:enc_val);
31       l_mod          NUMBER
32          :=   DBMS_CRYPTO.encrypt_aes128
33             + DBMS_CRYPTO.chain_cbc
34             + DBMS_CRYPTO.pad_pkcs5;
35       l_dec          RAW (2000);
36       l_enc_key      RAW (2000);
37    BEGIN
38       l_enc_key :=
39          UTL_RAW.bit_xor (utl_i18n.string_to_raw (l_key, 'AL32UTF8'),
40                           utl_i18n.string_to_raw (l_master_key, 'AL32UTF8')
41                          );
42       l_dec := DBMS_CRYPTO.decrypt (l_in_val, l_mod, l_enc_key);
43       DBMS_OUTPUT.put_line ('Decrypted=' || utl_i18n.raw_to_char (l_dec));
44    END;

 

При выполнении этого блока вSQI*Plus будет получен следующий результат (обратите внимание: сначала главный ключ вводится для шифрования данных, а затем тот же главный ключ вводится при дешифровании):

Enter value for master_key: MasterKey0123456
old   3:     l_master_key varchar2(2000) := '&master_key';
new   3:     l_master_key varchar2(2000) := 'MasterKey0123456';
Encrypted=C2CABD4FD4952BC3ABB23BD50849D0C937D3EE6659D58A32AC69EFFD4E83F79D

PL/SQL procedure successfully completed.

Enter value for master_key: MasterKey0123456
old   3:     l_master_key varchar2(2000) := '&master_key';
new   3:     l_master_key varchar2(2000) := 'MasterKey0123456';
Decrypted=ConfidentialData

PL/SQL procedure successfully completed.

Программа запросила главный ключ, который был введен правильно, и программа вы­дала правильное значение. А что произойдет, если главный ключ задан неверно?

Enter value for master_key: MasterKey0123456
old   3:     l_master_key varchar2(2000) := '&master_key';
new   3:     l_master_key varchar2(2000) := 'MasterKey0123456';
Encrypted=C2CABD4FD4952BC3ABB23BD50849D0C937D3EE6659D58A32AC69EFFD4E83F79D

PL/SQL procedure successfully completed.

Enter value for master_key: MasterKey0123455
old   3:     l_master_key varchar2(2000) := '&master_key';
new   3:     l_master_key varchar2(2000) := 'MasterKey0123455';
declare
*
ERROR at line 1:
ORA-28817: PL/SQL function returned an error.
ORA-06512: at "SYS.DBMS_CRYPTO_FFI", line 67
ORA-06512: at "SYS.DBMS_CRYPTO", line 41
ORA-06512: at line 15

Обратите внимание на ошибку при вводе главного ключа: она привела к тому, что за­шифрованные данные остались недоступными. Улучшенный механизм безопасности зависит от двух ключей, и успешное дешифрование данных возможно лишь при на­личии обоих. При отсутствии главного ключа несанкционированное дешифрование становится невозможным.

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

Идеальной схемы управления ключами не существует. Выбор зависит от природы при­ложения и попытки найти компромисс между безопасностью и простотой использования. Три основные разновидности методов управления ключами, описанные в этом разделе, станут отправной точкой для ваших собственных разработок. Возможно, у вас появится более удачная идея, которая лучше подойдет для вашей конкретной ситуации, например гибридное решение с разными ключами для важнейших таблиц.

 

Криптографическое хеширование

Шифрование гарантирует, что с данными смогут работать только санкционированные пользователи. Для этого критические данные преобразуются в новую форму. Однако в некоторых случаях требуется не скрыть данные, а просто защитить их от манипуля­ций. Классическим примером служит хранение финансовых счетов. Сами данные не настолько критичны, чтобы их шифровать, но вы должны позаботиться о том, чтобы никто не мог их изменить. Как это сделать?

Задача решается при помощи процесса, называемого криптографическим хешированием. В результате применения функции криптографического хеширования к некоторым данным генерируется новое значение, по которому невозможно «восстановить» исход­ные данные и которое с большой вероятностью является уникальным. Хеш-функции также обладают свойством детерминированности, то есть для одних исходных данных хеш-функция всегда возвращает один и то же хеш-код.

Хеширование не является разновидностью шифрования, потому что по хеш-коду невоз­можно получить исходные данные. Хеширование дает непрозрачный «след» исходных данных, хранимый отдельно от них. Когда возникает необходимость в проверке под­линности данных, вы снова генерируете хеш-код и сравниваете его с хранимым хеш- кодом. Если исходные данные изменились, то и хеш-код тоже изменится, и вы сможете выполнить соответствующие действия.

Теоретически для двух разных исходных значений могут быть сгенерированы одинаковые хеш-коды. Однако широко распространенные алгоритмы (такие, какMD5 и SHA-1) гарантируют, что вероятность конфликтов составляет статистически ничтожную величину 1/1038. Если вас не устраивает даже такая вероятность, вам придется реализовать собственную логику разрешения конфликтов.

В Oracle поддерживаются два вида хеширования: MD5 (Message Digest) и SHA-1 (Secure Hash Algorithm). Оба реализованы в функции HASH пакета DBMS_CRYPTO. Функция HASH получает два аргумента:

DBMS_CRYPTO.HASH_SH1
DBMS_CRYPTO.HASH_SH256
DBMS_CRYPTO.HASH_SH384
DBMS_CRYPTO.HASH_SH512
DBMS_CRYPTO.HASH_MD5
DBMS_CRYPTO.HASH_MD4

Разумеется, префикс SHA в именах констант относится к алгоритму хеширования SHA:SH1 — для метода SHA-1.SHA-2 — более новый и криптографически более защищенный метод хеширования, поддерживаемый в PL/SQL начиная с Oracle Database 12c. Доступ­ны три разновидности хеширования SHA-2SHA256, SHA384 и SHA512, в зависимости от используемой длины ключа. В следующем примере объявляются две локальные пере­менные: для исходных данных и генерируемого хеш-кода. Затем вызывается функция HASH с выбором алгоритма хеширования SHA-1:

/* File on web: hash.sql */
 1    DECLARE
 2       l_in_val   VARCHAR2 (2000) := 'CriticalData';
 3       l_hash     RAW (2000);
 4    BEGIN
 5       l_hash :=
 6          DBMS_CRYPTO.HASH (src      => utl_i18n.string_to_raw (
 7                                           l_in_val, 'AL32UTF8'
 8                                                               ),
 9                            typ      => DBMS_CRYPTO.hash_sh1
10                           );
11       DBMS_OUTPUT.put_line ('Hash=' || l_hash);
12    * END;

Программа выводит сгенерированный хеш-код: Hash=9222DE984C1A7DD792F680FDFD3EA05CB6CA59A9

Конечно, в реальных программах хеш-коды обычно не выводятся; они либо сохраняются в базе данных, либо передаются получателю для дальнейшей проверки.

Хеширование применяется во многих областях, не связанных с криптографией. На­пример, веб-приложения по своей природе не имеют состояния; сеанс приложения не обязательно соответствует «сеансу» экземпляра Oracle. Соответственно, блокировки уровня строк не гарантируют защиты от потерянных обновлений; после того как веб­страница загрузит строку, другое приложение может изменить данные. Как сеанс веб­приложения узнает об изменении загруженной ранее страницы? Одно из возможных решений заключается в генерировании и сохранении хеш-кодов данных строк. Когда в будущем приложению понадобится работать со строкой, оно выполняет повторное хеширование, сравнивает значения и быстро определяет актуальность данных.

 

Коды MAC

Хеширование предназначено для проверки подлинности данных, а не для их защиты от посторонних. Идея заключается в том, что вы генерируете хеш-код и сохраняете его в другом месте, отличном от места хранения самих данных. Позднее хеширование про­изводится заново, а результат сравнивается с сохраненным значением. Однако при этом возникает небольшая проблема: что делать, если злоумышленник обновит исходные данные, выполнит хеширование и обновит сохраненный хеш-код?

Для предотвращения подобных неприятностей создается своего рода хеш-код, защи­щенный паролем, — он называется кодом MAC (Message Authentication Code). Код MAC представляет собой хеш-код, объединенный с ключом. При использовании другого ключа на основании тех же исходных данных будет сгенерирован другой код MAC. Использование ключа не позволяет злоумышленнику сгенерировать тот же код MAC, если только он не угадает ключ (поэтому не используйте очевидные значения!). Алгоритм MAC реализуется функцией MAC пакета DBMS_CRYPTO. Функция получает три параметра:

Следующий пример практически идентичен тому, который использовался для демонстра­ции хеширования, кроме добавления ключа «1234567890123456». И ключ, и исходные данные должны относиться к типу RAW; если они хранятся в переменной другого типа, их необходимо преобразовать.

DECLARE
   l_in_val   VARCHAR2 (2000) := 'Critical Data';
   l_key      VARCHAR2 (2000) := 'SecretKey';
   l_mac      RAW (2000);
BEGIN
   l_mac :=
      DBMS_CRYPTO.mac (src      => utl_i18n.string_to_raw (l_in_val,'AL32UTF8'),
                       typ      => DBMS_CRYPTO.hmac_sh1,
                       KEY      => utl_i18n.string_to_raw (l_key, 'AL32UTF8')
                      );
   DBMS_OUTPUT.put_line ('MAC=' || l_mac);
   -- let's use a different key
   l_key := 'Another Key';
   l_mac :=
      DBMS_CRYPTO.mac (src      => utl_i18n.string_to_raw (l_in_val,'AL32UTF8'),
                       typ      => DBMS_CRYPTO.hmac_sh1,
                       KEY      => utl_i18n.string_to_raw (l_key, 'AL32UTF8')
                      );
   DBMS_OUTPUT.put_line ('MAC=' || l_mac);
END;

Результат:

MAC=7A23524E8B665A57FE478FBE1D5BFE2406906B2E
MAC=0C0E467B588D2AD1DADE7393753E3D67FCCE800C

Как и предполагалось, с другим ключом для тех же исходных данных генерируется дру­гой код MAC. Таким образом, если злоумышленник обновит код MAC, не зная ключа, он сгенерирует другой код, который не совпадет с ранее сгенерированным значением.

Приведенный пример чрезвычайно упрощен. В реальных приложениях для вы­полнения подобных операций используются намного более сложные ключи.

 

Прозрачное шифрование данных

В предыдущих разделах вы узнали, как построить инфраструктуру шифрования с нуля. Такая инфраструктура может понадобиться в том случае, если ваша организация должна выполнять требования многих правил и директив, действующих в наше время, или вы просто хотите защитить свою базу данных от потенциальных атак. Наверняка вы замети­ли, что в приведенных примерах построение компонентов, относящихся к шифрованию (триггеры, пакеты), было относительно простым и прямолинейным. Безусловно, самой сложной частью инфраструктуры было управление ключами шифрования. И хотя эти ключи должны быть доступны для приложений, доступ к ним необходимо ограничить для защиты их от похищения, что может быть непросто.

Начиная сOracle Database 10g Release 2 механизм прозрачного шифрования данных (TDE, Transparent Data Encryption) сильно упрощает шифрование. Все, что для этого нужно, — пометить столбец как зашифрованный; Oracle сделает все остальное. Значение столбца обрабатывается при вводе пользователем, шифруется и сохраняется в зашифро­ванном формате. В будущем при запросе данных значение автоматически дешифруется, и результат возвращается пользователю, которому даже не нужно знать о выполняемом шифровании и дешифровании, — отсюда и термин «прозрачный». Все происходит в коде Oracle без необходимости применения триггеров или сложной процедурной логики. Рассмотрим пример использования TDE: чтобы объявить столбец SSN таблицы ACCOUNTS как зашифрованный, достаточно включить в команду следующее условие:

ALTER TABLE accounts MODIFY (ssn ENCRYPT USING 'AES256')

База данных Oracle шифрует столбецSSN с применением алгоритма AES и 256-разрядного ключа. Ключ хранится в таблице словаря данных, но для его защиты от похищения он также шифруется с главным ключом, хранящимся в отдельном месте — электронном бу­мажнике. Бумажник по умолчанию хранится в каталоге $ORACLE_BASE/admin/$ORACLE_SID/wallet; впрочем, в файле SQLNET.ORA можно выбрать другое местонахождение. Допустим, пользователь вводит данные:

INSERT INTO accounts (ssn) VALUES ('123456789')

Фактическое значение сохраняется в зашифрованном виде в файлах данных, журналах операций и их архивах, а значит, и в резервных копиях. При следующих обращениях поль­зователя к данным зашифрованное значение автоматически дешифруется, и пользователь получает исходное значение. Перед выполнением приведенных команд бумажник должен быть открыт администратором базы данных или администратором по безопасности. Начиная с Oracle Database 12c в секции шифрования появился еще один параметр, обе­спечивающий дополнительную защиту шифруемых данных: к каждому зашифрованному значению добавляется 20-байтовый код MAC. Если кто-то изменит зашифрованное значение, код MAC измененных данных будет отличаться от исходного, и модификация данных будет обнаружена. Однако добавление кода MAC увеличивает затраты памяти на хранение данных, что может создать проблемы в базах данных, ограниченных по занимаемому пространству. Эту функцию можно отключить:

ALTER TABLE accounts MODIFY (ssn ENCRYPT USING 'AES256' NOMAC)

Как видите, использовать TDE очень просто, поэтому возникает закономерный вопрос: насколько актуально все, что говорилось ранее о шифровании в этой главе?

Кратко о шифровании

 


Вовсе нет! Область примененияTDE ограничена защитой файлов базы данных от возможного похищения посредством шифрования конфиденциальной информации с минимальными усилиями. Однако следует обратить особое внимание на слово «про­зрачный» в названии технологии — то есть автоматически выполняется как шифрова­ние, так и дешифрование данных. Oracle не различает пользователей в контексте базы данных. Когда пользователь обращается с запросом к данным, Oracle предоставляет текстовое значение независимо от того, кто выдал запрос.

Во многих случаях требуется строить более сложные системы, в которых доступ к тексто­вому значению открывается только в том случае, если пользователь, выдавший запрос, обладает привилегиями для его просмотра; в остальных случаях должно возвращаться зашифрованное значение. Выполнить это требование средствамиTDE не удается, потому что TDE расшифровывает все данные без разбора. Впрочем, желаемой цели можно добиться построением собственной инфраструктуры с применением методов, описанных в этой статье.

У TDE имеются свои ограничения. Во-первых, технология TDE не может использоваться для шифрования столбца внешнего ключа; во многих бизнес-приложениях это может стать серьезным препятствием. Кроме того, для столбцов, защищенных TDE, могут создаваться только индексы B-деревьев. При самостоятельной реализации шифрования средствамиPL/SQL такие ограничения отсутствуют.

Принимая решение о том, подойдет лиTDE для ваших целей, также необходимо принять во внимание другой аспект — автоматизацию. В TDE электронный бумажник (в котором хранится главный ключ) должен быть открыт администратором базы данных, для чего используется команда следующего вида:

ALTER SYSTEM SET ENCRYPTION WALLET OPEN AUTHENTICATED BY "pooh";

Здесь "pooh" — пароль к бумажнику. Если файлы базы данных (или журналы операций, или резервные копии этих файлов) будут похищены, то зашифрованные столбцы оста­нутся недоступными — вор не знает пароль, который позволит ему открыть бумажник. После каждого запуска базы данных вставка или обращение к зашифрованным столбцам станут возможны лишь после того, как бумажник будет явно открыт администратором. Если бумажник закрыт, операции вставки и попытки обращения к этим столбцам завер­шатся неудачей. После открытия базы данных необходимо выполнить одно лишнее дей­ствие; кроме того, человек, открывающий базу данных, должен знать пароль к бумажнику. В принципе для упрощения и автоматизации процесса можно создать триггер запуска базы данных, который вызывает команду ALTER SYSTEM (см. выше). Но в этом случае вы лишаете единственной защиты свой бумажник — а следовательно, и зашифрованные столбцы. Итак, при использовании TDE такие триггеры создавать никогда не следует, а вы должны быть готовы к выполнению лишней операции при каждом запуске базы данных. Однако самостоятельно реализованная инфраструктура шифрования станет доступна одновременно с базой данных; никакие дополнительные действия не потре­буются, а вам не придется запоминать и вводить пароли от бумажника.

Короче говоря, область применения TDE ограничена. Эта технология предоставляет простые и быстрые средства для шифрования файлов данных, журналов операций и резервных копий. Тем не менее она не защищает данные посредством ограничения доступа в зависимости от пользователя; данные всегда дешифруются при обращении. Если вы хотите более точно управлять процессом дешифрования, то вам придется строить собственную инфраструктуру.

 

Прозрачное шифрование табличного пространства

Итак, основные недостатки TDE (и в меньшей степени — пользовательских реализаций шифрования) в отношении производительности приложения:

По этим причинам при разработке реальных приложений технологияTDE часто от­вергается как неприемлемая, а обширные требования к пользовательским реализациям шифрования на базе DBMS_CRYPTO создают изрядные трудности во многих организациях. Для решения этих проблем в Oracle Database 11g появилась новая технология прозрач­ного шифрования табличного пространства (TTE, Transparent Tablespace Encryption). Она позволяет включить шифрование для всего табличного пространства, а не для от­дельных таблиц. Пример создания шифрованного табличного пространства:

TABLESPACE securets1
   DATAFILE '+DG1/securets1_01.dbf'
   SIZE 10M
   ENCRYPTION USING 'AES128'
   DEFAULT STORAGE (ENCRYPT)

Все объекты, создаваемые в этом табличном пространстве, должны преобразовываться в зашифрованный формат по алгоритму AES с использованием 128-разрядного ключа. Для этого необходимо заранее создать бумажник и открыть его так, как описано в пре­дыдущем разделе. Ключ шифрования хранится в таблице ENC$ в зашифрованном виде, а ключ к этому шифрованию хранится в бумажнике (как и в случае TDE). Конечно, бумажник должен быть открыт до создания табличного пространства.

Как шифрование табличного пространства поможет решить проблемы шифрования уровня таблиц, спросите вы? Принципиальное отличие между двумя технологиями заключается в том, что данные в табличном пространстве шифруются только на дис­ке; сразу же после чтения данные дешифруются и помещаются в буферный кэш SGA в виде простого текста. Операции сканирования индекса выполняются с буферным кэшем, тем самым решается проблема несоответствий зашифрованных данных. Кроме того, поскольку данные дешифруются и помещаются в буферный кэш только один раз (по крайней мере до их устаревания), дешифрование происходит только один раз, а не при каждом обращении к данным. Соответственно, в то время, пока данные остаются в SGA, шифрование не снижает производительность. Достигаются сразу обе цели — без­опасность посредством шифрования и минимальное влияние на производительность. Итак, проблема решена, и с TTE отпадает надобность в пользовательских процедурах шифрования, приводившихся ранее? Вовсе нет!

При шифровании табличного пространства шифруются все объекты — индексы и таблицы, независимо от того, нужно их шифровать или нет, если вам потребовалось шифровать все данные в табличном пространстве или хотя бы их большинство. А если шифроваться должна лишь малая часть общего объема данных? С применениемTTE на производи­тельность вашего приложения будет влиять существенно больший объем данных, чем это действительно необходимо. База данных Oracle сокращает последствия шифрования, но не может полностью избежать их. В результате вам, может, все равно придется самостоя­тельно реализовать избирательное шифрование данных в таблицах своего приложения.

Кроме того, зашифрованные табличные пространства могут только создаваться; вы не сможете преобразовать существующее табличное пространство в зашифрованную форму (или шифрованное табличное пространство в обычное). Вместо этого придется создать зашифрованное табличное пространство и переместить в него объекты. При внедрении шифрования в существующую базу данных решение на базе TTE может оказаться не­приемлемым из-за гигантских объемов многих рабочих баз данных. Пользовательское шифрование позволяет точно управлять тем, какие данные будут шифроваться (и де­шифроваться) вашим приложением.

Безусловно, пользовательская реализация шифрования находит свое место в реальных приложениях. Прозрачное шифрование табличного пространства реализуется намного быстрее и проще, но вы должны убедиться в том, что «тотальное» шифрование, осно­ванное на методе «грубой силы», подходит для вашего приложения.

ШИФРОВАНИЕ И EXADATA

  

Вас заинтересует / Intresting for you:

Управление приложениями PL/SQL...
Управление приложениями PL/SQL... 4633 просмотров Stas Belkov Thu, 16 Jul 2020, 06:20:48
Встроенные методы коллекций PL...
Встроенные методы коллекций PL... 14734 просмотров sepia Tue, 29 Oct 2019, 09:54:01
Использование записей (records...
Использование записей (records... 19623 просмотров Алексей Вятский Thu, 05 Jul 2018, 07:49:43
Символьные функции и аргументы...
Символьные функции и аргументы... 18544 просмотров Анатолий Wed, 23 May 2018, 18:54:01
Печать
Войдите чтобы комментировать

borisen аватар
borisen ответил в теме #9447 4 года 6 мес. назад
Просто ценнейший материал. Спасибо за публикацию!!)) Отечество Вас не забудет, Илья!)))