2025年9月25日: PostgreSQL 18 釋出!
支援版本:當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3

F.26. pgcrypto — 加密函式 #

pgcrypto 模組為 PostgreSQL 提供了加密函式。

此模組被認為是受信任的,這意味著非超級使用者也可以在其擁有的資料庫上安裝它,前提是他們具有 CREATE 許可權。

pgcrypto 需要 OpenSSL,如果 PostgreSQL 在構建時未選擇 OpenSSL 支援,則不會安裝它。

F.26.1. 通用雜湊函式 #

F.26.1.1. digest() #

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

計算給定 data 的二進位制雜湊。 type 是要使用的演算法。標準演算法有 md5sha1sha224sha256sha384sha512。此外,OpenSSL 支援的任何摘要演算法都會自動被拾取。

如果你想要十六進位制字串形式的摘要,請在結果上使用 encode()。例如:

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

F.26.1.2. hmac() #

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

使用金鑰 key 計算 data 的雜湊 MAC。 typedigest() 中的相同。

這與 digest() 類似,但如果不知道金鑰,則無法重新計算雜湊。這可以防止有人篡改資料並更改雜湊以匹配的情況。

如果金鑰大於雜湊塊大小,它將首先被雜湊,然後結果將用作金鑰。

F.26.2. 密碼雜湊函式 #

函式 crypt()gen_salt() 是專門為雜湊密碼設計的。 crypt() 執行雜湊,而 gen_salt() 為其準備演算法引數。

crypt() 中的演算法與普通的 MD5 或 SHA-1 雜湊演算法在以下方面有所不同:

  1. 它們很慢。由於資料量很小,這是使密碼暴力破解變得困難的唯一方法。

  2. 它們使用一個稱為 salt 的隨機值,以便具有相同密碼的使用者將具有不同的加密密碼。這也是防止演算法反轉的額外安全措施。

  3. 它們將演算法型別包含在結果中,因此可以用不同演算法雜湊的密碼可以共存。

  4. 其中一些是自適應的 — 這意味著當計算機速度變快時,你可以調整演算法使其變慢,而不會與現有密碼不相容。

表 F.18 列出了 crypt() 函式支援的演算法。

表 F.18. crypt() 支援的演算法

演算法 最大密碼長度 自適應? Salt 位數 輸出長度 描述
bf 72 128 60 基於 Blowfish 的變體 2a
md5 無限制 48 34 基於 MD5 的 crypt
xdes 8 24 20 擴充套件 DES
des 8 12 13 原始 UNIX crypt
sha256crypt 無限制 最多 32 80 改編自公開可用的參考實現 使用 SHA-256 和 SHA-512 的 Unix crypt
sha512crypt 無限制 最多 32 123 改編自公開可用的參考實現 使用 SHA-256 和 SHA-512 的 Unix crypt

F.26.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.26.2.2. gen_salt() #

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

crypt() 生成一個新的隨機 salt 字串。 salt 字串還告訴 crypt() 使用哪種演算法。

引數 type 指定雜湊演算法。可接受的型別有:desxdesmd5bfsha256cryptsha512crypt。後兩種 sha256cryptsha512crypt 是基於 SHA-2 的現代密碼雜湊。

引數 iter_count 允許使用者指定迭代次數(對於具有該引數的演算法)。計數越高,雜湊密碼所需的時間就越長,因此破解密碼所需的時間也越長。儘管計數過高可能需要數年時間才能計算出雜湊值 — 這不太實用。如果省略 iter_count 引數,則使用預設的迭代次數。 iter_count 的允許值取決於演算法,並在 表 F.19 中顯示。

表 F.19. crypt() 的迭代次數

演算法 預設 最小 最大
xdes 725 1 16777215
bf 6 4 31
sha256crypt, sha512crypt 5000 1000 999999999

對於 xdes,還有一個額外的限制,即迭代次數必須是奇數。

為了選擇合適的迭代次數,請注意,原始 DES crypt 被設計成在當時硬體上每秒能進行 4 次雜湊。每秒慢於 4 次雜湊可能會影響可用性。每秒快於 100 次雜湊可能太快了。

表 F.20 概述了不同雜湊演算法的相對速度。該表顯示了嘗試 8 個字元密碼的所有字元組合所需的時間,假設密碼只包含小寫字母,或者包含大寫字母、小寫字母和數字。在 crypt-bf 條目中,斜槓後的數字是 gen_saltiter_count 引數。

sha256cryptsha512crypt 的預設 iter_count5000,這對於現代硬體來說太低了,但可以調整以生成更強的密碼雜湊。否則,這兩種雜湊 sha256cryptsha512crypt 都被認為是安全的。

