F.28. pgcrypto — 暗号関数 #

pgcryptoモジュールはPostgreSQL用の暗号関数を提供します。

このモジュールはtrustedと見なされます。つまり、現在のデータベースに対してCREATE権限を持つ非スーパーユーザがインストールできます。

pgcryptoはOpenSSLを必要とし、PostgreSQLの構築時にOpenSSLサポートが選択されなかった場合にはインストールされません。

F.28.1. 汎用ハッシュ関数 #

F.28.1.1. digest() #

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

与えられたdataのバイナリハッシュを計算します。 typeは使用するアルゴリズムです。 標準アルゴリズムはmd5sha1sha224sha256sha384、およびsha512です。 さらに、OpenSSLがサポートするダイジェストアルゴリズムが自動的に選択されます。

ダイジェストを16進数表記の文字列としたい場合は、結果に対してencode()を使用してください。 以下に例を示します。

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

F.28.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.28.2. パスワードハッシュ化関数 #

crypt()およびgen_salt()関数は特にパスワードのハッシュ化のために設計されたものです。 crypt()がハッシュ処理を行い、gen_salt()はハッシュ処理用のアルゴリズム上のパラメータを準備します。

crypt()アルゴリズムは、以下の点で通常のMD5やSHA1のようなハッシュ処理アルゴリズムと異なります。

  1. 低速です。 データ量が少ないためパスワード総当たり攻撃に対して頑健にする唯一の方法です。

  2. 結果にはソルトというランダムな値が含まれます。 このため同じパスワードのユーザでも異なった暗号化パスワードを持ちます。 これはアルゴリズムの逆処理に対する追加の防御です。

  3. 結果内にアルゴリズムの種類が含まれます。 このため異なるアルゴリズムでハッシュ化したパスワードが混在可能です。

  4. 一部は適応型です。 つまり、コンピュータが高速になったとしても、既存のパスワードとの互換性を損なうことなくアルゴリズムを低速に調整することができます。

crypt()関数がサポートするアルゴリズムを表 F.18に列挙します。

表F.18 crypt()がサポートするアルゴリズム

アルゴリズムパスワード最大長適応型かどうかソルトビット長出力長説明
bf72はい12860Blowfishベース、2a版
md5無制限いいえ4834MD5ベースの暗号
xdes8はい2420拡張DES
des8いいえ1213元来のUNIX crypt

F.28.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.28.2.2. gen_salt() #

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

crypt()で使用するランダムなソルト文字列を新規に生成します。 また、このソルト文字列はcrypt()にどのアルゴリズムを使用するかを通知します。

typeパラメータはハッシュ化アルゴリズムを指定します。 受付可能な種類は、desxdesmd5bfです。

繰り返し回数を持つアルゴリズムでは、ユーザはiter_countパラメータを使用して繰り返し回数を指定できます。 指定する回数を高くすれば、パスワードのハッシュ化にかかる時間が長くなり、それを破るための時間も長くなります。 しかし、あまりに多くの回数を指定すると、ハッシュ計算にかかる時間は数年に渡ってしまう可能性があります。 これは実用的ではありません。 iter_countパラメータを省略した場合、デフォルトの繰り返し回数が使用されます。 iter_countで受け付けられる値はアルゴリズムに依存し、表 F.19に示す通りです。

表F.19 crypt()用の繰り返し回数

アルゴリズムデフォルト最小最大
xdes725116777215
bf6431

xdesの場合は他にも、回数が奇数でなければならないという制限があります。

適切な繰り返し回数を選択するために、元々のDES暗号は当時のハードウェアで1秒あたり4個のハッシュを持つことができるように設計されたことを考えてください。 毎秒4ハッシュより遅いと、おそらく使い勝手が悪いでしょう。 毎秒100ハッシュより速いというのは、十中八九、あまりにも速すぎるでしょう。

