F.40. pgcrypto — криптографические функции #

Модуль pgcrypto предоставляет криптографические функции для Postgres Pro.

Данный модуль считается «доверенным», то есть его могут устанавливать обычные пользователи, имеющие право CREATE в текущей базе данных.

Для работы pgcrypto требуется OpenSSL — модуль не будет установлен, если Postgres Pro был собран без OpenSSL.

F.40.1. Стандартные функции хеширования #

F.40.1.1. digest() #

digest(data text, type text) returns bytea
digest(data bytea, type text) returns bytea

Вычисляет двоичный хеш данных (data). Параметр type выбирает используемый алгоритм. Поддерживаются стандартные алгоритмы: md5, sha1, sha224, sha256, sha384 и sha512. Кроме того, эта функция автоматически поддерживает любые другие алгоритмы хеширования, которые поддерживает OpenSSL.

Если вы хотите получить дайджест в виде шестнадцатеричной строки, примените encode() к результату. Например:

CREATE OR REPLACE FUNCTION sha1(bytea) returns text AS $$
    SELECT encode(digest($1, 'sha1'), 'hex')
$$ LANGUAGE SQL STRICT IMMUTABLE;

F.40.1.2. hmac() #

hmac(data text, key text, type text) returns bytea
hmac(data bytea, key bytea, type text) returns bytea

Вычисляет имитовставку на основе хеша для данных data с ключом key. Параметр type имеет то же значение, что и для digest().

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

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

F.40.2. Функции хеширования пароля #

Функции crypt() и gen_salt() разработаны специально для хеширования паролей. Функция crypt() выполняет хеширование, а gen_salt() подготавливает параметры алгоритма для неё.

Алгоритмы в crypt() отличаются от обычных алгоритмов хеширования MD5 и SHA1 в следующих аспектах:

  1. Они медленные. Так как объём данных невелик, это единственный способ усложнить перебор паролей.

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

  3. Они включают в результат тип алгоритма, что допускает сосуществование паролей, хешированных разными алгоритмами.

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

В Таблице F.30 перечислены алгоритмы, поддерживаемые функцией crypt().

Таблица F.30. Алгоритмы, которые поддерживает crypt()

АлгоритмМакс. длина пароляАдаптивный?Размер соли (бит)Размер результатаОписание
bf72да12860На базе Blowfish, вариация 2a
md5без ограниченийнет4834crypt на базе MD5
xdes8да2420Расширенный DES
des8нет1213Изначальный crypt из UNIX

F.40.2.1. crypt() #

crypt(password text, salt text) returns text

Вычисляет хеш пароля (password) в стиле crypt(3). Для сохранения нового пароля необходимо вызвать gen_salt(), чтобы сгенерировать новое значение соли (salt). Для проверки пароля нужно передать сохранённое значение хеша в параметре salt и проверить, соответствует ли результат сохранённому значению.

Пример установки нового пароля:

UPDATE ... SET pswhash = crypt('new password', gen_salt('md5'));

Пример проверки пароля:

SELECT (pswhash = crypt('entered password', pswhash)) AS pswmatch FROM ... ;

Этот запрос возвращает true, если введённый пароль правильный.

F.40.2.2. gen_salt() #

gen_salt(type text [, iter_count integer ]) returns text

Вычисляет новое случайное значение соли для функции crypt(). Строка соли также говорит crypt(), какой алгоритм использовать.

Параметр type задаёт алгоритм хеширования. Принимаются следующие варианты: des, xdes, md5 и bf.

Параметр iter_count позволяет пользователю указать счётчик итераций для алгоритма, который его принимает. Чем больше это число, тем больше времени уйдёт на вычисление хеша пароля, а значит, тем больше времени понадобится, чтобы взломать его. Хотя со слишком большим значением время вычисления хеша может вырасти до нескольких лет — это вряд ли практично. Когда параметр iter_count опускается, применяется количество итераций по умолчанию. Множество допустимых значений для iter_count зависит от алгоритма, как показано в Таблице F.31.

Таблица F.31. Счётчики итераций для crypt()

АлгоритмПо умолчаниюМин.Макс.
xdes725116777215
bf6431

Для xdes есть дополнительное ограничение: счётчик итераций должен быть нечётным.