表 F.20. 雜湊演算法速度

演算法 每秒雜湊次數 對於 [a-z] 對於 [A-Za-z0-9] 相對於 md5 hash 的持續時間
crypt-bf/8 1792 4 年 3927 年 100k
crypt-bf/7 3648 2 年 1929 年 50k
crypt-bf/6 7168 1 年 982 年 25k
crypt-bf/5 13504 188 天 521 年 12.5k
crypt-md5 171584 15 天 41 年 1k
crypt-des 23221568 157.5 分鐘 108 天 7
sha1 37774272 90 分鐘 68 天 4
md5 (hash) 150085504 22.5 分鐘 17 天 1

註釋

  • 使用的機器是 Intel Mobile Core i3。

  • crypt-descrypt-md5 演算法數字來自 John the Ripper v1.6.38 -test 輸出。

  • md5 hash 數字來自 mdcrack 1.2。

  • sha1 數字來自 lcrack-20031130-beta。

  • crypt-bf 數字是透過一個簡單的程式獲得的,該程式迴圈遍歷 1000 個 8 個字元的密碼。這樣就可以顯示不同迭代次數的速度。供參考:john -test 顯示 crypt-bf/5 的速度為 13506 次/秒。(結果之間非常小的差異與 pgcrypto 中的 crypt-bf 實現與 John the Ripper 中使用的實現相同這一事實一致。)

請注意,嘗試所有組合 並不是一個現實的練習。通常密碼破解是藉助字典進行的,這些字典包含常規單詞和它們的各種變體。因此,即使是看起來像單詞的密碼,破解速度也可能比上述數字快得多,而一個 6 個字元的非單詞類密碼可能會逃脫破解。或者不一定。

F.26.3. PGP 加密函式 #

這裡的函式實現了 OpenPGP(RFC 4880)標準的加密部分。支援對稱金鑰和公鑰加密。

PGP 加密訊息由 2 部分組成,或稱為

  • 包含會話金鑰的包 — 加密方式為對稱金鑰或公鑰加密。

  • 包含使用會話金鑰加密的資料的包。

使用對稱金鑰(即密碼)加密時:

  1. 給定的密碼使用 String2Key (S2K) 演算法進行雜湊。這與 crypt() 演算法相當相似 — 故意做得慢並帶有隨機 salt — 但它會生成一個全長二進位制金鑰。

  2. 如果請求單獨的會話金鑰,則會生成一個新的隨機金鑰。否則,S2K 金鑰將直接用作會話金鑰。

  3. 如果 S2K 金鑰要直接使用,則只有 S2K 設定會放入會話金鑰包。否則,會話金鑰將使用 S2K 金鑰進行加密,並放入會話金鑰包。

使用公鑰加密時:

  1. 生成一個新的隨機會話金鑰。

  2. 它使用公鑰進行加密,並放入會話金鑰包。

在任何一種情況下,要加密的資料都按如下方式處理:

  1. 可選的資料處理:壓縮、轉換為 UTF-8 和/或行尾轉換。

  2. 資料前面會加上一個隨機位元組塊。這相當於使用隨機 IV。

  3. 隨機字首和資料的 SHA-1 雜湊會被附加。

  4. 所有這些都使用會話金鑰進行加密,並放入資料包。

F.26.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

使用對稱 PGP 金鑰 psw 加密 dataoptions 引數可以包含選項設定,如下所述。

F.26.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 訊息。

不允許使用 pgp_sym_decrypt 解密 bytea 資料。這是為了避免輸出無效字元資料。使用 pgp_sym_decrypt_bytea 解密原始文字資料是可以的。

引數 options 可以包含選項設定,如下所述。

F.26.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

使用公鑰 PGP key 加密 data。將金鑰提供給此函式將產生錯誤。

引數 options 可以包含選項設定,如下所述。

F.26.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 中提供密碼。如果沒有密碼,但您想指定選項,則需要提供空密碼。

不允許使用 pgp_pub_decrypt 解密 bytea 資料。這是為了避免輸出無效字元資料。使用 pgp_pub_decrypt_bytea 解密原始文字資料是可以的。

引數 options 可以包含選項設定,如下所述。

F.26.3.5. pgp_key_id() #

pgp_key_id(bytea) returns text

pgp_key_id 提取 PGP 公鑰或私鑰的金鑰 ID。或者,如果給定加密訊息,它會給出用於加密資料的金鑰 ID。

