Enterprise-grade envelope encryption for PostgreSQL with Rust-powered KMS integrations
pgEnvelope is a PostgreSQL extension that provides modern, secure, KMS-backed envelope encryption for database columns. It uses a Rust cryptographic core for modern security guarantees and a lightweight C shim to integrate seamlessly with PostgreSQL.
pgEnvelope brings PostgreSQL into a world where encrypted-at-rest is not enough. Instead, each piece of sensitive data receives its own envelope encryption key, automatically managed through an enterprise KMS or HSM.
- Authenticated encryption using modern AEAD (ChaCha20-Poly1305 in the local core)
- Automatic nonce generation
- Strict integrity protection
- Zero unsafe cryptography in Cβonly in Rust
pgEnvelope currently supports:
| Provider | Status | Notes |
|---|---|---|
| LocalKMS | β Ready | Development & testing mode |
| AWS KMS | β Ready | Uses AWS SDK; supports any region |
| Azure Key Vault | β Ready | Access token retrieval with Key Vault mock support |
| Google Cloud KMS | βοΈ Functional | Requires explicit env configuration |
| Hashicorp Vault | β Ready | Supports transit engine |
| SoftHSM2 / PKCS#11 | βοΈ Functional | Key slots tested, token initialization supported |
Each provider is implemented inside the Rust core and selected by prefix (e.g., aws:mykey, vault:mykey).
pgEnvelope supports three compatible ciphertext formats:
- Inline v2 (legacy inline):
| version (2) | scheme_id | nonce | ciphertext+tag |
Used by older schemes like local_default, vault_default, and early AWS integrations.
Still fully supported for decryption and migration.
- Envelope v3 (legacy envelope):
| version (3) | provider scheme_id | envelope_header | wrapped_dek | ciphertext+tag |
Legacy envelope format still supported for decryption/rewrap.
- Envelope v4 (current, recommended):
| version (4) | provider_scheme_id | logical_scheme_id | envelope_header | wrapped_dek | ciphertext+tag |
The envelope_header includes:
- provider_scheme_id β backend KMS family (local, Vault, AWS, β¦)
- logical_scheme_id β versioned scheme identifier (from
pgenvelope_schemes) - algo β local AEAD algorithm (e.g. chacha20poly1305)
- nonce β per-message nonce for the local AEAD
- dek_id / dek_version β identity of the data-encryption key
- master_key_alias β KMS alias used to wrap this DEK
In v4, the payload is always encrypted using a local AEAD key (DEK), and the DEK is then wrapped by the KMS. This gives you:
- strong separation of concerns (local crypto vs. remote KMS)
- cheap rotations via DEK rewrap
- a single, stable format across providers (local, Vault, AWS, β¦)
You can inspect any ciphertext via:
SELECT *
FROM pgenvelope_inspect_ciphertext(<ciphertext_bytea>);which exposes version, scheme_id (logical), algorithm, master_key_alias, wrapped_dek_len, and more.
On top of the raw providers, pgEnvelope ships with envelope-mode schemes:
| Scheme name | Backend provider | Ciphertext version | Notes |
|---|---|---|---|
local_envelope |
LocalKMS | v4 envelope | Dev / test envelope-mode using local AEAD |
vault_envelope |
Vault Transit | v4 envelope | Production-style envelope using Vault KMS |
aws_envelope |
AWS KMS | v4 envelope | AWS-backed envelope mode |
These schemes all use the same v4 envelope format and differ only in how the DEK is wrapped (local, Vault Transit, AWS KMS, β¦). Legacy v3 envelope ciphertexts remain decryptable.
pgEnvelope supports:
- Manual rotation and rewrap (reencrypt column / rotate scheme)
- Transparent decryption with legacy ciphertext formats
- Auditable rotation logs and dry-run planning
SQL interface functions include:
pgenvelope_register_column(regclass, text, text)pgenvelope_encrypt_for_column(regclass, text, anyelement)pgenvelope_decrypt_for_column(regclass, text, bytea)pgenvelope_encrypt_for_column_typed(regclass, text, anyelement)pgenvelope_decrypt_for_column_typed(regclass, text, bytea, regtype)- Typed convenience wrappers:
pgenvelope_decrypt_for_column_typed_text/int2/int4/int8/bool/uuid/float4/float8/time/timetz/timestamp/timestamptz/date/interval/json/jsonb/jsonpath/bytea/inet/cidr/macaddr/macaddr8/tsvector/tsquery/numeric pgenvelope_list_schemes()pgenvelope_kms_status()pgenvelope_rotate_scheme*/pgenvelope_rewrap_*pgenvelope_inspect_ciphertext(bytea)
Regression tests are included and run during CI.
PostgreSQL 13β18
β
βΌ
C Extension Shim (pgenvelope.so)
β FFI
βΌ
Rust Core Library (libpgenvelope_core.so)
βββ KMS Providers
βββ ChaCha20-Poly1305 (local AEAD)
βββ Key Wrapping / Unwrapping
βββ Envelope Format + Rotation Helpers
This repo includes a full dev stack (Postgres + Vault + LocalStack + Azure mock + optional SoftHSM2).
- Start the stack:
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d --build- Run the full test suite:
make test-all- Try it manually:
psql -h localhost -U pgenvelope -d pgenvelopedbCREATE EXTENSION pgenvelope;
CREATE TABLE public.quickstart (
id bigserial primary key,
data_plain text,
data_enc bytea
);
SELECT pgenvelope_register_column('public.quickstart'::regclass, 'data_enc', 'local_default');
INSERT INTO public.quickstart (data_plain, data_enc)
VALUES ('hello', pgenvelope_encrypt_for_column('public.quickstart'::regclass, 'data_enc', 'hello'::text));
SELECT * FROM pgenvelope_inspect_ciphertext((SELECT data_enc FROM public.quickstart LIMIT 1));pgEnvelope builds against:
PostgreSQL 13, 14, 15, 16, 17, 18
The same source code is used for all versions.
The extension must be compiled separately for each major version using that versionβs pg_config.
pgEnvelope can be installed:
- via Docker (recommended)
- manually from source
- inside CI/CD pipelines for packaging or testing
Full instructions are available in:
π docs/Building and Installing.md
Summary below.
cd rustcore
cargo build --releaseProduces:
rustcore/target/release/libpgenvelope_core.so
Example: for PostgreSQL 16
cd cshim
make PG_CONFIG=/usr/lib/postgresql/16/bin/pg_config
sudo make install PG_CONFIG=/usr/lib/postgresql/16/bin/pg_configCopy Rust core into PostgreSQLβs library directory:
sudo cp ../rustcore/target/release/libpgenvelope_core.so $(pg_config --libdir)/CREATE
EXTENSION pgenvelope;pgEnvelope provides a multi-version Docker build system. Build an image for the desired PostgreSQL version:
docker build --build-arg PG_MAJOR=16 -t pgenvelope-pg16 .
docker build --build-arg PG_MAJOR=15 -t pgenvelope-pg15 .
docker build --build-arg PG_MAJOR=14 -t pgenvelope-pg14 .
docker build --build-arg PG_MAJOR=13 -t pgenvelope-pg13 .Start a pgEnvelope-enabled database:
docker run -p 5432:5432 --name pgenvelope16 pgenvelope-pg16Inside the container:
CREATE
EXTENSION pgenvelope;The repo also contains ready-made docker-compose environments including Vault / SoftHSM2 setups.
pgEnvelope configures KMS providers via environment variables.
See detailed configuration instructions:
π docs/KMS-Configuration.md
SQL regression tests (PGXS):
cd cshim
make PG_CONFIG=/usr/lib/postgresql/16/bin/pg_config installcheckRust tests:
cd rustcore
cargo testFull stack (Docker + SQL + Rust):
make test-alldocs/Building and Installing.mddocs/KMS-Configuration.mddocs/Type Support.mddocs/Security Model Overview.mddocs/Operations Cookbook.mddocs/Operations Incident Runbook.mddocs/Scheme-Model-Decision.md
For exclusions and pseudo-type notes, see Not applicable / not supported in docs/Type Support.md.
pgEnvelope/
β
βββ rustcore/ # Rust cryptographic engine + KMS providers
β βββ src/
β βββ kms/ # AWS, Azure, Vault, LocalKms, SoftHSM2, GCP
β βββ ffi.rs # FFI boundary to PostgreSQL shim
β
βββ cshim/ # PostgreSQL extension C layer
β βββ pgenvelope.c
β βββ pgenvelope_ffi.h
β βββ pgenvelope--1.0.sql
β
βββ e2e/ # Testcontainers-based E2E harness
β
βββ docker/ # Vault + SoftHSM2 dev environments
βββ docs/ # Installation, KMS config, architecture docs
βββ cshim/sql/ # SQL regression tests
-- Register an encrypted column
SELECT pgenvelope_register_column('public.my_table'::regclass, 'data_enc', 'local_default');
-- Encrypt using column-aware AAD binding
INSERT INTO public.my_table (data_plain, data_enc)
VALUES ('hello', pgenvelope_encrypt_for_column('public.my_table'::regclass, 'data_enc', 'hello'::text));-- Typed API uses a binary wire format when available, otherwise text
INSERT INTO public.my_table (data_enc)
VALUES (pgenvelope_encrypt_for_column_typed('public.my_table'::regclass, 'data_enc', now()::timestamptz));
SELECT pgenvelope_decrypt_for_column_typed('public.my_table'::regclass,
'data_enc',
data_enc,
'timestamptz')::timestamptz
FROM public.my_table
LIMIT 1;SELECT pgenvelope_decrypt_for_column_typed_text('public.my_table'::regclass, 'data_enc', data_enc)
FROM public.my_table;
SELECT pgenvelope_decrypt_for_column_typed_int4('public.my_table'::regclass, 'data_enc', data_enc)
FROM public.my_table;SELECT pgenvelope_decrypt_for_column('public.my_table'::regclass, 'data_enc', data_enc)
FROM my_table;SELECT pgenvelope_create_decrypted_view('public.my_table'::regclass);
SELECT * FROM public.my_table_dec;- ChaCha20-Poly1305 authenticated encryption in the local core
- Keys never stored unencrypted inside PostgreSQL
- KMS / HSM performs all key-wrapping operations
- FFI boundary kept minimal and memory-safe
- Modern Rust crypto crates
- Optional HSM-based root keys
PPL
We welcome PRs for:
- New KMS / HSM providers (GCP KMS is next!)
- More regression tests
- Performance optimizations
- Documentation improvements
- Packaging (Deb/RPM)
It helps the project grow and encourages future development!