При выборе подходящего числа итераций учтите, что оригинальный алгоритм DES crypt был рассчитан так, чтобы выдавать 4 хеша в секунду на компьютерах того времени. Если за секунду будет вычисляться меньше 4 хешей, скорее всего, возникнут определённые неудобства при пользовании. С другой стороны, скорость больше, чем 100 хешей в секунду, вероятно, будет слишком высокой.

В Таблице F.32 дана сводка относительной скорости различных алгоритмов хеширования. В таблице показано, сколько времени уйдёт на перебор всех комбинацией символов в восьмисимвольном пароле, в предположении, что пароль содержит только буквы в нижнем регистре, либо буквы в верхнем и нижнем регистре, а также цифры. В строках crypt-bf числа после косой черты показывают значение параметра iter_count функции gen_salt.

Таблица F.32. Скорости алгоритмов хеширования

АлгоритмХешей/сек.Для [a-z]Для [A-Za-z0-9]Длительность относительно md5
crypt-bf/817924 года3927 лет100k
crypt-bf/736482 года1929 лет50k
crypt-bf/671681 год982 лет25k
crypt-bf/513504188 дней521 лет12.5k
crypt-md517158415 дней41 год1k
crypt-des23221568157.5 минут108 дней7
sha13777427290 минут68 дней4
md5 (хеш)15008550422.5 минут17 дней1

Замечания:

  • Для расчётов использовался процессор Intel Mobile Core i3.

  • Показатели алгоритмов crypt-des и crypt-md5 взяты из вывода теста программы John the Ripper v1.6.38.

  • Показатели md5 получены программой mdcrack 1.2.

  • Показатели sha1 получены программой lcrack-20031130-beta.

  • Показатели crypt-bf получены простой программой, обрабатывающей в цикле 1000 паролей из 8 символов. Таким способом можно показать скорость с разным числом итераций. Для справки: john -test показывает 13506 циклов/с для crypt-bf/5. (Это очень небольшое различие в результатах согласуется с тем фактом, что реализация crypt-bf в pgcrypto не отличается от применяемой в программе John the Ripper.)

Заметьте, что вариант «перепробовать все комбинации» не вполне реалистичен. Обычно перебор паролей производится с применением словарей, которые содержат и обычные слова, и их различные видоизменения. Поэтому даже похожие на слова пароли обычно можно подобрать быстрее, чем за указанное время, тогда как 6-символьный несловесный пароль может избежать взлома. А может и не избежать.

F.40.3. Функции шифрования на базе PGP #

Функции, описанные здесь, реализуют часть стандарта OpenPGP (RFC 4880), относящуюся к шифрованию. Они поддерживают шифрование как с симметричным, так и с закрытым ключом.

Зашифрованное PGP сообщение состоит из 2 частей или пакетов:

  • Пакет, содержащий сеансовый ключ — либо симметричный, либо открытый (в зашифрованном виде).

  • Пакет, содержащий данные, зашифрованные сеансовым ключом.

При шифровании с симметричным ключом (то есть, паролем):

  1. Заданный пароль хешируется по алгоритму String2Key (S2K). Этот алгоритм подобен алгоритмам crypt() — специально замедлен и добавляет случайную соль — но на выход выдаёт двоичный ключ полной длины.

  2. Если требуется отдельный сеансовый ключ, генерируется новый случайный ключ. В противном случае в качестве сеансового будет использоваться непосредственно ключ S2K.

  3. Когда используется непосредственно ключ S2K, в пакет сеансового ключа помещаются только параметры S2K. В противном случае сеансовый ключ шифруется ключом S2K и результат помещается в пакет сеансового ключа.

При шифровании с открытым ключом:

  1. Генерируется новый случайный сеансовый ключ.

  2. Он зашифровывается открытым ключом и помещается в пакет сеансового ключа.

В любом случае данные, которые должны быть зашифрованы, обрабатываются так:

  1. Необязательная подготовка данных: сжатие, перекодировка в UTF-8 и/или преобразование концов строк.

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

  3. В конце добавляется хеш SHA1 случайного префикса и данных.

  4. Всё это шифруется сеансовым ключом и помещается в пакет данных.

F.40.3.1. pgp_sym_encrypt() #

