PostgreSQLの暗号化は、単一の設定項目ではありません。「暗号化しておいて」という要件がそのまま渡ってくると、通信路の話なのか、ディスクに置いたファイルの話なのか、特定の列だけを読めなくする話なのかが混ざったまま設計に入ってしまいます。厄介なのは、PostgreSQL本体に他社製DBのような透過的暗号化(TDE)が入っていないため、層ごとに担当する仕組みが違うことです。
この記事では、通信・保存時・列単位の三層に分けたうえで、どこを設定で閉じ、どこをクラウド側に預け、どこはアプリケーション側の改修になるのかを整理します。暗号方式そのものの考え方は暗号化とは?共通鍵・公開鍵の違いと企業が守るべきデータの判断に、データベース全般の位置づけはデータベースとは?種類・DBMS・RDBとNoSQLの選び方に譲ります。動作の前提は18系(2026年8月時点、最新のマイナーは18.6)としました。
まとめ:PostgreSQLの暗号化で先に決める4点
結論を先に4点で示します。第一に、PostgreSQL本体には保存時の透過的暗号化がありません。ディスク上のファイルを暗号化したいなら、担当するのはOSのファイルシステムか、マネージドサービスのストレージ暗号化か、Perconaなど第三者が配布する拡張です。本体の設定だけで完結すると考えると、設計が最初から破綻します。
第二に、通信の暗号化はサーバ側のsslを有効にしただけでは終わりません。既定値はoffで、有効にしても非SSL接続は拒まれず、クライアント側のsslmodeの既定はpreferです。サーバ・接続許可設定・クライアントの三点をそろえて、はじめて平文へ落ちる経路が消えます。
第三に、pgcryptoによる列単位の暗号化は、アプリケーションの改修と鍵の管理を丸ごと引き受ける選択になります。復号はサーバのプロセス内で走るため、DBの管理者を信頼できない前提を本当に満たしたいなら、暗号処理はクライアント側へ寄せることになるからです。
第四に、マネージドサービスの保存時暗号化は「後から有効化」がほぼ効きません。RDSは作成時にしか暗号化を選べず、既存インスタンスはスナップショットの暗号化コピーから復元する手順を踏みます。Azure Database for PostgreSQLの顧客管理キーも作成時限定で、サービス管理キーへ戻せません。以下、層ごとに根拠を見ていきます。
PostgreSQL本体にTDEが無い前提で暗号化の層を分ける
設計を誤らせる最大の要因は、層の取り違えです。「暗号化した」という状態が何を防ぐのかは層ごとに違い、コストも改修範囲も別物になります。
暗号化で防げる持ち出し経路を保存時・通信時・列単位の三層に分ける
三層の守備範囲を先に固定しておくと、要件の翻訳が早くなります。
| 層 | 担当する仕組み | 防げる場面 | 防げない場面 |
|---|---|---|---|
| 保存時 | FS暗号化・クラウド側 | ディスクや媒体の持ち出し | 正規の接続からの参照 |
| 通信時 | TLS(サーバとクライアント) | 経路上の盗聴と改ざん | DB内部での平文保持 |
| 列単位 | pgcrypto・アプリ側実装 | DB管理者による参照 | 鍵ごと漏れた場合 |
保存時暗号化は、サーバが動いている限り透過的に読めるため、稼働中の不正な参照には効きません。逆に列単位の暗号化は、鍵を持つアプリケーションが侵害されれば意味を失います。三つは代替関係ではなく、塞ぐ穴が別だと理解してください。
pg_tdeが17系とPercona配布に限られる2026年8月時点の現在地
本体にTDEを入れる動きは続いていますが、コミュニティ版のPostgreSQLは18.6の時点でも透過的暗号化を持っていません。現実的な選択肢はPerconaが配布するpg_tdeで、テーブル・索引・WALの暗号化に対応するtde_heapアクセスメソッドを提供します。
ただし適用範囲には制約があります。公式ドキュメントの対応表では、pg_tdeの最新版は2.2.1(2026年7月6日リリース)で、対応するのはPercona Server for PostgreSQL 17系のみ。コミュニティ配布のバイナリには入らず、機能を絞った旧来のtde_heap_basicは非推奨として整理されました。
つまり18系を選んだ時点で、拡張によるTDEは候補から外れます。18系の新機能を取るか、TDEのために17系とPercona配布へ寄せるかは、ここで先に決める判断です。バージョン選定の考え方はAurora PostgreSQLとは?対応バージョン・拡張機能と移行判断もあわせて確認してください。
通信の暗号化をsslとpg_hba.confとsslmodeで閉じる
通信の層は、設定の手数が少ない割に取りこぼしが多い箇所です。三点そろわないと平文の経路が残ります。TLS自体の仕組みはTLSとは?SSLとの違い・仕組みとバージョン選定で整理しています。
サーバ側でsslをonにしただけでは非SSL接続を拒めない理由
サーバ側のsslパラメータは既定がoffです。証明書と秘密鍵を配置してonにすると暗号化接続を受け付けられるようになりますが、これは「受け付けられる」だけで、平文の接続を拒む設定ではありません。拒否する役割はpg_hba.confが持ちます。
# postgresql.conf
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
# pg_hba.conf ... host ではなく hostssl で受ける
hostssl appdb app_rw 10.0.0.0/16 scram-sha-256
hostnossl appdb all 0.0.0.0/0 reject
プロトコルの下限にも既定があります。ssl_min_protocol_versionの既定はTLSv1.2で、上限は指定なし、つまり利用できる最新版まで許容する形です。古いクライアントに合わせて下限を落とす変更は、理由と期限を決めてから行ってください。
クライアントのsslmode既定がpreferで平文へ落ちる経路
libpqのsslmodeは既定がpreferです。まずSSLを試し、失敗すると非SSLで接続し直します。サーバ側がhostsslで閉じていなければ、この再試行がそのまま平文接続として成立してしまう。監視やバッチのように接続文字列を後から足した箇所ほど、既定のまま残ります。
requireは暗号化だけを要求し、証明書の検証はしません。中間者を排除したいならverify-ca以上、さらにホスト名の一致まで確認するならverify-fullを指定します。両者の差は、CAによる署名の確認までで止めるか、接続先ホスト名が証明書の名前と一致することまで見るかです。
証明書の置き場所を意識せずに堅くしたい場合、sslrootcert=systemという指定があります。OSやライブラリが持つ信頼済みルート証明書を読み込む設定で、これを使うとsslmodeの既定がverify-fullへ変わり、それより弱い指定はエラーになります。
sslnegotiation=directを17以降で使ってよい条件
17でsslnegotiationが追加されました。既定のpostgresはPostgreSQL独自の手順でSSL対応の可否を問い合わせてからハンドシェイクへ進みますが、directはTCP接続の直後にTLSハンドシェイクを始めます。往復が一回減り、TLS対応の中継機器を挟みやすくなる利点があります。
ただしdirectはsslmodeがrequire以上のときしか許されません。弱い設定と組み合わせると、サーバが直接ハンドシェイクに対応していない場合に平文の認証へ落ちる恐れがあるためです。接続の往復を削る目的で入れるなら、sslmodeを先に固めるのが順序になります。
pgcryptoで列単位に暗号化する方法と鍵の置き場所の決め方
特定の列だけを読めなくしたい要件では、contribのpgcryptoが候補になります。非スーパーユーザーでもCREATE権限があれば導入できる「trusted」な拡張で、導入の敷居は低い部類です。
pgp_sym_encryptを使いraw encryptを避ける根拠
pgcryptoには、ハッシュ系のdigestとhmac、パスワード用のcryptとgen_salt、PGP形式のpgp_sym_encryptとpgp_pub_encrypt、そして低水準のencryptとdecryptが入っています。列の暗号化で使うのはPGP形式の関数です。
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- 共通鍵で列を暗号化して bytea で保持する
INSERT INTO members (id, secret_no)
VALUES (1, pgp_sym_encrypt('1234-5678', :'enc_key'));
-- 参照時に復号する
SELECT pgp_sym_decrypt(secret_no, :'enc_key') FROM members WHERE id = 1;
低水準のencrypt系は、公式ドキュメントが明確に非推奨としています。理由は四つで、利用者の鍵をそのまま暗号鍵として使うこと、完全性の検証がないこと、IVを含む暗号パラメータの管理を利用者へ丸投げすること、テキストを扱えないこと。素の暗号アルゴリズムを直接叩く形になり、実装の誤りが脆弱性になります。
なおpgp_sym_encryptの既定の暗号アルゴリズムはaes128です。要件でAES-256を求められている案件では、引数でオプション文字列を渡して明示的に指定してください。既定のまま通すと、監査の場で説明できません。
暗号鍵をSQL文へ直接書いた場合に露出する範囲と回避の考え方
列暗号化で本当に難しいのは関数の使い方ではなく、鍵の置き場所です。公式ドキュメントは前提をはっきり書いています。pgcryptoの関数はすべてサーバ内部で動くため、データも鍵もクライアントとサーバの間を平文で行き来する。したがって、ローカル接続にするかSSL接続を使うこと、そしてシステム管理者とデータベース管理者の双方を信頼できることが条件になります。
この条件を満たせないなら、公式の推奨は「クライアントアプリケーション側で暗号処理を行う」ことです。DBの管理者から守るのが目的なら、そもそも鍵をDBサーバへ渡さない設計にするしかありません。SQL文に鍵をリテラルで書けば、文そのものがpg_stat_activityやログの出力対象になり得ます。プレースホルダで渡す、鍵をKMSやシークレット管理サービスから取得する、といった手当てが要ります。
鍵の交換手順も設計の対象です。鍵を替えるなら該当列を復号して再暗号化する処理が必要で、行数が多いテーブルでは無停止の実施計画がそのまま案件になります。保存する値がパスワードなら、復号できる暗号化ではなくハッシュを使う判断が先です。ハッシュ化とは?暗号化との違いとパスワード保管・改ざん検知の仕組みで違いを整理しています。
列単位の暗号化で索引と検索が効かなくなる代償と絞り込みの基準
暗号化した列はbyteaになり、値の同一性も順序も失われます。等価検索も範囲検索も部分一致も、索引では引けません。結果として、暗号化した列を検索条件に使いたい場合は、検索用のハッシュ列を別に持つか、全件を復号して絞り込む処理を書くことになります。後者は行数が増えた瞬間に破綻する設計です。
そこで、暗号化する列は「検索条件に使わず、参照頻度が低く、漏れたときの影響が大きい列」に絞ります。クレジットカード番号、マイナンバー、健康情報のような項目が該当するでしょう。氏名やメールアドレスのように名寄せで使う列を暗号化すると業務要件と衝突するため、保存時暗号化とアクセス制御で守る方針へ切り替えます。
RDS・Aurora・Azureで保存時暗号化を有効にできる条件
マネージドサービスを使うなら、保存時の層はサービス側の機能に寄せるのが定石になります。ただし有効化のタイミングに強い制約があります。
RDSの保存時暗号化が作成時限定でスナップショット経由になる制約
Amazon RDSの保存時暗号化はAES-256で、対象はストレージ本体・ログ・自動バックアップ・リードレプリカ・スナップショットに及びます。鍵はKMSで管理し、AWS管理キーか顧客管理キーを選べます。
制約は有効化の手順にあります。ドキュメントは「暗号化はDBインスタンスの作成時にのみ設定でき、作成後には設定できない」と明記しています。既存の非暗号化インスタンスを暗号化したい場合の手順は次のとおりです。
1. 対象インスタンスの手動スナップショットを取得する
2. そのスナップショットを「暗号化を有効にして」コピーする
3. 暗号化済みスナップショットから新しいインスタンスへ復元する
4. 接続先を切り替えて旧インスタンスを停止・削除する
逆方向、つまり暗号化済みインスタンスの暗号化を解除する操作はできません。KMSキーの変更も不可で、変えたい場合は手動スナップショットを取り、コピー時に別のキーで暗号化し直します。非暗号化インスタンスから暗号化スナップショットを直接作れない点も押さえておいてください。
運用面の落とし穴は、KMSキーを無効にした場合の挙動です。バックアップが有効なインスタンスでは検知から2時間後に復旧可能な状態へ移り、7日続くと復旧できなくなります。キーの削除保護と権限設計は、DBの可用性に直結する設定です。インスタンス側の構成はAmazon RDS for PostgreSQLとは?マルチAZ構成・拡張機能の統制と移行で扱っています。
Azureは既定で暗号化済みでもCMKは作成時しか選べない仕様
Azure Database for PostgreSQL フレキシブルサーバーは、すべてのデータを常に保存時暗号化します。対象はシステムとユーザーのデータベース、サーバーログ、WALセグメント、バックアップまでで、既定はサービス管理キーです。何もしなくても暗号化されている点がRDSと異なります。
顧客管理キーへ寄せる場合、モードを選べるのはサーバー作成時だけで、作成後の変更はできません。しかも一度顧客管理キーにすると、サービス管理キーへ戻す操作も提供されていない。戻したいならポイントインタイム復元で新しいサーバーを作り直す形になります。Key Vault側では論理削除と消去保護の有効化が求められ、鍵はRSAで2048・3072・4096ビットが対象です。
鍵へ到達できなくなるとサーバーは「アクセス不可」になり、接続がすべて拒否されます。鍵の無効化・削除・期限切れ・ファイアウォール変更から、状態が変わるまでの目安はおよそ60分。バージョンを含めないURIで登録しておけば、ローテーション時の追随は自動です。環境ごとの構成はAzure Database for PostgreSQLとは?フレキシブルサーバーの構成と移行判断を参照してください。
rds.force_sslで非SSL接続を落とす設定と既定値の版差
通信の強制はパラメータグループ側で行います。RDS for PostgreSQLのrds.force_sslは、15以降で既定が1(有効)、14以前は既定が0です。移行元が14以前の環境では、この差だけで移行後の接続要件が変わる点に注意してください。1にすると非SSL接続は接続拒否のエラーになります。
暗号スイート側にも版差があります。RDSの16以降ではssl_max_protocol_versionの既定がTLS 1.3で、ssl_ciphersで指定した内容はTLS 1.3の接続には効きません。指定を効かせたいなら上限をTLS 1.2に下げる必要があり、18以降ではssl_tls13_ciphersという別パラメータで指定します。Azure側で同じことをしたい場合はrequire_secure_transportを有効にします。
パスワード認証をscram-sha-256へ寄せる手順と注意点
通信路を暗号化しても、認証方式が古いままなら守られる範囲は狭いままです。ここは設定一行では終わらない箇所になります。
password_encryptionの既定とmd5が非推奨になった経緯
PostgreSQLはscram-sha-256を「現在提供されている方式の中で最も安全」と位置づけており、password_encryptionの既定値もscram-sha-256です。対するmd5については、ドキュメントが「MD5で暗号化されたパスワードのサポートは非推奨であり、将来のリリースで削除される」と明記しています。
md5方式は盗聴と平文保存を防ぐ仕組みこそ持つものの、ハッシュそのものを盗まれた場合には防御になりません。18系を新規に構築するなら選ぶ理由はなく、既存環境の移行が唯一の論点でしょう。ロールと権限の設計側はPostgreSQLのロールと権限設計|CREATE ROLEの属性とGRANT・既定権限で整理しています。
既存ロールはパスワードを再設定しない限り方式が変わらない理由
ここが移行でつまずく点です。password_encryptionを変更しても、既に保存されているパスワードの検証子は書き換わりません。方式が切り替わるのは、そのロールのパスワードを設定し直したときだけ。設定を変えたのにmd5のままだった、という状況はここから生まれます。
-- 現在どの方式で保存されているかを確認する
SELECT rolname,
CASE WHEN rolpassword LIKE 'SCRAM-SHA-256%' THEN 'scram'
WHEN rolpassword LIKE 'md5%' THEN 'md5'
ELSE 'other' END AS method
FROM pg_authid WHERE rolcanlogin;
-- 再設定して方式を切り替える
ALTER ROLE app_rw PASSWORD 'new-secret';
切り替えの順序は次のとおり。先にクライアントライブラリがSCRAMに対応していることを確認し、次にpassword_encryptionを変更、そのうえで全ロールのパスワードを再設定して、最後にpg_hba.confの認証方式をscram-sha-256へ変更します。古いドライバが残る環境で最後の手順を先に実施すると、接続できないロールが出ます。
暗号化の適用範囲を決める判断基準と見送ってよい場面の見分け方
ここまでの制約を踏まえると、案件で採るべき順序は一つに収束します。
通信と保存時を先に閉じてから列暗号化を要件のある列だけに絞る
先にやるべきは通信と保存時です。理由は費用対効果がはっきりしているからで、どちらもアプリケーションの改修を伴いません。通信はhostsslとsslmode=verify-full、保存時はマネージドサービスの機能かファイルシステム暗号化で閉じます。ここまでを済ませずに列暗号化へ進むのは、鍵をかけていない部屋の中に金庫を置くのと同じ構図でしょう。
列暗号化を採用してよいのは、次の条件がそろったときに限ります。第一に、対象の列が検索条件にも結合条件にも使われないこと。第二に、法令や契約で当該項目の暗号化が明示的に求められていること。第三に、鍵をDBの外側で管理する仕組みと、鍵交換時の再暗号化手順を運用として引き受けられること。三つ目が用意できないまま導入すると、鍵を失った時点でデータそのものが失われます。
列単位の暗号化を見送ってよい場面と代わりに手当てする管理項目
見送ってよい場面もはっきりしています。社内利用に閉じた業務システムで、扱うのが取引先名や案件情報といった一般的な業務データであり、マネージドサービスの保存時暗号化とTLSが有効になっている場合です。ここに列暗号化を足しても、追加で防げる脅威は「DB管理者による参照」だけで、その管理者は結局アプリケーション側の鍵にも到達できることが多いためです。
その代わり、見送るなら手当てすべき項目が三つあります。読み取り専用ロールの分離とアプリ用ロールの権限最小化、接続元のネットワーク制限、そして参照操作の監査ログ取得です。これらは列暗号化より安価で、実際の内部不正に対しては検知の面で効きます。設定の抜けを第三者の目で洗い出す段階では、脆弱性診断・セキュリティ診断のように実機の設定値まで踏み込んで確認する診断を挟むと、パラメータグループの取りこぼしや古い認証方式の残存を公開前に潰せます。
よくある質問
PostgreSQLにTDEはありますか?
コミュニティ版の本体にはありません。18.6の時点でも透過的暗号化の機能は入っていない状態です。拡張で実現するならPerconaのpg_tdeが該当しますが、2.2.1(2026年7月6日)時点で対応するのはPercona Server for PostgreSQL 17系のみで、コミュニティ配布のバイナリでは使えません。クラウドを使う場合は、サービス側のストレージ暗号化が実質的な代替になります。
ssl=onにすれば平文接続は拒否されますか?
拒否されません。ssl = onは暗号化接続を受け付ける設定で、平文を弾く役割はpg_hba.confが担います。hostsslで受け、hostnosslをrejectにしてはじめて経路が閉じます。クライアント側のsslmodeが既定のpreferのままだと、サーバが平文を受ける限り平文で接続し直す点にも注意してください。
pgcryptoで暗号化した列は検索できますか?
そのままでは検索できません。暗号文はbyteaで保持され、等価比較も範囲比較も索引では引けなくなります。検索が必要なら、検索用のハッシュ値を別列に持たせて完全一致だけを引く、あるいはそもそも暗号化の対象から外す設計にします。全件を復号してから絞り込む実装は、行数が増えると応答が保ちません。
RDSで作成済みのDBを後から暗号化できますか?
直接の有効化はできません。手動スナップショットを取り、コピー時に暗号化を有効にして、その暗号化済みスナップショットから新しいインスタンスを復元する手順になります。切り替えには接続先の変更が伴うため、停止できる時間帯の確保が前提です。逆に暗号化済みインスタンスの解除もできず、解除したい場合はデータをエクスポートして入れ直します。
md5認証のままだと何が起きますか?
直ちに接続できなくなるわけではありませんが、将来のリリースで削除が予告されている方式です。password_encryptionを変更しただけでは既存ロールの保存形式は変わらず、パスワードを再設定した時点で切り替わります。移行はクライアントライブラリのSCRAM対応を確認してから、パラメータ変更、全ロールのパスワード再設定、pg_hba.confの変更という順で進めてください。