它可以返回 2 個特殊的金鑰 ID:

  • SYMKEY

    訊息使用對稱金鑰加密。

  • ANYKEY

    訊息是公鑰加密的,但金鑰 ID 已被移除。這意味著您需要嘗試使用您所有的私鑰來解密它,看看哪個有效。 pgcrypto 本身不會生成此類訊息。

請注意,不同的金鑰可能具有相同的 ID。這種情況很少見,但很正常。客戶端應用程式應然後嘗試用每個金鑰解密,以檢視哪個適合 — 就像處理 ANYKEY 一樣。

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

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

這些函式將二進位制資料包裝/解包到 PGP ASCII-armor 格式,它基本上是帶有 CRC 和附加格式的 Base64。

如果指定了 keysvalues 陣列,則會在 armor 格式中為每個鍵/值對新增一個 armor 頭。兩個陣列都必須是單維的,並且必須具有相同的長度。鍵和值都不能包含任何非 ASCII 字元。

F.26.3.7. pgp_armor_headers #

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

pgp_armor_headers()data 中提取 armor 頭。返回值是一組行,包含兩列:鍵和值。如果鍵或值包含任何非 ASCII 字元,則它們被視為 UTF-8。

F.26.3.8. PGP 函式的選項 #

選項的命名方式與 GnuPG 類似。選項的值應放在等號後面;使用逗號分隔選項。例如:

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

除了 convert-crlf 之外,所有選項僅適用於加密函式。解密函式從 PGP 資料中獲取引數。

最有趣的選項可能是 compress-algounicode-mode。其餘的應該有合理的預設值。

F.26.3.8.1. cipher-algo #

要使用的密碼演算法。


值: bf, aes128, aes192, aes256, 3des, cast5
預設值: aes128
適用於: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.2. compress-algo #

要使用的壓縮演算法。僅當 PostgreSQL 使用 zlib 構建時可用。



  0 - 不壓縮
  1 - ZIP 壓縮
  2 - ZLIB 壓縮 (等於 ZIP 加上 元資料和 塊 CRC)
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.3. compress-level #

壓縮程度。級別越高,壓縮越小但速度越慢。0 停用壓縮。


值: 0, 1-9
預設值: 6
適用於: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.4. convert-crlf #

在加密時是否將 \n 轉換為 \r\n,在解密時是否將 \r\n 轉換為 \nRFC4880 規定文字資料應使用 \r\n 行尾符儲存。使用此選項可獲得完全符合 RFC 的行為。


值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt

F.26.3.8.5. disable-mdc #

不要用 SHA-1 保護資料。使用此選項的唯一好理由是與 PGP 的舊版本相容,這些版本在新增 SHA-1 保護的資料包之前。RFC4880。最近的 gnupg.org 和 pgp.com 軟體可以很好地支援它。


值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.6. sess-key #

使用單獨的會話金鑰。公鑰加密始終使用單獨的會話金鑰;此選項用於對稱金鑰加密,預設情況下它直接使用 S2K 金鑰。


值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt

F.26.3.8.7. s2k-mode #

要使用的 S2K 演算法。



  0 - 無 salt。 危險!
  1 - 有 salt 但迭代次數固定。
  3 - 可變迭代次數。
預設值: 3
適用於: pgp_sym_encrypt

F.26.3.8.8. s2k-count #

要使用的 S2K 演算法的迭代次數。它必須是介於 1024 和 65011712 之間的值(含)。


預設值: 介於 65536 和 253952 之間的隨機值
適用於: pgp_sym_encrypt, 僅當 s2k-mode=3

F.26.3.8.9. s2k-digest-algo #

在 S2K 計算中要使用的摘要演算法。


值: md5, sha1
預設值: sha1
適用於: pgp_sym_encrypt

F.26.3.8.10. s2k-cipher-algo #

用於加密單獨的會話金鑰的密碼。


值: bf, aes, aes128, aes192, aes256
預設值: 使用 cipher-algo
適用於: pgp_sym_encrypt

F.26.3.8.11. unicode-mode #

是否將文字資料從資料庫內部編碼轉換為 UTF-8,然後再轉換回來。如果您的資料庫已經是 UTF-8,則不會進行任何轉換,但訊息將被標記為 UTF-8。沒有此選項則不會。


值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.9. 使用 GnuPG 生成 PGP 金鑰 #

生成新金鑰

gpg --gen-key

首選金鑰型別為DSA 和 Elgamal

對於 RSA 加密,您必須建立一個 DSA 或 RSA 僅用於簽名的金鑰作為主金鑰,然後使用 gpg --edit-key 新增一個 RSA 加密子金鑰。