ハッシュ化アルゴリズム別に相対的な速度に関する概要を表 F.20にまとめました。 この表は、8文字のパスワード内のすべての文字の組合せを取るためにかかる時間を示します。 また、すべて小文字の英字のみのパスワードである場合と大文字小文字が混在した英字と数字のパスワードの場合を仮定します。 crypt-bfの項では、スラッシュの後の数値はgen_saltiter_countです。

表F.20 ハッシュアルゴリズムの速度

アルゴリズム1秒当たりのハッシュ数[a-z]の場合[A-Za-z0-9]の場合md5ハッシュを単位とした持続期間
crypt-bf/817924年3927年100k
crypt-bf/736482年1929年50k
crypt-bf/671681年982年25k
crypt-bf/513504188日521年12.5k
crypt-md517158415日41年1k
crypt-des23221568157.5分108日7
sha13777427290分68日4
md5(ハッシュ)15008550422.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 -testcrypt-bf/5で13506 loops/secでした。 (結果の差異が非常に小さいことは、pgcryptoにおけるcrypt-bf実装がJohn the Ripperで使用されるものと同じであるという事実と一致します。)

すべての組み合わせを試行することは現実的な行使ではありません。 通常パスワード推定は、普通の単語とその変形の両方を含む辞書を使用して行われます。 ですので、いささかなりとも言葉に似たパスワードは上で示した数値よりも速く推定されます。 また6文字の単語に似ていないパスワードは推定を免れるかもしれませんし、免れないかもしれません。

F.28.3. PGP暗号化関数 #

ここで示す関数はOpenPGP(RFC 4880)標準の暗号処理部分を実装します。 対称鍵および公開鍵暗号化がサポートされます。

暗号化されたPGPメッセージは次の2つの部品(またはパケット)から構成されます。

  • セッションキーを含むパケット。 対称鍵または公開鍵で暗号化されています。

  • セッションキーにより暗号化されたデータを含むパケット。

対称鍵(つまりパスワード)で暗号化する場合

  1. 与えられたパスワードはString2Key(S2K)アルゴリズムでハッシュ化されます。 これはどちらかというとcrypt()アルゴリズムと似て、意図的に低速で、かつランダムなソルトを使用します。 しかし、全長のバイナリキーを生成します。

  2. 分離したセッションキーが要求された場合、新しいランダムなキーが生成されます。 さもなくば、S2Kキーがそのままセッションキーとして使用されます。

  3. S2Kキーがそのまま使用される場合、S2K設定のみがセッションキーパケットに格納されます。 さもなくば、セッションキーはS2Kキーで暗号化され、セッションキーパケットに格納されます。

公開鍵で暗号化する場合

  1. 新しいランダムなセッションキーが生成されます。

  2. これは公開鍵を使用して暗号化され、セッションキーパケットに格納されます。

どちらの場合でもデータ暗号化は以下のように処理されます。

  1. 省略可能なデータ操作として、圧縮、UTF-8への変換、改行の変換があります。

  2. データの前にはランダムなバイト数のブロックが付きます。 これはrandom IVを使用する場合と同じです。

  3. ランダムな前置ブロックとデータのSHA1ハッシュが後に付けられます。

  4. これをすべてセッションキーで暗号化し、データパケットに格納します。

F.28.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キーpswdataを暗号化します。 optionsパラメータには後述のオプション設定を含めることができます。

F.28.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_decryptbytea型のデータを復号することはできません。 これは無効な文字データの出力を防止するためです。 元のテキストのデータをpgp_sym_decrypt_byteaで復号することが正しい方法です。

optionsパラメータには後述のオプション設定を含めることができます。

F.28.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キーkeydataを暗号化します。 この関数に秘密キーを与えるとエラーになります。

optionsパラメータには後述のオプション設定を含めることができます。

F.28.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_decryptbytea型のデータを復号することはできません。 これは無効な文字データの出力を防止するためです。 元のテキストのデータをpgp_pub_decrypt_byteaで復号することが正しい方法です。

