pgcrypto
モジュールはPostgreSQL用の暗号関数を提供します。
このモジュールは「trusted」と見なされます。つまり、現在のデータベースに対して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がサポートするダイジェストアルゴリズムが自動的に選択されます。
ダイジェストを16進数表記の文字列としたい場合は、結果に対して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やSHA1のようなハッシュ処理アルゴリズムと異なります。
低速です。 データ量が少ないためパスワード総当たり攻撃に対して頑健にする唯一の方法です。
結果にはソルトというランダムな値が含まれます。 このため同じパスワードのユーザでも異なった暗号化パスワードを持ちます。 これはアルゴリズムの逆処理に対する追加の防御です。
結果内にアルゴリズムの種類が含まれます。 このため異なるアルゴリズムでハッシュ化したパスワードが混在可能です。
一部は適応型です。 つまり、コンピュータが高速になったとしても、既存のパスワードとの互換性を損なうことなくアルゴリズムを低速に調整することができます。
crypt()
関数がサポートするアルゴリズムを表 F.16に列挙します。
表F.16 crypt()
がサポートするアルゴリズム
アルゴリズム | パスワード最大長 | 適応型かどうか | ソルトビット長 | 出力長 | 説明 |
---|---|---|---|---|---|
bf | 72 | はい | 128 | 60 | Blowfishベース、2a版 |
md5 | 無制限 | いいえ | 48 | 34 | MD5ベースの暗号 |
xdes | 8 | はい | 24 | 20 | 拡張DES |
des | 8 | いいえ | 12 | 13 | 元来の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()
で使用するランダムなソルト文字列を新規に生成します。
また、このソルト文字列はcrypt()
にどのアルゴリズムを使用するかを通知します。
type
パラメータはハッシュ化アルゴリズムを指定します。
受付可能な種類は、des
、xdes
、md5
、bf
です。
繰り返し回数を持つアルゴリズムでは、ユーザはiter_count
パラメータを使用して繰り返し回数を指定できます。
指定する回数を高くすれば、パスワードのハッシュ化にかかる時間が長くなり、それを破るための時間も長くなります。
しかし、あまりに多くの回数を指定すると、ハッシュ計算にかかる時間は数年に渡ってしまう可能性があります。
これは実用的ではありません。
iter_count
パラメータを省略した場合、デフォルトの繰り返し回数が使用されます。
iter_count
で受け付けられる値はアルゴリズムに依存し、表 F.17に示す通りです。
表F.17 crypt()
用の繰り返し回数
アルゴリズム | デフォルト | 最小 | 最大 |
---|---|---|---|
xdes | 725 | 1 | 16777215 |
bf | 6 | 4 | 31 |
xdes
の場合は他にも、回数が奇数でなければならないという制限があります。
適切な繰り返し回数を選択するために、元々のDES暗号は当時のハードウェアで1秒あたり4個のハッシュを持つことができるように設計されたことを考えてください。 毎秒4ハッシュより遅いと、おそらく使い勝手が悪いでしょう。 毎秒100ハッシュより速いというのは、十中八九、あまりにも速すぎるでしょう。
ハッシュ化アルゴリズム別に相対的な速度に関する概要を表 F.18にまとめました。
この表は、8文字のパスワード内のすべての文字の組合せを取るためにかかる時間を示します。
また、すべて小文字の英字のみのパスワードである場合と大文字小文字が混在した英字と数字のパスワードの場合を仮定します。
crypt-bf
の項では、スラッシュの後の数値はgen_salt
のiter_count
です。
表F.18 ハッシュアルゴリズムの速度
アルゴリズム | 1秒当たりのハッシュ数 | [a-z] の場合 | [A-Za-z0-9] の場合 | md5ハッシュ を単位とした持続期間 |
---|---|---|---|---|
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 (ハッシュ) | 150085504 | 22.5分 | 17日 | 1 |
注意:
Intel Mobile Core i3のマシンを使用しました。
crypt-des
およびcrypt-md5
アルゴリズムの数値はJohn the Ripper v1.6.38の-test
出力から得たものです。
md5ハッシュ
の数値はmdcrack 1.2のものです。
sha1
の数値はlcrack-20031130-betaのものです。
crypt-bf
の数は、1000個の8文字パスワードをループする単純なプログラムを使用して得たものです。
こうして、異なる回数の速度を示すことができました。
参考までに、john -test
はcrypt-bf/5
で13506 loops/secでした。
(結果の差異が非常に小さいことは、pgcrypto
におけるcrypt-bf
実装がJohn the Ripperで使用されるものと同じであるという事実と一致します。)
「すべての組み合わせを試行する」ことは現実的な行使ではありません。 通常パスワード推定は、普通の単語とその変形の両方を含む辞書を使用して行われます。 ですので、いささかなりとも言葉に似たパスワードは上で示した数値よりも速く推定されます。 また6文字の単語に似ていないパスワードは推定を免れるかもしれませんし、免れないかもしれません。
ここで示す関数はOpenPGP(RFC 4880)標準の暗号処理部分を実装します。 対称鍵および公開鍵暗号化がサポートされます。
暗号化されたPGPメッセージは次の2つの部品(またはパケット)から構成されます。
セッションキーを含むパケット。 対称鍵または公開鍵で暗号化されています。
セッションキーにより暗号化されたデータを含むパケット。
対称鍵(つまりパスワード)で暗号化する場合
与えられたパスワードはString2Key(S2K)アルゴリズムでハッシュ化されます。
これはどちらかというとcrypt()
アルゴリズムと似て、意図的に低速で、かつランダムなソルトを使用します。
しかし、全長のバイナリキーを生成します。
分離したセッションキーが要求された場合、新しいランダムなキーが生成されます。 さもなくば、S2Kキーがそのままセッションキーとして使用されます。
S2Kキーがそのまま使用される場合、S2K設定のみがセッションキーパケットに格納されます。 さもなくば、セッションキーはS2Kキーで暗号化され、セッションキーパケットに格納されます。
公開鍵で暗号化する場合
新しいランダムなセッションキーが生成されます。
これは公開鍵を使用して暗号化され、セッションキーパケットに格納されます。
どちらの場合でもデータ暗号化は以下のように処理されます。
省略可能なデータ操作として、圧縮、UTF-8への変換、改行の変換があります。
データの前にはランダムなバイト数のブロックが付きます。 これはrandom IVを使用する場合と同じです。
ランダムな前置ブロックとデータのSHA1ハッシュが後に付けられます。
これをすべてセッションキーで暗号化し、データパケットに格納します。
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アーマー形式にデータを隠す、または、データを取り出します。 ASCIIアーマーは基本的にCRC付きのBASE64という形式で、追加のフォーマットがあります。
keys
とvalues
の配列が指定された場合には、各キーと値の対に対してアーマーヘッダがアーマー形式に追加されます。
どちらの配列も1次元で、同じ長さでなければなりません。
keysとvaluesに非ASCII文字を含めることはできません。
pgp_armor_headers
pgp_armor_headers(data text, key out text, value out text) returns setof record
pgp_armor_headers()
はdata
からアーマーヘッダを取り出します。
戻り値はキーと値の2つの列からなる行の集合です。
もしキーや値に非アスキー文字が含まれていれば、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
に変換するかどうか。
RFC 4880では、テキストデータは改行コードとして\r\n
を使用して格納すべきであると規定されています。
完全にRFC準拠の動作を行いたければ、これを使用してください。
値: 0, 1
デフォルト: 0
適用範囲: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt
データをSHA-1で保護しません。 このオプションを使用することが良い唯一の理由は、SHA-1で保護されたパケットがRFC 4880に追加される前の、古いPGP製品との互換性を得るためです。 最近のgnupg.orgおよびpgp.comのソフトウェアではこれを正しくサポートしています。
値: 0, 1
デフォルト: 0
適用範囲: pgp_sym_encrypt, pgp_pub_encrypt
分離したセッションキーを使用します。 公開鍵暗号では常に分離したセッションキーを使用します。 このオプションは対称鍵暗号向けのもので、デフォルトではS2Kキーをそのまま使用します。
値: 0, 1
デフォルト: 0
適用範囲: pgp_sym_encrypt
使用するS2Kアルゴリズム。
値:
0 - ソルト無。危険です!
1 - ソルト有。固定繰り返し回数。
3 - 可変繰り返し回数。
デフォルト: 3
適用範囲: pgp_sym_encrypt
使用するS2Kアルゴリズムで使う繰り返しの回数。 1024以上、65011712以下の値でなければなりません。
デフォルト: 65536から253952までの乱数値
適用範囲: s2k-mode=3と指定した時のpgp_sym_encrypt
S2K計算で使用するダイジェストアルゴリズム。
値: md5, sha1
デフォルト: sha1
適用範囲: pgp_sym_encrypt
分離したセッションキーの暗号化に使用する暗号。
値: 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アーマー形式で公開鍵をエクスポートします。
gpg -a --export KEYID > public.key
ASCIIアーマー形式の秘密鍵をエクスポートします。
gpg -a --export-secret-keys KEYID > secret.key
PGP関数にこれらのキーを渡す前にdearmor()
を使用する必要があります。
バイナリデータを扱うことができる場合、コマンドから-a
フラグを省略することができます。
詳細はman gpg
、The GNU Privacy Handbook、https://www.gnupg.org/サイトの各種文書を参照してください。
署名に関するサポートはありません。 これはまた、暗号化副キーがマスタキーに属しているかどうか検査しないことを意味します。
マスタキーとして暗号化キーをサポートしません。 一般的にこうした状況は現実的ではありませんので、問題にならないはずです。
複数の副キーに関するサポートはありません。
よくありますので、これは問題になりそうに見えます。
一方、通常のGPG/PGPキーをpgcrypto
で使用すべきではありません。
使用する状況が多少異なりますので新しく作成してください。
これらの関数はデータ全体を暗号化するためだけに実行します。 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
は以下の1つです。
bf
— Blowfish
aes
— AES (Rijndael-128, -192 or -256)
またmode
は以下の1つです。
cbc
— 次のブロックは前ブロックに依存します(デフォルト)
ecb
— 各ブロックは独自に暗号化されます(試験用途のみ)
padding
は以下の1つです。
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
バージョン4(ランダムな)UUIDを返します。 (廃れたものです。この関数は内部では同名のコア関数を呼び出します。)
pgcrypto
は自身で主PostgreSQLのconfigure
スクリプトの検出結果に従って構築します。
構築に影響するオプションは--with-zlib
と--with-openssl
です。
ZLIB付きでコンパイルされた場合、PGP暗号化関数は暗号化前にデータを圧縮することができます。
pgcrypto
はOpenSSLを必要とします。
そうでなければ、構築もインストールもされません。
OpenSSL 3.0.0とそれ以降に対してコンパイルされた場合、DESやBlowfishのような古い暗号を使うためにはopenssl.cnf
設定ファイルでレガシープロバイダを有効にしなければなりません。
標準SQLの通り、引数のいずれかがNULLの場合、すべての関数はNULLを返します。 注意せずに使用すると、これがセキュリティ上の問題になるかもしれません。
pgcrypto
の関数はすべてデータベースサーバ内部で実行されます。
これは、pgcrypto
とクライアントアプリケーションとの間でやり取りされるデータはすべて平文であることを意味します。
したがって、以下を行う必要があります。
ローカルまたはSSL接続で接続
システム管理者およびデータベース管理者を信頼
これらが不可能であれば、クライアントアプリケーション内で暗号化する方が望まれます。
実装はサイドチャネル攻撃に耐えられません。
例えば、pgcrypto
復号関数が完了するのに掛かる時間は、一定の長さの暗号文に対して一様ではありません。
https://www.gnupg.org/gph/en/manual.html
GNUプライバシーハンドブック
https://www.openwall.com/crypt/
blowfish暗号アルゴリズムの説明
https://www.iusmentis.com/security/passphrasefaq/
優れたパスワードの選び方
http://world.std.com/~reinhold/diceware.html
パスワード決定に関する面白い考え
http://www.interhack.net/people/cmcurtin/snake-oil-faq.html
良い暗号、悪い暗号に関する説明
https://tools.ietf.org/html/rfc4880
OpenPGPメッセージフォーマット
https://tools.ietf.org/html/rfc1321
MD5 メッセージダイジェストアルゴリズム
https://tools.ietf.org/html/rfc2104
HMAC: Keyed-Hashing for Message Authentication.
https://www.usenix.org/legacy/events/usenix99/provos.html
DES暗号、MD5暗号、bcryptアルゴリズムの比較
https://en.wikipedia.org/wiki/Fortuna_(PRNG)
Fortuna CSPRNGの説明
Linux用Jean-Luc Cooke Fortunaに基づく/dev/random
ドライバ
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 |