🚀 Feature: Encrypt private key saved to DB #199

Closed
opened 2025-10-07 23:57:23 +03:00 by OVERLORD · 3 comments
Owner

Originally created by @ItalyPaleAle on GitHub.

Originally assigned to: @ItalyPaleAle on GitHub.

Feature description

The private key is currently stored on disk un-encrypted. Anyone with access to the file system can get the "keys to the kingdom".

The private key could optionally be encrypted when stored to disk. The decryption key is provided to Pocket ID via env vars (e.g. ENCRYPTION_KEY) or with the name of another file containing the key (ENCRYPTION_KEY_FILE).

This allows also using a command to retrieve the key encryption key, e.g. to fetch it from a vault of some sort.

Pitch

Data stored on disk is not always safe and can be leaked or stolen by malicious code. Encrypting the private key is a simple and effective way to keep the "keys to the kingdom" safe.

Originally created by @ItalyPaleAle on GitHub. Originally assigned to: @ItalyPaleAle on GitHub. ### Feature description The private key is currently stored on disk un-encrypted. Anyone with access to the file system can get the "keys to the kingdom". The private key could optionally be encrypted when stored to disk. The decryption key is provided to Pocket ID via env vars (e.g. `ENCRYPTION_KEY`) or with the name of another file containing the key (`ENCRYPTION_KEY_FILE`). This allows also using a command to retrieve the key encryption key, e.g. to fetch it from a vault of some sort. ### Pitch Data stored on disk is not always safe and can be leaked or stolen by malicious code. Encrypting the private key is a simple and effective way to keep the "keys to the kingdom" safe.
OVERLORD added the feature label 2025-10-07 23:57:23 +03:00
Author
Owner

@ItalyPaleAle commented on GitHub:

I’ve given this some thought and here’s a more flushed-out design proposal.

Goals:

  1. Allow storing keys in the database if encrypted
  2. Maintain backwards-compatiblity
  3. Make it possible in the future (without adding much complexity/configuration and without breaking anything) to add additional options such as ephemeral keys (#581) or in some vaults (https://github.com/pocket-id/pocket-id/issues/497#issuecomment-2847421640 and next comments)

Configuration options:

  • Existing env var:
    • KEYS_PATH: path on disk where the keys are stored
  • Add the env vars:
    • KEYS_STORAGE: can be file (filesystem), db (database). In the future it can be memory (ephemeral) or services like hashicorp-vault
    • KEYS_ENCRYPTION_KEY: encryption key for the keys when stored on disk

How it works. Here are all the possible combinations. The combinations below preserve backwards-compatiblity so users don’t need to take any action.

KEYS_STORAGE KEYS_ENCRYPTION_KEY KEYS_PATH Result
file or empty Empty Any path, defaults to data/keys) Keys are stored on disk, unencrypted (current behavior)
database or empty Contains a key n/a When there’s an encryption key, defaults to store in the database, encrypted
file Contains a key Any path, defaults to data/keys) Keys are stored on disk, encrypted
database Empty n/a Error: when storing on database, a keys is required
memory (Future option) n/a n/a Uses ephemeral keys

Essentially the default behavior is to store on file, unencrypted. If an encryption key is passed, the default is to store in the database, encrypted.

Docs should say that the ideal value of KEYS_ENCRYPTION_KEY is a random string such as one generated with openssl rand -base64 32 - but note we’re going to stretch the key regardless)

As for how keys are actually encrypted, my recommendation is:

  1. Take KEYS_ENCRYPTION_KEY and stretch it using something like PBKDF2 to a 256-bit key
  2. Encrypt keys using AES-GCM with a random IV. Since we encrypt very few messages (most installations will encrypt just 1 per key), this is totally fine and there’s no risk of random IV reuse.
  3. Use the instance ID of Pocket ID (uniquely identifying each installation) as AEAD. This will “bind” each encrypted key to a specific installation, preventing some kinds of attacks.
@ItalyPaleAle commented on GitHub: I’ve given this some thought and here’s a more flushed-out design proposal. Goals: 1. Allow storing keys in the database if encrypted 2. Maintain backwards-compatiblity 3. Make it possible in the future (without adding much complexity/configuration and without breaking anything) to add additional options such as ephemeral keys (#581) or in some vaults (https://github.com/pocket-id/pocket-id/issues/497#issuecomment-2847421640 and next comments) Configuration options: - Existing env var: - `KEYS_PATH`: path on disk where the keys are stored - Add the env vars: - `KEYS_STORAGE`: can be `file` (filesystem), `db` (database). In the future it can be `memory` (ephemeral) or services like `hashicorp-vault` - `KEYS_ENCRYPTION_KEY`: encryption key for the keys when stored on disk How it works. Here are all the possible combinations. The combinations below preserve backwards-compatiblity so users don’t _need to_ take any action. | `KEYS_STORAGE` | `KEYS_ENCRYPTION_KEY` | `KEYS_PATH` | Result | |---|---|---|---| | `file` **or empty** | Empty | Any path, defaults to `data/keys`) | Keys are stored on disk, unencrypted **(current behavior)** | | `database` **or empty** | Contains a key | n/a | When there’s an encryption key, defaults to store in the database, encrypted | | `file` | Contains a key | Any path, defaults to `data/keys`) | Keys are stored on disk, **encrypted** | | `database` | Empty | n/a | **Error**: when storing on database, a keys is required | | `memory` (Future option) | n/a | n/a | Uses ephemeral keys | Essentially the default behavior is to store on file, unencrypted. If an encryption key is passed, the default is to store in the database, encrypted. > Docs should say that the ideal value of `KEYS_ENCRYPTION_KEY` is a random string such as one generated with `openssl rand -base64 32` - but note we’re going to stretch the key regardless) As for how keys are actually encrypted, my recommendation is: 1. Take `KEYS_ENCRYPTION_KEY` and stretch it using something like PBKDF2 to a 256-bit key 2. Encrypt keys using AES-GCM with a random IV. Since we encrypt very few messages (most installations will encrypt just 1 per key), this is totally fine and there’s no risk of random IV reuse. 3. Use the **instance ID** of Pocket ID (uniquely identifying each installation) as AEAD. This will “bind” each encrypted key to a specific installation, preventing some kinds of attacks.
Author
Owner

@ItalyPaleAle commented on GitHub:

@stonith404 I don't see why not actually, that would defintely make sense

@ItalyPaleAle commented on GitHub: @stonith404 I don't see why not actually, that would defintely make sense
Author
Owner

@stonith404 commented on GitHub:

Couldn't we store the encrypted key in the database then? If we properly encrypt the key, it should be safe to store in the database, which would make Pocket ID more independent from the filesystem and bring it a step closer to running serverless.

@stonith404 commented on GitHub: Couldn't we store the encrypted key in the database then? If we properly encrypt the key, it should be safe to store in the database, which would make Pocket ID more independent from the filesystem and bring it a step closer to running serverless.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/pocket-id-pocket-id-1#199