End-to-End Encrypted sharing in zeitkapsl

An explanation of zeitkapsl's end-to-end encryption architecture with a focus on sharing.

written by Peter Spiess-Knafl on August 24, 2025

One of the most common questions we hear is:

“If photos are end-to-end encrypted and only my device can decrypt them — how can I share them with others?”

Closely followed by:

“If I can generate such a link, could zeitkapsl not also do this on the server side?”

Great questions! Let’s take a look how this works.

What is E2EE?

👉 Put simply: Your photos are always locked before they leave your phone, and only the people you choose get the key to unlock them.

Think of your photo album like a photo book with a padlock:

  • When you upload a photo to zeitkapsl, your phone puts it into the book and locks it with a key.
  • That key never leaves your hands — not even zeitkapsl has it.
  • On our servers, we only ever see the closed, locked book.
  • The album travels safely over the internet, still locked.
  • Your device (or your friend’s device) has the key, so it can open the book and show the photos.
  • Without the key, the album is just meaningless scrambled data.

Your Photos Stay Locked đź”’

Every photo or video you upload is encrypted before it leaves your device. Each file gets its own random secret key, and only your devices can unlock it. zeitkapsl’s servers never see your password or your keys in plain text.

  • zeitkapsl can never peek inside — not even by mistake.
  • Hackers or third parties only ever see locked data, never your pictures.
  • Sharing is still possible: when you create a share link, you also hand over a copy of the key (hidden in the link). That way, your friend’s device can open the locked album too — but zeitkapsl still cannot.

When you create a share link:

  1. Your device generates a new share password.
  2. This password locks the album key that’s needed to open the photos.
  3. The server stores only the encrypted version of that album key.

The link looks like this:

https://app.zeitkapsl.eu/s/123456789/#abcdef

  • 123456789 → a reference on our servers to the share (e.g. expiry date, read-only).
  • #abcdef... → the secret share password.

Here’s the important part: The bit after the # (the “fragment”) never gets sent to the server. It only exists inside your friend’s browser or app.

So even though zeitkapsl delivers the encrypted data, we never see the key that decrypts it.

In Summary

  • Every photo has its own key.
  • Keys are nested: your password → your account key → album key → photo key.
  • Sharing works by encrypting the album key with a share password inside the link.
  • The secret part of the link never leaves your browser.

That’s why sharing in zeitkapsl is private, secure, and end-to-end encrypted.


Technical deep dive

This section explains the cryptographic details behind end-to-end encrypted sharing in zeitkapsl.

👉 If you prefer ascii-art: /keys.txt

Here is the complete overview of all involved keys and ciphers with a bit more colors and pixels.

Media encryption

Each media file (photo or video) is encrypted with a unique media key using AES-256-GCM. The encrypted version of each key is stored on the server for syncing. How to decrypt a media key is described in the following sections:

Registration

When you register:

  • A main key is generated on your device.

  • It is derived from your password using PBKDF2 + HKDF and stored encrypted on our server.

  • use sha512(email) as salt and password as keyMaterial for PBKDF2

  • this gives as us the passwordKey

  • using HKDF we derive an encryptionKey and an authToken from the passwordKey

  • the mainKey is generated from a secure random generator

    • the mainKey needs to backed-up on paper or somewhere else, see password reset
  • using HKDF we derive an indexKey and a passworResetToken

    • indexKey is used to decrypt all collectionKey items
      • collectionKey can decrypt mediaKey items
      • which can decrypt the actual media blobs and metadata
    • passworResetToken is used to prove ownership of the mainKey and allows to reset the user password
    • using HKDF from indexKey we get the searchLabelsKey
  • the encryptionKey as secret and mainKey are then wrapped into wrappedMainKey using AES-GCM

  • the authToken is sent and stored on the server using bcrypt and is basically the API password

  • the wrappedMainKey + email + passwordResetToken is sent to the server und stored there

Login

  • User enters email and password
  • PBKDF2 generates the passwordKey
  • Out of the passwordKey we use HKDF to derive two keys:
    • encryptionKey
    • authToken
  • We fetch wrappedMainKey from the server by authenticating using `authToken“
  • We decrypt wrappedMainKey using the encryptionKey via AES-GCM and receive the mainKey

Password reset mechanism

  • During registration the user must confirm that (s)he keeps the recovery kit safe which contains the mainKey in various representations.
  • The user requests a password reset by generating the passwordResetToken out of the reconstructed mainKey
  • Server verifies the passwordResetToken and sends a password-reset-link to the user’s email address.
  • The user then needs to define a new password which is again used to encrypt the mainKey via
  • Generate a new wrappedMainKey and authToken which is then updated on the server

Sharing an Album

When you share an album:

  1. A random share password is generated client-side.
  2. The album’s collection key is encrypted with this share password (AES-256-GCM).
  3. Our servers store only the encrypted collection key and the share metadata (ID, expiry date, permissions).
  4. The final link looks like this:

https://app.zeitkapsl.eu/s/123456789/#abcdef

  • 123456789 = share ID, used to look up the encrypted album key.
  • #abcdef... = share password, known only to the client.

The URL Fragment

The share password is placed in the fragment identifier (everything after the #).

According to the HTTP standard (RFC 3986, Section 3.5), the fragment identifier is never sent to the server in an HTTP request. It is only processed locally by the client (browser or app).

This guarantees that zeitkapsl never learns the share password.


Decryption Flow for a Recipient

Share Password (from #fragment)
↓
PBKDF2
↓
Share Key
↓
HKDF
↓
authKey (BCRYPT) + indexKey (AES-GCM-256)
↓
Collection Key
↓
Media Keys
↓
Files decrypted locally

The entire process happens client-side. Our servers only ever deliver encrypted data blobs.


Summary

  • All media files are encrypted with unique keys (AES-256-GCM).
  • e-mail+password -> PBKDF2 -> HKDF -> authToken + encryption Key -> mainKey -> indexKey + resetToken -> collectionKey -> mediaKey
  • Shares wrap the collection key in an additional layer of encryption using a share password.
  • The share password lives exclusively in the URL fragment (#...), which is never sent to the server (RFC 3986).
  • As a result, zeitkapsl cannot generate, open, or misuse share links.

Frequently Asked Questions

Q: Can zeitkapsl see my photos? No. All media, albums, and metadata are encrypted on your device before upload. Without your password or recovery kit, even we cannot access them.

Q: What happens if I lose my password? You will need your recovery kit (which contains your mainKey). With it, you can reset your password. Without it, your data is cryptographically unrecoverable.

Q: Can zeitkapsl create or open share links? No. The secret share password is embedded in the #fragment part of the link. By web standard (RFC 3986 §3.5), this is never sent to servers. Only the recipient’s device can use it to decrypt.