pgcrypto 模組為 PostgreSQL 提供了加密函式。
此模組被認為是“受信任的”,這意味著非超級使用者也可以在其擁有的資料庫上安裝它,前提是他們具有 CREATE 許可權。
pgcrypto 需要 OpenSSL,如果 PostgreSQL 在構建時未選擇 OpenSSL 支援,則不會安裝它。
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;
hmac() #hmac(data text, key text, type text) returns bytea hmac(data bytea, key bytea, type text) returns bytea
使用金鑰 key 計算 data 的雜湊 MAC。 type 與 digest() 中的相同。
這與 digest() 類似,但如果不知道金鑰,則無法重新計算雜湊。這可以防止有人篡改資料並更改雜湊以匹配的情況。
如果金鑰大於雜湊塊大小,它將首先被雜湊,然後結果將用作金鑰。
函式 crypt() 和 gen_salt() 是專門為雜湊密碼設計的。 crypt() 執行雜湊,而 gen_salt() 為其準備演算法引數。
crypt() 中的演算法與普通的 MD5 或 SHA-1 雜湊演算法在以下方面有所不同:
它們很慢。由於資料量很小,這是使密碼暴力破解變得困難的唯一方法。
它們使用一個稱為 salt 的隨機值,以便具有相同密碼的使用者將具有不同的加密密碼。這也是防止演算法反轉的額外安全措施。
它們將演算法型別包含在結果中,因此可以用不同演算法雜湊的密碼可以共存。
其中一些是自適應的 — 這意味著當計算機速度變快時,你可以調整演算法使其變慢,而不會與現有密碼不相容。
表 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 |
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。
gen_salt() #gen_salt(type text [, iter_count integer ]) returns text
為 crypt() 生成一個新的隨機 salt 字串。 salt 字串還告訴 crypt() 使用哪種演算法。
引數 type 指定雜湊演算法。可接受的型別有:des、xdes、md5、bf、sha256crypt 和 sha512crypt。後兩種 sha256crypt 和 sha512crypt 是基於 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_salt 的 iter_count 引數。
sha256crypt 和 sha512crypt 的預設 iter_count 為 5000,這對於現代硬體來說太低了,但可以調整以生成更強的密碼雜湊。否則,這兩種雜湊 sha256crypt 和 sha512crypt 都被認為是安全的。
表 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-des 和 crypt-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 個字元的非單詞類密碼可能會逃脫破解。或者不一定。
這裡的函式實現了 OpenPGP(RFC 4880)標準的加密部分。支援對稱金鑰和公鑰加密。
PGP 加密訊息由 2 部分組成,或稱為 包
包含會話金鑰的包 — 加密方式為對稱金鑰或公鑰加密。
包含使用會話金鑰加密的資料的包。
使用對稱金鑰(即密碼)加密時:
給定的密碼使用 String2Key (S2K) 演算法進行雜湊。這與 crypt() 演算法相當相似 — 故意做得慢並帶有隨機 salt — 但它會生成一個全長二進位制金鑰。
如果請求單獨的會話金鑰,則會生成一個新的隨機金鑰。否則,S2K 金鑰將直接用作會話金鑰。
如果 S2K 金鑰要直接使用,則只有 S2K 設定會放入會話金鑰包。否則,會話金鑰將使用 S2K 金鑰進行加密,並放入會話金鑰包。
使用公鑰加密時:
生成一個新的隨機會話金鑰。
它使用公鑰進行加密,並放入會話金鑰包。
在任何一種情況下,要加密的資料都按如下方式處理:
可選的資料處理:壓縮、轉換為 UTF-8 和/或行尾轉換。
資料前面會加上一個隨機位元組塊。這相當於使用隨機 IV。
隨機字首和資料的 SHA-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 加密 data。 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 訊息。
不允許使用 pgp_sym_decrypt 解密 bytea 資料。這是為了避免輸出無效字元資料。使用 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
使用公鑰 PGP key 加密 data。將金鑰提供給此函式將產生錯誤。
引數 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 中提供密碼。如果沒有密碼,但您想指定選項,則需要提供空密碼。
不允許使用 pgp_pub_decrypt 解密 bytea 資料。這是為了避免輸出無效字元資料。使用 pgp_pub_decrypt_bytea 解密原始文字資料是可以的。
引數 options 可以包含選項設定,如下所述。
pgp_key_id() #pgp_key_id(bytea) returns text
pgp_key_id 提取 PGP 公鑰或私鑰的金鑰 ID。或者,如果給定加密訊息,它會給出用於加密資料的金鑰 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 格式,它基本上是帶有 CRC 和附加格式的 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() 從 data 中提取 armor 頭。返回值是一組行,包含兩列:鍵和值。如果鍵或值包含任何非 ASCII 字元,則它們被視為 UTF-8。
選項的命名方式與 GnuPG 類似。選項的值應放在等號後面;使用逗號分隔選項。例如:
pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
除了 convert-crlf 之外,所有選項僅適用於加密函式。解密函式從 PGP 資料中獲取引數。
最有趣的選項可能是 compress-algo 和 unicode-mode。其餘的應該有合理的預設值。
要使用的密碼演算法。
值: bf, aes128, aes192, aes256, 3des, cast5
預設值: aes128
適用於: pgp_sym_encrypt, pgp_pub_encrypt
要使用的壓縮演算法。僅當 PostgreSQL 使用 zlib 構建時可用。
值
0 - 不壓縮
1 - ZIP 壓縮
2 - ZLIB 壓縮 (等於 ZIP 加上 元資料和 塊 CRC)
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt
壓縮程度。級別越高,壓縮越小但速度越慢。0 停用壓縮。
值: 0, 1-9
預設值: 6
適用於: pgp_sym_encrypt, pgp_pub_encrypt
在加密時是否將 \n 轉換為 \r\n,在解密時是否將 \r\n 轉換為 \n。RFC4880 規定文字資料應使用 \r\n 行尾符儲存。使用此選項可獲得完全符合 RFC 的行為。
值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt
不要用 SHA-1 保護資料。使用此選項的唯一好理由是與 PGP 的舊版本相容,這些版本在新增 SHA-1 保護的資料包之前。RFC4880。最近的 gnupg.org 和 pgp.com 軟體可以很好地支援它。
值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt
使用單獨的會話金鑰。公鑰加密始終使用單獨的會話金鑰;此選項用於對稱金鑰加密,預設情況下它直接使用 S2K 金鑰。
值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt
要使用的 S2K 演算法。
值
0 - 無 salt。 危險!
1 - 有 salt 但迭代次數固定。
3 - 可變迭代次數。
預設值: 3
適用於: pgp_sym_encrypt
要使用的 S2K 演算法的迭代次數。它必須是介於 1024 和 65011712 之間的值(含)。
預設值: 介於 65536 和 253952 之間的隨機值
適用於: pgp_sym_encrypt, 僅當 s2k-mode=3
用於加密單獨的會話金鑰的密碼。
值: bf, aes, aes128, aes192, aes256
預設值: 使用 cipher-algo
適用於: pgp_sym_encrypt
是否將文字資料從資料庫內部編碼轉換為 UTF-8,然後再轉換回來。如果您的資料庫已經是 UTF-8,則不會進行任何轉換,但訊息將被標記為 UTF-8。沒有此選項則不會。
值: 0, 1
預設值: 0
適用於: pgp_sym_encrypt, pgp_pub_encrypt
生成新金鑰
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 gpg、The GNU Privacy Handbook 以及 https://www.gnupg.org/ 上的其他文件。
不支援簽名。這也意味著不檢查加密子金鑰是否屬於主金鑰。
不支援將加密金鑰作為主金鑰。由於這種做法通常不被推薦,所以這應該不是問題。
不支援多個子金鑰。這可能看起來像個問題,因為這是常見的做法。另一方面,您不應在 pgcrypto 中使用您的常規 GPG/PGP 金鑰,而應建立新的金鑰,因為使用場景大不相同。
這些函式僅對資料執行密碼,它們不具備 PGP 加密的任何高階功能。因此,它們存在一些主要問題:
它們使用使用者金鑰直接作為密碼金鑰。
它們不提供任何完整性檢查,以檢視加密資料是否被修改。
它們期望使用者自己管理所有加密引數,甚至包括 IV。
它們不處理文字。
因此,隨著 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_iv 和 decrypt_iv 中,iv 引數是 CBC 和 CFB 模式的初始值;對於 ECB,它被忽略。如果長度不正好是塊大小,它會被截斷或用零填充。在沒有此引數的函式中,它預設為全零。
gen_random_bytes(count integer) returns bytea
返回 count 個加密強度高的隨機位元組。一次最多可以提取 1024 個位元組。這是為了避免耗盡隨機性生成器池。
gen_random_uuid() returns uuid
返回一個版本 4(隨機)UUID。(已廢棄,此函式內部呼叫同名的核心函式。)
有一個配置引數控制 pgcrypto 的行為。
pgcrypto.builtin_crypto_enabled (enum) #pgcrypto.builtin_crypto_enabled 確定內建加密函式 gen_salt() 和 crypt() 是否可用。將其設定為 off 會停用這些函式。 on (預設)使這些函式正常工作。 fips 會在檢測到 OpenSSL 以 FIPS 模式執行時停用這些函式。
在常規使用中,此引數在 postgresql.conf 中設定,儘管超級使用者可以在其自己的會話中即時更改它。
pgcrypto 根據主 PostgreSQL configure 指令碼的發現進行自我配置。影響它的選項是 --with-zlib 和 --with-ssl=openssl。
當使用 zlib 編譯時,PGP 加密函式能夠先壓縮資料再加密。
pgcrypto 需要 OpenSSL。否則,它將不會被構建或安裝。
當針對 OpenSSL 3.0.0 及更高版本進行編譯時,為了使用 DES 或 Blowfish 等舊式密碼,必須在 openssl.cnf 配置檔案中啟用舊式提供程式。
按照 SQL 的標準,如果任何引數為 NULL,則所有函式都返回 NULL。這可能在粗心使用時產生安全風險。
Marko Kreen <markokr@gmail.com>
pgcrypto 使用以下來源的程式碼:
| 演算法 | 作者 | 來源 |
|---|---|---|
| DES crypt | David Burren 和其他人 | FreeBSD libcrypt |
| MD5 crypt | Poul-Henning Kamp | FreeBSD libcrypt |
| Blowfish crypt | Solar Designer | www.openwall.com |
如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用此表單報告文件問題。