August 6, 2026
Why my local app has a file called encryption.py that does no encryption
A hidden marker in AI-generated frames, what a local key can honestly promise, and the difference between a label and a lock.

By Wlad Radchenko
5 min read
A hidden marker in AI-generated frames, what a local key can honestly promise, and the difference between a label and a lock.
Open the source of my desktop app and you will find a file named encryption.py, a class called EncryptionEncoder, and a video method called encrypted(). If you trusted the names, you would assume the app scrambles its output behind a key, the way a password manager scrambles your vault.
It does nothing of the kind. There is no cipher in that file. No key, no AES, no Fernet, no secret of any sort. What the code actually does is hide a short string inside the pixels of a frame, invisibly, so that a program can later read it back. That is a watermark. I am going to tell the story of why a fully local app wants one, how this one works, and why the word on the file is the wrong word.
The question a synthetic-media tool keeps asking itself
My app makes synthetic video. Face swaps, lip-sync, retargeted portraits. The more convincing the output, the more one mundane question matters: when a file lands back in front of the app, is it an untouched original, or something the app produced earlier?
You could answer that with a visible logo in the corner. But a visible mark is ugly, and it is gone the instant someone crops it. So the better answer is a mark you cannot see and a program can still find. Embed a hidden string when you generate a file. Read for that string when you analyze a file. If it is there, the file came from you.
That is exactly what the analysis path does. It calls decrypted() and logs one of two outcomes: "Content is original" or "Content is Wunjo fake." The whole apparatus exists to produce that one boolean. The hidden string it stamps and hunts for is the four letters fake.
Hiding a string where the eye does not look
The interesting question is where in an image you can hide thirty-two bits so they are both invisible and durable. You cannot just flip the least significant bits of the pixels. Those die the moment the file is compressed. You need somewhere that survives a wash.
The answer the code reaches for is the frequency domain, and it gets there in three moves.
First it converts the patch from BGR into YUV and works only on the luminance and one chroma channel. These are the dimensions where a small nudge does not catch the eye.
Then it runs a Haar wavelet transform and keeps the coarse band, the low-frequency summary of the patch. Coarse coefficients are the survivors. Resize the image, compress it, save it again, and the fine detail is the first thing to go while the coarse structure stays. So the watermark lives in the part of the image that handling tends to preserve.
Then, inside that coarse band, it slices the patch into tiny four-by-four blocks and for each block computes a singular value decomposition. The largest singular value of a block is, roughly, how much energy that block carries. The code quantizes that single number. It rounds it down to a multiple of thirty-six, then pushes it to either a quarter or three-quarters of the way into the next thirty-six-wide bin, depending on whether the bit it wants to store is a zero or a one.
To read the bit back, you only ask where in the bin the value sits. Low half, zero. High half, one. The reason it parks the value at a quarter and three-quarters, rather than right at the edges, is to keep each bit as far from the decision line as possible. Compression jostles these numbers. Park them in the middle of their safe zones and the jostling has to be large before a bit flips.
Repetition is the error correction
A four-by-four block holds one bit. A patch the size of a small thumbnail holds hundreds of blocks. The code does not need hundreds of bits, it needs thirty-two. So it writes the same thirty-two bits again and again across the patch, cycling through them block after block.
This is the quiet cleverness of the scheme. When it reads the watermark back, it gathers every copy of bit number five, takes the average, and only then decides if it is a zero or a one. Compression might corrupt a few blocks here and there. The average over a hundred copies of the same bit barely flinches. It is the simplest error correction there is, redundancy plus averaging, and for this job it is enough.
That redundancy is the reason the marker survives a normal save-and-reopen. The app reassembles its video frames with a stream copy rather than a re-encode, so the watermark goes in clean, and even when a file later passes through a lossy platform transcode, enough copies of each bit usually make it through to recover the string.
There are small touches that show the code was tuned against real footage rather than a textbook. It only marks a single small region of a frame, chosen at random from the corners and the center, and on video it skips some frames entirely. Branding every pixel of every frame would risk a visible shimmer. Sprinkling the marker keeps it invisible while keeping it findable. And before it stamps anything, it reads first and skips if the marker is already present, so running the tool on its own output does not pile one watermark on top of another.
A watermark looks like security. It is closer to a label written in invisible ink.
What a local key can and cannot promise
Here is the part I think gets glossed over whenever someone bolts the word "encryption" onto a desktop app.
Real encryption rests on a secret the other side does not have. A key. Take the key away and the scheme is just a public algorithm doing a public thing. Now look at where this code lives. It runs entirely on the user's own machine. The encoder is there. The decoder is there. The marker string fake is sitting in plain Python in a public repository. Nothing is hidden from the person running the app, because there is nowhere on their own computer to hide it from them.
So this scheme makes none of the promises a cipher makes. It does not stop anyone from copying or editing the output. It is not digital rights management, it locks nothing. And it is not a cipher, despite the name on the file, because there is no key and no attacker who lacks one. Anyone who reads the repo can detect the marker, strip it out, or stamp it onto a file the app never touched.
What it does buy is provenance under cooperative conditions. If a file moves through ordinary handling, a re-save, a crop that spares a corner, a platform that re-compresses it, the hidden string usually rides along, and the app can say with reasonable confidence "this came from me." That is a real and useful property for a tool that generates synthetic media and wants to flag its own work. It is a label, invisible and reasonably sticky, not a lock.
The skill is matching the mechanism to the threat. The threat here is not a determined adversary, it is the ordinary erosion of context as a file gets passed around and re-saved until nobody remembers where it came from. Against that, an invisible marker that survives normal handling is a good answer. Against someone who wants the marker gone, it offers nothing, and it should not pretend to.
Which leaves the naming. A watermark named encryption.py is not a lie so much as a trap. It invites every future reader, the author included, to reason about a security model the code was never built for. The honest version of this story is short: the app writes its signature in invisible ink, the ink survives a wash but not a scrub, and anyone holding the same pen can sign too. Calling that encryption only makes it harder to see what it really is.