-
Notifications
You must be signed in to change notification settings - Fork 0
Encryption
Photosphere can encrypt your media database so that your database (originals, thumbnails, display versions, and metadata) and the Merkle tree (.db/files.dat) are stored in an encrypted format. The plaintext markers and config (README.md, .db/config.json, and .db/encryption.pub) are not encrypted. This is useful when storing a database in the cloud or on untrusted storage.
-
Keys: You use a private key stored in your local secure OS keychain vault by Photosphere on your behalf. Keys are referenced by name (e.g.
my-photos). The corresponding public key is stored in the database as.db/encryption.pubso that the database is clearly marked as encrypted. See #The vault below for details. -
Default key: The first key you provide in any command is the default key. It is used to read and write encrypted data and must match the public key in
.db/encryption.pub. Additional keys in the list are used for reading files that were encrypted with those keys (see “Multiple keys” below).
Encrypted files use one of two formats:
-
New format (current): A 44-byte header (4-byte tag
PSEN, format version, encryption type, 32-byte public-key hash) followed by the encrypted payload (RSA-wrapped AES key, IV, AES-256-CBC ciphertext). The key hash in the header identifies which key was used to encrypt the file. - Legacy format: No header; the file starts directly with the encrypted payload. Decryption uses the default key. Old databases created before this format remain readable.
New writes always use the new format. Reading supports both formats so existing encrypted databases keep working.
Commands that accept --key or --dest-key accept a comma-separated list of key file paths. These are merged into a single key map:
- The first key in the list is the default key (used for writing and for reading legacy-format files).
- Every key is also registered under the SHA-256 hash of its public key, so files encrypted with different keys in the same database can be decrypted as long as you provide all the relevant keys.
Example: if your database has some files encrypted with key1 and others with key2, you can run:
psi verify --db ~/photos --key key1,key2 --yesThe first key (key1) is the default and must match .db/encryption.pub if you are writing; both keys are available for reading.
Generate a key and create a new encrypted database:
psi init --db ~/photos --key my-photos --generate-key--key my-photos is the name under which the key is stored in the vault. If the name does not exist yet, --generate-key creates a new RSA-4096 key pair and saves it to the vault automatically. Use the same name for all subsequent operations on that database.
Refer to the key by path or by name (if it’s in the Photosphere config directory):
psi add --db ~/photos ~/new-photos/ --key my-photos
psi list --db ~/photos --key my-photos
psi export --db ~/photos <asset-id> ./output.jpg --key my-photosTo create an encrypted copy (e.g. in the cloud), use --dest-key and optionally --generate-key:
psi replicate --db ~/photos --dest s3:my-bucket/my-photos --dest-key backup --generate-keypsi encrypt transforms the database in place at --db (there is no destination path). Use it to:
-
Plain → encrypted: Turn a plain database into an encrypted one. Use
--key(and optionally--generate-key). -
Re-encrypt with a new key: The database is already encrypted; put the new default key first in
--key, followed by any existing keys needed to read the current database. The new public key is written to.db/encryption.pubonly after the entire database has been re-encrypted. -
Same key / format conversion: You can also run
psi encryptwith the same key to rewrite older encrypted files into the current format.
# Plain → encrypted (in place)
psi encrypt --db ~/photos --key my-photos --generate-key --yes
# Re-encrypt with a new key (in place)
psi encrypt --db ~/photos --key new-key,old-key --yes
# Rewrite using the same key (for example, to convert to the current format)
psi encrypt --db ~/photos --key my-photos --yesOptions: --db, --key (comma-separated key list; first key is the write/default key), --generate-key, --yes. No --dest.
To make an encrypted database plain in place (remove encryption from the database at --db):
psi decrypt --db ~/encrypted-photos --key my-photos --yesAll files are rewritten as plain; .db/encryption.pub is removed. Use --key to supply the key(s) needed to read the encrypted database. There is no destination path; the database at --db is decrypted in place.
Photosphere resolves credentials automatically when you open a named database. For ad-hoc commands you can supply them explicitly.
Photosphere looks for the encryption key in this order, using the first one it finds:
-
--keyflag: always wins. The value must be a vault secret name (see below). -
databases.jsonentry: if the database matches an entry in~/.config/photosphere/databases.jsonwith anencryptionKeyfield, the named vault secret is loaded automatically. This is how the desktop app links a database to its key. -
PSI_ENCRYPTION_KEYenvironment variable: a file path or vault secret name used as a fallback for scripted or automated use.
--key and databases.json entries take a vault secret name: the string must match a secret stored in the vault. Use psi secrets list to see available names.
PSI_ENCRYPTION_KEY accepts either a vault secret name or a file path to a PEM file on disk. This makes it useful for automated environments where keys are managed as files rather than vault entries.
If a vault secret name is not found and the CLI is running interactively, you will be prompted to add the missing key on the spot, either by pasting PEM text directly or by importing from a file. The new secret is saved to the vault under the name you supplied. In non-interactive mode (--yes), the command exits with an error instead.
To pre-import a key that currently exists only as a PEM file:
psi secrets import --private-key /path/to/my-photos.pemS3 credentials and the geocoding API key follow the same pattern:
-
S3:
databases.jsonentry (s3Keyvault secret name) →AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_REGION/AWS_ENDPOINTenvironment variables. -
Geocoding:
databases.jsonentry (geocodingKeyvault secret name) →GOOGLE_API_KEYenvironment variable.
The vault is where Photosphere stores credentials (encryption keys, S3 credentials, API keys) so they do not need to be passed on the command line every time.
By default the vault uses the OS keychain:
- macOS: Keychain
- Windows: Windows Credential Manager
- Linux: libsecret / secret-tool (GNOME Keyring or KWallet)
Set PHOTOSPHERE_VAULT_TYPE=plaintext to use plain JSON files under ~/.config/vault instead. This is primarily used for testing and development, the files are not encrypted at rest by the OS.
Use psi secrets to manage vault contents:
psi secrets list # list all secrets
psi secrets add # add a new secret interactively
psi secrets view # show a secret's value
psi secrets edit # edit a secret in place
psi secrets remove # delete a secret
psi secrets send # share a secret to another device via LAN
psi secrets receive # receive a secret from another device via LANRSA is an asymmetric algorithm: the public key encrypts data and only the corresponding private key can decrypt it. The public key is stored in .db/encryption.pub and is safe to distribute; the private key is stored in the OS keychain (or the configured vault, see above) and must be kept secret.
Each file is encrypted with a freshly generated random 256-bit AES key and 16-byte IV (initialisation vector). The IV ensures that encrypting the same content twice, even with the same key, always produces completely different ciphertext, without it, an attacker could detect duplicate files by comparing ciphertext, and in CBC mode any two files sharing a common prefix would leak that fact. The AES key is encrypted with the RSA public key and stored in the file header alongside the IV and a SHA-256 hash of the public key. The key hash lets Photosphere select the correct private key at decryption time, which is important when a database has been re-keyed or uses multiple keys.
Attack resistance:
- Brute-force: the AES-256 key is 256 bits of cryptographic randomness (~10⁷⁷ possible values). Even a machine checking a trillion keys per second would require around 10⁴¹ times the age of the universe to exhaust the key space. The RSA-4096 key presents a similarly impossible factoring problem for classical computers, the factoring record stands at 829-bit keys.
- Key reuse: each file has its own unique random AES key. Extracting one file's key (which requires the RSA private key anyway) would decrypt only that file.
- Known-plaintext: photo and video files have predictable headers, but knowing a plaintext/ciphertext pair for one file gives no leverage against any other, since every file uses an independent random key.
- Ciphertext correlation: because every file uses a unique key and IV, an attacker with storage access cannot determine whether any two files contain the same content.
- Ciphertext tampering: AES-CBC provides confidentiality but not integrity. This is covered at a separate layer: the Merkle tree detects any modification to any file, so tampering is caught regardless of encryption. Encryption provides confidentiality; the Merkle tree provides integrity.
- Replay attacks: if an attacker replaces a file with an older version, the Merkle tree detects the hash mismatch.
- Cross-database compromise: each database has its own unique RSA key pair. Compromising one database's private key has no effect on any other.
- Padding oracle attacks: AES-CBC without a MAC is theoretically vulnerable to padding oracle attacks. In Photosphere's deployment files are decrypted locally with no oracle exposed to an attacker, so this is not a practical concern.
Known limitations:
- Quantum computing (harvest now, decrypt later): Shor's algorithm on a sufficiently powerful quantum computer could factor the RSA-4096 key and recover the wrapped AES keys. AES-256 is more resistant: Grover's algorithm reduces it to 128-bit effective security, still considered strong. An adversary who records your encrypted storage today could potentially decrypt it if large-scale quantum computing becomes available in future decades. This is a known limitation of all current RSA-based systems.
- Metadata leakage: encryption protects file content, but file sizes, file count, and filesystem timestamps remain visible to anyone with storage access.
- Private key as single point of failure: if the device keychain is compromised by malware, a vulnerable OS, or physical access to an unlocked device, all files protected by that key become decryptable.
- No cryptographic integrity within encrypted files: AES-CBC does not authenticate ciphertext; a bit-flip attack produces garbage plaintext rather than an explicit error. The Merkle tree catches this, but it is a separate mechanism rather than a built-in property of the encryption.
See Security-Overview for the complete threat model and social engineering guidance.
- Do not lose your encryption key. If you lose it, you cannot decrypt the database.
- Back up your key (e.g. in a password manager) as soon as you generate it.
- Commands that need to write to an encrypted database require the default key (first in the list) to match the public key stored in
.db/encryption.pub.