pgp_sym_encrypt(data text, psw text [, options text ]) returns bytea
pgp_sym_encrypt_bytea(data bytea, psw text [, options text ]) returns bytea

Шифрует данные (data) симметричным ключом PGP psw. В options могут передаваться криптографические параметры, описанные ниже.

F.40.3.2. pgp_sym_decrypt() #

pgp_sym_decrypt(msg bytea, psw text [, options text ]) returns text
pgp_sym_decrypt_bytea(msg bytea, psw text [, options text ]) returns bytea

Расшифровывает сообщение, зашифрованное симметричным ключом PGP.

Расшифровывать данные bytea функцией pgp_sym_decrypt запрещено. Это ограничение введено, чтобы не допустить вывода некорректных символьных данных. Расшифровывать изначально текстовые данные с помощью pgp_sym_decrypt_bytea можно без ограничений.

Аргумент options может содержать криптографические параметры, описанные ниже.

F.40.3.3. pgp_pub_encrypt() #

pgp_pub_encrypt(data text, key bytea [, options text ]) returns bytea
pgp_pub_encrypt_bytea(data bytea, key bytea [, options text ]) returns bytea

Зашифровывает данные (data) открытым ключом PGP (key). Если передать этой функции закрытый ключ, она выдаст ошибку.

Аргумент options может содержать криптографические параметры, описанные ниже.

F.40.3.4. pgp_pub_decrypt() #

pgp_pub_decrypt(msg bytea, key bytea [, psw text [, options text ]]) returns text
pgp_pub_decrypt_bytea(msg bytea, key bytea [, psw text [, options text ]]) returns bytea

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

Расшифровывать данные bytea функцией pgp_pub_decrypt запрещено. Это ограничение введено, чтобы не допустить вывода недопустимых символьных данных. Расшифровывать изначально текстовые данные с помощью pgp_pub_decrypt_bytea можно без ограничений.

Аргумент options может содержать криптографические параметры, описанные ниже.

F.40.3.5. pgp_key_id() #

pgp_key_id(bytea) returns text

pgp_key_id извлекает идентификатор ключа из открытого или закрытого ключа PGP. Она также может выдать идентификатор ключа, которым были зашифрованы данные, если ей передаётся зашифрованное сообщение.

Она может выдать два специальных идентификатора ключа:

  • SYMKEY

    Сообщение зашифровано симметричным ключом.

  • ANYKEY

    Сообщение зашифровано открытом ключом, но идентификатор ключа был удалён. Это означает, что вам надо будет перепробовать ключи, чтобы подобрать подходящий. Сама библиотека pgcrypto не генерирует такие сообщения.

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

F.40.3.6. armor(), dearmor() #

armor(data bytea [ , keys text[], values text[] ]) returns text
dearmor(data text) returns bytea

Эти функции переводят двоичные данные в/из формата PGP «ASCII Armor», по сути представляющий собой кодировку Base64 с контрольными суммами и дополнительным форматированием.

Если задаются массивы keys и values, для каждой пары ключ/значения в формат Armor добавляется заголовок Armor. Оба массива должны быть одномерными и иметь одинаковую длину. Задаваемые ключи и значения могут содержать только символы ASCII.

F.40.3.7. pgp_armor_headers #

pgp_armor_headers(data text, key out text, value out text) returns setof record

Функция pgp_armor_headers() извлекает заголовки Armor из параметра data. Она возвращает набор строк с двумя столбцами, key и value. Если в ключах или значениях оказываются символы не ASCII, они воспринимаются как UTF-8.

F.40.3.8. Параметры функций PGP #

Имена параметров подобны принятым в GnuPG. Значение параметра должно задаваться после знака равно; друг от друга параметры отделяются запятыми. Например:

pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')

Все эти параметры, кроме convert-crlf, применяются только к функциям шифрования. Функции расшифровывания получают параметры из данных PGP.

Вероятно, самые интересные параметры — это compress-algo и unicode-mode. Остальные должны иметь достаточно адекватные значения по умолчанию.

F.40.3.8.1. cipher-algo #

Выбирает алгоритм шифрования.

Значения: bf, aes128, aes192, aes256, 3des, cast5
По умолчанию: aes128
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

F.40.3.8.2. compress-algo #

