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