Skip to content
← All posts

How tapeshelf encrypts video, and why it stores no keys

A private video on tapeshelf is for its owner and the people the owner chose. Here’s how the app holds to that, and where the protection ends.

From upload to stream

Your browser uploads the file straight to a private bucket in S3-compatible storage, in 16 MB parts that resume if the connection drops. The file never passes through our API servers.

A background worker checks the file with ffprobe, then strips its metadata (place and time of shooting, camera, author, comments) by rewriting it without re-encoding. That cleaned file is what people download. Videos uploaded before 18 October 2026 keep their metadata.

H.264 video at up to 1080p that passes a few more checks is only repackaged, which takes seconds. Anything else is converted to H.264 at up to 1080p. ffmpeg then cuts the result into six-second HLS segments, which Safari plays natively and other browsers play through hls.js.

Encrypting the segments

ffmpeg can encrypt HLS by itself, but it wants the key in a file on disk. We didn’t want a key on any disk, so the worker encrypts the segments itself:

  • each segment is encrypted with AES-128 in CBC mode, the method the HLS standard defines;
  • the key is 16 bytes, unique to the video, and exists only in the worker’s memory while it works;
  • the initialization vector is the segment’s sequence number, which is what an HLS player assumes when the playlist doesn’t name one.

Only the encrypted segments and their playlist go to the private bucket. The worker’s temporary files are deleted when the job ends.

Keys that are never stored

After that, the key isn’t kept anywhere. Instead of generating a random key and guarding it, tapeshelf derives it each time it’s needed:

video key = HKDF-SHA256(master key, "video:" + video id + version) → 16 bytes

Editing a video raises the version, which gives it a new key.

The master key is one secret in the server’s configuration. It isn’t in the database, the buckets or the backups. A copy of the database holds no video keys and the bucket holds only encrypted segments, so an attacker would need the server’s configuration too.

Lose the master key and no video will play again, and no database backup brings it back. That matters most if you run tapeshelf on your own server.

Three requests when you press play

  1. Playback. The API checks that you may watch: you’re the author, the video is on your shelf, or it’s public. Then it returns a playlist address with a signed token inside, naming the video and you (or no one, if you aren’t signed in), valid for an hour.
  2. Playlist. The API checks the token and your access again and rewrites the playlist on the fly. Every segment becomes a signed storage link, and the key line points back at the API.
  3. Key. The token must be for this video. For a private video, your signed-in session must also belong to the person named in the token. Only then does the API derive the key and send its 16 bytes.

A temporary link has no account to check, so on its page the token names the link. Each request checks that the link still works and still holds the video, and the token never outlives the link. Videos a guest received in an exchange play the same way on the exchange page, for a week.

Access decisions are cached for a minute, so a two-hour film doesn’t hit the database on every request. A deleted video can still be started within that minute. A temporary link that runs out or is revoked stops at once. Either way, a player already running keeps its key and segment links and may play on.

Segment links are signed and expire too, after an hour or more. The player loads the playlist once, and short links would break a simple pause. When one expires, the player asks for fresh playback and carries on from the same spot. Without the key, a segment is useless anyway.

Why there is no file to grab

The player never gets a link to the video file. It gets a playlist bound to a token, encrypted pieces, and a key behind a check. Copy a private video’s playlist address into a friend’s browser and nothing plays, because the key needs your session.

Downloading is separate. Signed-in people can download what they may watch. Without an account you can download only through a temporary link someone sent you, or what you received as a guest in an exchange, for a week. A download link lasts two minutes.

Images work differently. Covers, the 16 frames taken from each video and the frame preview made from them sit in a public bucket under long random names, and the bucket can’t be listed. A private video’s preview is visible on purpose: it shows a visitor what they would be asking for.

What this protects against

  • People without access get no playlist, no segment and no key.
  • A leaked segment link is encrypted noise. A leaked playlist link to a private video doesn’t work without the session of the person it was issued to, and it expires within an hour anyway.
  • A stolen copy of the database reveals no keys.

What it doesn’t

  • Anyone who can watch can record. A person with access can record their screen or save the stream with the right tools. They were given the key legitimately. tapeshelf has no DRM. Real DRM such as Widevine or FairPlay depends on commercial license servers, and it still can’t stop a camera pointed at a screen.
  • The people you share with. A share or a trade gives the other person a permanent copy, and they can download the file. So can anyone who opens a temporary link you sent, while it works. What happens to the file next is out of our reach. Share with people you trust.
  • The server itself. This isn’t end-to-end encryption. The server derives the keys, so whoever runs it can play any video. On tapeshelf.com that’s us, and we open private videos when it’s needed to run and moderate the service. On a self-hosted instance, it’s the admin.

The short version is on the security page. If you’d rather check than take our word for it, the code is open source, and you’ll be able to run it yourself.