Выбирает алгоритм сжатия. Принимается, только если Postgres Pro собран с zlib.

Значения:
  0 — без сжатия
  1 — сжатие ZIP
  2 — сжатие ZLIB (= ZIP плюс метаданные и CRC блоков)
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

F.40.3.8.3. compress-level #

Определяет уровень сжатия. Чем больше уровень, тем меньшего объёма результат, но длительнее процесс. Значение 0 отключает сжатие.

Значения: 0, 1-9
По умолчанию: 6
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

F.40.3.8.4. convert-crlf #

Определяет, преобразовывать ли \n в \r\n при шифровании и \r\n в \n при дешифровании. В RFC 4880 требуется, чтобы текстовые данные хранились с переводами строк в виде \r\n. Воспользуйтесь этим параметром, чтобы поведение полностью соответствовало RFC.

Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt

F.40.3.8.5. disable-mdc #

Не защищать данные хешем SHA-1. Единственная разумная причина использовать этот параметр — добиться совместимости с древними программами PGP, вышедшими до того, как в RFC 4880 была предусмотрена защита пакетов с SHA-1. Все последние реализации с gnupg.org и pgp.com прекрасно поддерживают это.

Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

F.40.3.8.6. sess-key #

Использовать отдельный сеансовый ключ. Для шифрования с открытым ключом всегда используется отдельный сеансовый ключ; этот параметр предназначен для шифрования с симметричным ключом, которое по умолчанию использует непосредственно ключ S2K.

Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt

F.40.3.8.7. s2k-mode #

Режим алгоритма S2K.

Значения:
  0 — Без соли. Опасно!
  1 — С солью, но с фиксированным числом итераций.
  3 — С переменным числом итераций.
По умолчанию: 3
Применим к: pgp_sym_encrypt

F.40.3.8.8. s2k-count #

Число итераций для алгоритма S2K. Оно должно быть не меньше 1024 и не больше 65011712.

По умолчанию: случайное значение между 65536 и 253952
Применим к: pgp_sym_encrypt, только с s2k-mode=3

F.40.3.8.9. s2k-digest-algo #

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

Значения: md5, sha1
По умолчанию: sha1
Применим к: pgp_sym_encrypt

F.40.3.8.10. s2k-cipher-algo #

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

Значения: bf, aes, aes128, aes192, aes256
По умолчанию: используется cipher-algo
Применим к: pgp_sym_encrypt

F.40.3.8.11. unicode-mode #

Определяет, преобразовывать ли текстовые данные из внутренней кодировки базы данных в UTF-8 и обратно. Если кодировка базы уже UTF-8, перекодировка не производится, но сообщение помечается как UTF-8. Без данного параметра этого не происходит.

Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

F.40.3.9. Формирование ключей PGP с применением GnuPG #

Формирование нового ключа:

gpg --gen-key

Предпочитаемый тип ключей: «DSA and Elgamal».

Для шифрования RSA вы должны создать главный ключ либо DSA, либо RSA только для подписания, а затем добавить подключ для шифрования, выполнив команду gpg --edit-key.

Просмотр списка ключей:

gpg --list-secret-keys

Экспорт открытого ключа в формате «ASCII Armor»:

gpg -a --export KEYID > public.key

Экспорт закрытого ключа в формате «ASCII Armor»:

gpg -a --export-secret-keys KEYID > secret.key

Прежде чем передавать эти ключи функциям PGP, вы должны применить функцию dearmor() к этим ключам. Либо, если вы можете обработать двоичные данные, уберите -a из команды.

Дополнительную информацию вы можете получить в руководстве man gpg, The GNU Privacy Handbook (Руководство GNU по обеспечению конфиденциальности) и другой документации на сайте https://www.gnupg.org/.

F.40.3.10. Ограничения кода PGP #

  • Не поддерживается подписывание. Это также означает, что принадлежность подключа шифрования главному ключу не проверяется.

  • Не поддерживается использование ключа шифрования в качестве главного ключа. Так как подобная практика обычно не приветствуется, это не должно быть проблемой.

  • Нет поддержки нескольких подключей. Это может представляться проблемой, так как такие ключи не редкость. С другой стороны, вы всё равно не должны использовать обычные ключи GPG/PGP с pgcrypto, а должны создать новые, учитывая, что это другой сценарий использования.