列出金鑰

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 gpgThe GNU Privacy Handbook 以及 https://www.gnupg.org/ 上的其他文件。

F.26.3.10. PGP 程式碼的侷限性 #

  • 不支援簽名。這也意味著不檢查加密子金鑰是否屬於主金鑰。

  • 不支援將加密金鑰作為主金鑰。由於這種做法通常不被推薦,所以這應該不是問題。

  • 不支援多個子金鑰。這可能看起來像個問題,因為這是常見的做法。另一方面,您不應在 pgcrypto 中使用您的常規 GPG/PGP 金鑰,而應建立新的金鑰,因為使用場景大不相同。

F.26.4. 原始加密函式 #

這些函式僅對資料執行密碼,它們不具備 PGP 加密的任何高階功能。因此,它們存在一些主要問題:

  1. 它們使用使用者金鑰直接作為密碼金鑰。

  2. 它們不提供任何完整性檢查,以檢視加密資料是否被修改。

  3. 它們期望使用者自己管理所有加密引數,甚至包括 IV。

  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 字串的語法是:

algorithm [ - mode ] [ /pad: padding ]

其中 algorithm 是以下之一:

  • bf — Blowfish

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

mode 是以下之一:

  • cbc — 下一個塊取決於前一個塊(預設)

  • cfb — 下一個塊取決於前一個加密塊

  • ecb — 每個塊單獨加密(僅用於測試)

padding 是以下之一:

  • pkcs — 資料可以是任何長度(預設)

  • none — 資料必須是密碼塊大小的倍數

所以,例如,以下是等效的:

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

encrypt_ivdecrypt_iv 中,iv 引數是 CBC 和 CFB 模式的初始值;對於 ECB,它被忽略。如果長度不正好是塊大小,它會被截斷或用零填充。在沒有此引數的函式中,它預設為全零。

F.26.5. 隨機資料函式 #

gen_random_bytes(count integer) returns bytea

返回 count 個加密強度高的隨機位元組。一次最多可以提取 1024 個位元組。這是為了避免耗盡隨機性生成器池。

gen_random_uuid() returns uuid

返回一個版本 4(隨機)UUID。(已廢棄,此函式內部呼叫同名的核心函式。)

F.26.6. OpenSSL 支援函式 #

fips_mode() returns boolean

如果 OpenSSL 以 FIPS 模式執行,則返回 true,否則返回 false

F.26.7. 配置引數 #

有一個配置引數控制 pgcrypto 的行為。

pgcrypto.builtin_crypto_enabled (enum) #

pgcrypto.builtin_crypto_enabled 確定內建加密函式 gen_salt()crypt() 是否可用。將其設定為 off 會停用這些函式。 on (預設)使這些函式正常工作。 fips 會在檢測到 OpenSSL 以 FIPS 模式執行時停用這些函式。

在常規使用中,此引數在 postgresql.conf 中設定,儘管超級使用者可以在其自己的會話中即時更改它。

F.26.8. 注意事項 #

F.26.8.1. 配置 #

pgcrypto 根據主 PostgreSQL configure 指令碼的發現進行自我配置。影響它的選項是 --with-zlib--with-ssl=openssl

當使用 zlib 編譯時,PGP 加密函式能夠先壓縮資料再加密。

pgcrypto 需要 OpenSSL。否則,它將不會被構建或安裝。

當針對 OpenSSL 3.0.0 及更高版本進行編譯時,為了使用 DES 或 Blowfish 等舊式密碼,必須在 openssl.cnf 配置檔案中啟用舊式提供程式。

F.26.8.2. NULL 處理 #

按照 SQL 的標準,如果任何引數為 NULL,則所有函式都返回 NULL。這可能在粗心使用時產生安全風險。

F.26.8.3. 安全限制 #

所有 pgcrypto 函式都在資料庫伺服器內部執行。這意味著所有資料和密碼都在明文中透過 pgcrypto 和客戶端應用程式之間傳輸。因此,您必須:

  1. 本地連線或使用 SSL 連線。

  2. 信任系統和資料庫管理員。

如果不能,則最好在客戶端應用程式內部進行加密。

該實現不能抵抗側通道攻擊。例如,pgcrypto 解密函式完成所需的時間因給定大小的密文而異。

F.26.9. 作者 #

Marko Kreen

pgcrypto 使用以下來源的程式碼:

演算法 作者 來源
DES crypt David Burren 和其他人 FreeBSD libcrypt
MD5 crypt Poul-Henning Kamp FreeBSD libcrypt
Blowfish crypt Solar Designer www.openwall.com

提交更正

如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用此表單報告文件問題。