pgcrypto

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

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



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

digest()

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

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

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

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

hmac()

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

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

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

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



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

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

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

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

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

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

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

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

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

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

crypt()

crypt(password text, salt text) returns text

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

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

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

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

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

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


gen_salt()

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

Генерирует новую случайную строку соли для использования в crypt(). Строка соли также сообщает crypt(), какой алгоритм применять.

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

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

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

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

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

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

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

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

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

Примечания:

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

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

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

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

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

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



Функции шифрования на основе 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. Все это шифруется сеансовым ключом и помещается в пакет данных.


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 может содержать криптографические параметры, описанные ниже.


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 может содержать криптографические параметры, описанные ниже.


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 может содержать криптографические параметры, описанные ниже.


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 может содержать криптографические параметры, описанные ниже.


pgp_key_id()

pgp_key_id(bytea) returns text

Функция pgp_key_id извлекает ID ключа из открытого или секретного ключа PGP. Либо выдает ID ключа, который использовался для шифрования данных, если ей передается зашифрованное сообщение.

Она может возвращать 2 специальных ID ключа:

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

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

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


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.


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.


Параметры функций PGP

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

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

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

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

cipher-algo
Выбирает алгоритм шифрования.
Значения: bf, aes128, aes192, aes256 (только OpenSSL: 3des, cast5)
По умолчанию: aes128
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

compress-algo
Выбирает алгоритм сжатия. Доступен, только если QHB была собрана с zlib.
Значения:
0 - без сжатия
1 - сжатие ZIP
2 - сжатие ZLIB (= ZIP плюс метаданные и контрольные суммы блоков)
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

compress-level
Определяет уровень сжатия. При более высоких уровнях на выходе получается меньший объем, но сам процесс замедляется. Значение 0 выключает сжатие.
Значения: 0, 1-9
По умолчанию: 6
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

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

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

sess-key
Использовать отдельные сеансовый ключ. Для шифрования с открытым ключом всегда используется отдельный сеансовый ключ; этот параметр предназначен для шифрования с симметричным ключом, которое по умолчанию использует непосредственно ключ S2K.
Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt

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

s2k-count
Выбирает число итераций для алгоритма S2K. Значение должно быть от 1024 до 65011712 включительно.
По умолчанию: Случайное значение от 65536 до 253952
Применим к: pgp_sym_encrypt, только с s2k-mode=3

s2k-digest-algo
Выбирает алгоритм хеширования, который будет использоваться для вычисления S2K.
Значения: md5, sha1
По умолчанию: sha1
Применим к: pgp_sym_encrypt

s2k-cipher-algo
Выбирает шифр, который будет использоваться для шифрования отдельного сеансового ключа.
Значения: bf, aes, aes128, aes192, aes256
По умолчанию: используется cipher-algo
Применим к: pgp_sym_encrypt

unicode-mode
Определяет, преобразовывать ли текстовые данные из внутренней кодировки базы данных в UTF-8 и обратно. Если кодировка базы уже UTF-8, преобразование не производится, но сообщение будет помечено как UTF-8. Без этого параметра этого не произойдет.
Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt


Генерирование ключей PGP с помощью GnuPG

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

gpg --gen-key

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

Для шифрования RSA необходимо создать в качестве главного либо DSA, либо RSA ключ только для подписания, а затем добавить подключ для шифрования 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/.


Ограничения кода PGP

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

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

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



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

Эти функции выполняют только шифрование данных; у них нет расширенных возможностей шифрования 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 or -256)

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

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

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

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

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

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

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

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

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



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

gen_random_bytes(count integer) returns bytea

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

gen_random_uuid() returns uuid

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



Примечания

Конфигурирование

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

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

При компиляции с OpenSSL будут доступны дополнительные алгоритмы. Кроме того, функции шифрования с открытым ключом будут работать быстрее, поскольку OpenSSL содержит более оптимизированные функции для работы с большими числами (BIGNUM).

Таблица 19. Обзор функциональности с OpenSSL и без него

ФункциональностьВстроеннаяС OpenSSL
MD5дада
SHA1дада
SHA224/256/384/512дада
Другие алгоритмы выборки сообщенийнетда (см. Примечание)
Blowfishдада
AESдада
DES/3DES/CAST5нетда
Низкоуровневое шифрованиедада
Симметричное шифрование PGPдада
Шифрование PGP с открытым ключомдада

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

Примечание:

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

Обработка NULL

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


Ограничения безопасности

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

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

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

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

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


Дополнительная литература


Техническая справка



Автор

Марко Крин (Marko Kreen), markokr@gmail.com

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

АлгоритмАвторИсточник исходного кода
Шифрование DESДэвид Вуррен (David Burren) и др.FreeBSD, libcrypt
Шифрование MD5Пол-Хеннинг Камп (Poul-Henning Kamp)FreeBSD, libcrypt
Шифрование BlowfishSolar Designerwww.openwall.com
Шифр BlowfishСаймон Тэтем (Simon Tatham)PuTTY
Шифр RijndaelБрайан Глэдмен (Brian Gladman)OpenBSD, sys/crypto
Хеш MD5 и SHA1Проект WIDEKAME, kame/sys/crypto
SHA256/384/512Аарон Д. Гиффорд (Aaron D. Gifford)OpenBSD, sys/crypto
Математика BIGNUMМайкл Дж. Фромбергер (Michael J. Fromberger)dartmouth.edu/~sting/sw/imath