optionsパラメータには後述のオプション設定を含めることができます。

F.28.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.28.3.6. armor(), dearmor() #

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

PGPのASCIIアーマー形式にデータを隠す、または、データを取り出します。 ASCIIアーマーは基本的にCRC付きのBASE64という形式で、追加のフォーマットがあります。

keysvaluesの配列が指定された場合には、各キーと値の対に対してアーマーヘッダがアーマー形式に追加されます。 どちらの配列も1次元で、同じ長さでなければなりません。 keysとvaluesに非ASCII文字を含めることはできません。

F.28.3.7. pgp_armor_headers #

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

pgp_armor_headers()dataからアーマーヘッダを取り出します。 戻り値はキーと値の2つの列からなる行の集合です。 もしキーや値に非アスキー文字が含まれていれば、UTF-8として扱われます。

F.28.3.8. PGP関数用のオプション #

オプションはGnuPGに似せて命名しています。 オプションの値は等号記号の後に指定しなければなりません。 複数のオプションはカンマで区切ってください。 以下に例を示します。

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

convert-crlfを除くすべてのオプションは暗号化関数にのみ適用可能です。 復号関数はPGPデータからこうしたパラメータを入手します。

もっとも興味深いオプションはおそらくcompress-algounicode-modeでしょう。 残りはデフォルトで問題ないはずです。

F.28.3.8.1. cipher-algo #

使用する暗号アルゴリズム。



値: bf, aes128, aes192, aes256, 3des, cast5
デフォルト: aes128
適用範囲: pgp_sym_encrypt, pgp_pub_encrypt

F.28.3.8.2. compress-algo #

使用する圧縮アルゴリズム。 PostgreSQLがzlib付きで構築されている場合のみ利用可能です。



値:
 0 - 非圧縮
 1 - ZIP圧縮
 2 - ZLIB圧縮 (ZIPにメタデータとブロックCRCを加えたもの)
デフォルト: 0
適用範囲: pgp_sym_encrypt, pgp_pub_encrypt

F.28.3.8.3. compress-level #

どの程度圧縮するかです。 レベルが大きい程小さくなりますが、低速になります。 0は圧縮を無効にします。



値: 0, 1-9
デフォルト: 6
適用範囲: pgp_sym_encrypt, pgp_pub_encrypt

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

F.28.3.8.5. disable-mdc #

データをSHA-1で保護しません。 このオプションを使用することが良い唯一の理由は、SHA-1で保護されたパケットがRFC 4880に追加される前の、古いPGP製品との互換性を得るためです。 最近のgnupg.orgおよびpgp.comのソフトウェアではこれを正しくサポートしています。



値: 0, 1
デフォルト: 0
適用範囲: pgp_sym_encrypt, pgp_pub_encrypt

F.28.3.8.6. sess-key #

分離したセッションキーを使用します。 公開鍵暗号では常に分離したセッションキーを使用します。 このオプションは対称鍵暗号向けのもので、デフォルトではS2Kキーをそのまま使用します。



値: 0, 1
デフォルト: 0
適用範囲: pgp_sym_encrypt

F.28.3.8.7. s2k-mode #

使用するS2Kアルゴリズム。



値:
  0 - ソルト無。危険です!
  1 - ソルト有。固定繰り返し回数。
  3 - 可変繰り返し回数。
デフォルト: 3
適用範囲: pgp_sym_encrypt

F.28.3.8.8. s2k-count #

使用するS2Kアルゴリズムで使う繰り返しの回数。 1024以上、65011712以下の値でなければなりません。



デフォルト: 65536から253952までの乱数値
適用範囲: s2k-mode=3と指定した時のpgp_sym_encrypt

F.28.3.8.9. s2k-digest-algo #

S2K計算で使用するダイジェストアルゴリズム。



値: md5, sha1
デフォルト: sha1
適用範囲: pgp_sym_encrypt

F.28.3.8.10. s2k-cipher-algo #