F.40.4. Низкоуровневые функции шифрования #

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

  1. Они используют ключ пользователя непосредственно в качестве ключа шифрования.

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

  3. Они рассчитаны на то, что пользователи будут управлять всеми параметрами шифрования самостоятельно, даже вектором инициализации.

  4. Они не рассчитаны на текст.

Поэтому с появлением поддержки шифрования PGP использовать низкоуровневые функции шифрования не рекомендуется.

encrypt(data bytea, key bytea, type text) returns bytea
decrypt(data bytea, key bytea, type text) returns bytea

encrypt_iv(data bytea, key bytea, iv bytea, type text) returns bytea
decrypt_iv(data bytea, key bytea, iv bytea, type text) returns bytea

Эти функции зашифровывают/расшифровывают данные, применяя метод шифрования, заданный параметром type. Строка type имеет следующий формат:

алгоритм [ - режим ] [ /pad: дозаполнение ]

где допустимый алгоритм:

  • bf — Blowfish

  • aes — AES (Rijndael-128, -192 или -256)

допустимый режим:

  • cbc — следующий блок зависит от предыдущего (по умолчанию)

  • ecb — каждый блок шифруется отдельно (только для тестирования)

и допустимое дозаполнение:

  • pkcs — данные могут быть любой длины (по умолчанию)

  • none — размер данных должен быть кратен размеру блока шифра

Так что, например, эти вызовы равнозначны:

encrypt(data, 'fooz', 'bf')
encrypt(data, 'fooz', 'bf-cbc/pad:pkcs')

Для функций encrypt_iv и decrypt_iv параметр iv задаёт начальное значение для режима CBC; для ECB он игнорируется. Оно обрезается или дополняется нулями, если его размер не равен ровно размеру блока. В функциях без этого параметра оно по умолчанию заполняется нулями.

F.40.5. Функции получения случайных данных #

gen_random_bytes(count integer) returns bytea

Возвращает криптографически стойкие случайные байты в количестве count. За один вызов можно получить максимум 1024 байт. Это ограничение предотвращает исчерпание пула энтропии.

gen_random_uuid() returns uuid

Возвращает UUID версии 4 (случайный). (Эта функция в данном модуле считается устаревшей, она вызывает одноимённую функцию ядра.)

F.40.6. Примечания #

F.40.6.1. Конфигурирование #

Модуль pgcrypto настраивается согласно установкам, полученным в главном скрипте configure Postgres Pro. На его конфигурацию влияют аргументы --with-zlib и --with-ssl=openssl.

При компиляции с zlib шифрующие функции PGP могут сжимать данные перед шифрованием.

Для работы pgcrypto требуется OpenSSL. Без этой библиотеки модуль не будет собран и установлен.

Чтобы использовать устаревшие алгоритмы шифрования, такие как DES или Blowfish, при компиляции с OpenSSL версии 3.0.0 и выше, нужно активировать поставщиков этих алгоритмов в файле конфигурации openssl.cnf.

F.40.6.2. Обработка NULL #

Как и положено по стандарту SQL, все эти функции возвращают NULL, если один из аргументов — NULL. Это может угрожать безопасности при неаккуратном использовании.

F.40.6.3. Ограничения безопасности #

Все функции pgcrypto выполняются внутри сервера баз данных. Это означает, что все данные и пароли передаются между функциями pgcrypto и клиентскими приложениями открытым текстом. Поэтому вы должны:

  1. Подключаться локально или использовать подключения SSL.

  2. Доверять и системе, и администратору баз данных.

Если это невозможно, лучше произвести шифрование в клиентском приложении.

Эта реализация не противостоит атакам по сторонним каналам. Например, время, требующееся для выполнения функции дешифрования pgcrypto, будет разным для разного шифротекста заданного размера.

F.40.7. Автор #

Марко Крин

Модуль pgcrypto заимствует код из следующих источников:

АлгоритмАвторИсточник исходного кода
Шифрование DESДэвид Буррен и другиеFreeBSD, libcrypt
Хеширование MD5Пол-Хеннинг КампFreeBSD, libcrypt
Шифрование BlowfishSolar Designerwww.openwall.com