Encrypt on writing, not only on reading - #8
Merged
Merged
Conversation
The reader has understood every revision of the standard security handler since its third wave; the writer could only produce files in the clear. Now it can protect one: AES-256 by default, AES-128 for readers older than 2008, with a user password, an owner password and the permissions a viewer is asked to respect. Order matters twice over. A stream is compressed and then encrypted, never the other way, because encrypted bytes do not compress. And the /Encrypt dictionary and the cross-reference stream are written untouched, since a reader needs both before it has a key. Packing and encryption meet correctly: objects inside an object stream are not individually encrypted, the stream that holds them is — which falls out of doing the work where an object is written rather than where it is handed over. Two things this found. An owner password nobody set was written as the empty one, so any password at all opened the file with every permission — the exact opposite of asking for encryption. It is now the user's password, which is what the pre-2.0 algorithms have always done and what the AES-256 path was missing. And randomness that fails must refuse the file rather than hand back the bytes it could not protect; the source of randomness is a variable so a test can take it away and watch every draw give up in turn. Measured on the corpus: every one of the 118 833 files is written behind a password in both methods and read back with the user's and the owner's — all 118 833 match in both, and all 237 666 written files refuse to open without a password. An encrypted file is deliberately not reproducible: encryption needs randomness, so the writer stops being a function of its input, and only there. Exact 100% statement coverage, go vet clean, nine cross-compilation targets. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The reader has understood every revision of the standard security handler
since its third wave; the writer could only produce files in the clear. Now it
can protect one: AES-256 by default, AES-128 for readers older than 2008, with
a user password, an owner password and the permissions a viewer is asked to
respect.
Order matters twice over. A stream is compressed and then encrypted, never the
other way, because encrypted bytes do not compress. And the /Encrypt dictionary
and the cross-reference stream are written untouched, since a reader needs both
before it has a key.
Packing and encryption meet correctly: objects inside an object stream are not
individually encrypted, the stream that holds them is — which falls out of
doing the work where an object is written rather than where it is handed over.
Two things this found. An owner password nobody set was written as the empty
one, so any password at all opened the file with every permission — the exact
opposite of asking for encryption. It is now the user's password, which is what
the pre-2.0 algorithms have always done and what the AES-256 path was missing.
And randomness that fails must refuse the file rather than hand back the bytes
it could not protect; the source of randomness is a variable so a test can take
it away and watch every draw give up in turn.
Measured on the corpus: every one of the 118 833 files is written behind a
password in both methods and read back with the user's and the owner's — all
118 833 match in both, and all 237 666 written files refuse to open without a
password.
An encrypted file is deliberately not reproducible: encryption needs
randomness, so the writer stops being a function of its input, and only there.
Exact 100% statement coverage, go vet clean, nine cross-compilation targets.