You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Transparent AES-256-GCM page-level encryption for PGlite PostgreSQL databases.
7
+
An encrypted virtual filesystem for [PGlite](https://pglite.dev/). Provides transparent AES-256-GCM page-level encryption so your PGlite database files are encrypted at rest.
8
+
9
+
> **Status:** Alpha. The on-disk format is not yet versioned and may change before 1.0.
10
+
11
+
## Why this exists
12
+
13
+
[PGlite](https://pglite.dev/) gives you a full embedded PostgreSQL in Node.js -- SQL, indexes, transactions, extensions like pgvector, all without a server. But out of the box, database files sit **plaintext on disk**.
14
+
15
+
This package is an encrypted VFS that plugs into PGlite's filesystem layer, encrypting every page as it's written and decrypting as it's read. Your PGlite code stays the same -- you just pass an `EncryptedFS` instance at creation.
8
16
9
17
## Features
10
18
11
19
-**AES-256-GCM authenticated encryption** -- page-level encryption with integrity verification on every read
12
-
-**PBKDF2-SHA512 key derivation** -- 256K iterations, compliant with OWASP recommendations
// IMPORTANT: Store the salt alongside your database path.
59
-
// You'll need it to reopen the database later.
60
57
```
61
58
62
59
## Reopening an Existing Database
63
60
64
-
The salt is required to reopen a database. Store it alongside your database path -- for example, in a config file or as a hex-encoded string in your application's settings.
61
+
Use the same passphrase -- the salt is stored automatically.
The constructor creates the data directory if it does not exist, and verifies the passphrase against an existing database (or creates the verification token for a new one).
109
+
The constructor creates the data directory if it does not exist. On first use, it generates a random salt and creates a verification token. On subsequent opens, it reads the salt from the existing verification token and verifies the passphrase is correct.
90
110
91
111
### `deriveKeys(passphrase, salt)`
92
112
@@ -158,7 +178,7 @@ Each file receives a random 32-byte identifier stored in its header. Because fil
158
178
159
179
### Passphrase Verification
160
180
161
-
On first initialization, a `.encryption-verify` file is created containing a known magic value encrypted with the derived key. On every subsequent open, this file is decrypted and checked. A wrong passphrase fails immediately rather than silently serving corrupted data.
181
+
On first initialization, a `.encryption-verify` file is created containing the 16-byte salt followed by a known magic value encrypted with the derived key. On every subsequent open, the salt is read from this file, the key is re-derived, and the magic value is decrypted and checked. A wrong passphrase fails immediately rather than silently serving corrupted data.
162
182
163
183
### Unencrypted Files
164
184
@@ -172,6 +192,17 @@ The following PostgreSQL metadata files are left unencrypted because they contai
172
192
-`.lock` files
173
193
-`replorigin_checkpoint`
174
194
195
+
## Threat Model
196
+
197
+
This provides **at-rest encryption** of PGlite/PostgreSQL database files on disk. It protects against offline theft or unauthorized access to the stored files.
198
+
199
+
**Non-goals:**
200
+
201
+
- Does not protect against an attacker who can run code in your process
202
+
- Data is decrypted in memory during query execution (like any database encryption-at-rest)
- This package has not been independently audited. If you find a vulnerability, please report it privately via GitHub.
205
+
175
206
## Performance
176
207
177
208
Benchmarks measured on Node.js (see `pnpm run bench` for your own results):
@@ -190,7 +221,7 @@ Benchmarks measured on Node.js (see `pnpm run bench` for your own results):
190
221
| Single page decrypt | -- | 3.9us | -- |
191
222
| Key derivation (PBKDF2) | -- | 48ms | -- |
192
223
193
-
Read operations have near-zero overhead because data is decrypted when pages are loaded into PostgreSQL's buffer pool. Subsequent reads hit the cache. Write overhead comes from per-page encryption. Run benchmarks yourself with `pnpm run bench`.
224
+
Read operations have near-zero overhead because data is decrypted when pages are loaded into PostgreSQL's buffer pool. Subsequent reads hit the cache, so reads are only slow the first time a page is loaded. Write overhead comes from per-page encryption. Run benchmarks yourself with `pnpm run bench`.
194
225
195
226
## Platform Support
196
227
@@ -205,6 +236,20 @@ Read operations have near-zero overhead because data is decrypted when pages are
205
236
206
237
This package uses Node.js `crypto` and `fs` modules and is not compatible with browser environments.
207
238
239
+
## FAQ
240
+
241
+
**Can I rotate the passphrase?**
242
+
243
+
Not in-place. Export your data with `pg_dump` (or application-level export), create a new encrypted database with the new passphrase, and re-import.
244
+
245
+
**Can I migrate an existing plaintext PGlite database?**
246
+
247
+
Same approach -- dump and re-import into a new encrypted database.
248
+
249
+
**Why not SQLCipher?**
250
+
251
+
SQLCipher encrypts **SQLite** databases. This package encrypts **PGlite/PostgreSQL** databases. If you're already using PGlite for its PostgreSQL features (extensions, SQL semantics, pgvector), this adds at-rest encryption without changing databases.
0 commit comments