分離したセッションキーの暗号化に使用する暗号。



値: bf, aes, aes128, aes192, aes256
デフォルト: cipher-algoを使用
適用範囲: pgp_sym_encrypt

F.28.3.8.11. unicode-mode #

テキストデータをデータベース内部符号化方式からUTF-8に変換して戻すかどうかです。 データベースがすでにUTF-8であれば、変換は起こらず、データにUTF-8としてタグが付くのみです。 このオプションがないと、何も行われません。



値: 0, 1
デフォルト: 0
適用範囲: pgp_sym_encrypt, pgp_pub_encrypt

F.28.3.9. GnuPGを使用したキーの生成 #

新しいキーを生成します。

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 gpgThe GNU Privacy Handbookhttps://www.gnupg.org/サイトの各種文書を参照してください。

F.28.3.10. PGPコードの制限 #

  • 署名に関するサポートはありません。 これはまた、暗号化副キーがマスタキーに属しているかどうか検査しないことを意味します。

  • マスタキーとして暗号化キーをサポートしません。 一般的にこうした状況は現実的ではありませんので、問題にならないはずです。

  • 複数の副キーに関するサポートはありません。 よくありますので、これは問題になりそうに見えます。 一方、通常のGPG/PGPキーをpgcryptoで使用すべきではありません。 使用する状況が多少異なりますので新しく作成してください。

F.28.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は以下の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では無視されます。 正確にブロック長でない場合、切り詰められるか、もしくはゼロで埋められます。 このパラメータがない場合、関数のデフォルト値はすべてゼロです。

F.28.5. ランダムデータ関数 #

gen_random_bytes(count integer) returns bytea

暗号論的に強いランダムなcountバイトを返します。 一度に最大で1024バイトを抽出することができます。 ランダム性ジェネレータプールを空にすることを防止するためのものです。

gen_random_uuid() returns uuid

バージョン4(ランダムな)UUIDを返します。 (廃れたものです。この関数は内部では同名のコア関数を呼び出します。)

F.28.6. 注釈 #

F.28.6.1. 構築 #

pgcryptoは自身で主PostgreSQLのconfigureスクリプトの検出結果に従って構築します。 構築に影響するオプションは--with-zlib--with-opensslです。

ZLIB付きでコンパイルされた場合、PGP暗号化関数は暗号化前にデータを圧縮することができます。

pgcryptoOpenSSLを必要とします。 そうでなければ、構築もインストールもされません。

OpenSSL 3.0.0とそれ以降に対してコンパイルされた場合、DESやBlowfishのような古い暗号を使うためにはopenssl.cnf設定ファイルでレガシープロバイダを有効にしなければなりません。

F.28.6.2. NULLの扱い #

標準SQLの通り、引数のいずれかがNULLの場合、すべての関数はNULLを返します。 注意せずに使用すると、これがセキュリティ上の問題になるかもしれません。

F.28.6.3. セキュリティ上の制限 #

pgcryptoの関数はすべてデータベースサーバ内部で実行されます。 これは、pgcryptoとクライアントアプリケーションとの間でやり取りされるデータはすべて平文であることを意味します。 したがって、以下を行う必要があります。

  1. ローカルまたはSSL接続で接続

  2. システム管理者およびデータベース管理者を信頼

これらが不可能であれば、クライアントアプリケーション内で暗号化する方が望まれます。

実装はサイドチャネル攻撃に耐えられません。 例えば、pgcrypto復号関数が完了するのに掛かる時間は、一定の長さの暗号文に対して一様ではありません。

F.28.6.4. 有用な文書 #

F.28.6.5. 技術的な参考情報 #

F.28.7. 作者 #

Marko Kreen

pgcryptoは以下のソースを使用しています。

アルゴリズム作者元ソース
DES cryptDavid Burren他FreeBSD libcrypt
MD5 cryptPoul-Henning KampFreeBSD libcrypt
Blowfish cryptSolar Designerwww.openwall.com