How It Works - Cryptography Behind Content Proof
Introduction
Content Proof is an attestation (notary) service. It uses SHA-256 to fingerprint your file in your browser and an Ed25519 digital signature, made by the ContentProof server key, to attest that your Google account submitted that fingerprint at a particular time. This page explains each piece and exactly what the result does and does not prove.
The trust model
A valid Content Proof record means:
The ContentProof service attests that the Google account
<email>submitted the SHA-256 hash<hash>at time<timestamp>.
Things to understand:
- ContentProof signs, not you. You hold no key. The ContentProof server key signs every record after checking your Google sign-in.
- No non-repudiation by the user. The signature shows what the service attests, not that you personally signed anything.
- A verifier trusts ContentProof's published key. Verification checks the signature against the key set published at
/.well-known/public-keys.json. If you trust that key and the sign-in check, you can trust the record. - Your file never leaves your browser. Only its SHA-256 hash is sent to the server.
- It is not proof of authorship. It shows who submitted a hash and when, not who created the file.
Digital signatures overview
What are digital signatures?
A digital signature is a mathematical scheme that lets anyone holding a public key check that a message was signed by the matching private key. It provides:
- Authentication: the message came from the holder of the private key. Here, that is the ContentProof service.
- Integrity: the message has not been modified since signing.
How they differ from encryption
| Digital signatures | Encryption |
|---|---|
| Prove who signed | Hide information |
| Anyone can verify | Only the intended recipient can read |
| Detect tampering with the signed message | Prevent unauthorized access |
| Public verification | Private communication |
The Ed25519 algorithm
Why Ed25519?
Ed25519 is a modern elliptic curve signature algorithm (RFC 8032) that offers:
Security
- Designed to resist timing side channels
- Deterministic signatures, so weak random number generation cannot leak the key
- No parameter choices that could weaken security
Performance
- Fast signing and verification
- Small signatures (64 bytes)
- Compact public keys (32 bytes)
Ed25519 is not resistant to large-scale quantum computers. If post-quantum signatures become necessary, the format version field allows moving to a new scheme.
Ed25519 vs other algorithms
| Algorithm | Signature size | Public key size |
|---|---|---|
| Ed25519 | 64 bytes | 32 bytes |
| ECDSA P-256 | about 64 bytes | 33 to 65 bytes |
| RSA-2048 | 256 bytes | about 270 bytes |
| RSA-4096 | 512 bytes | about 550 bytes |
SHA-256 file hashing
What is a hash function?
A cryptographic hash function takes any amount of data and produces a fixed-size fingerprint. SHA-256 produces a 256-bit (32-byte) hash with these properties:
- Deterministic: the same input always produces the same hash
- Avalanche effect: tiny input changes create completely different hashes
- One-way: recovering the data from the hash is practically impossible
- Collision-resistant: finding two inputs with the same hash is extremely difficult
Hash properties in action
Original file: "Hello, World!"
SHA-256 hash: dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f
Modified file: "Hello, World?" (just changed ! to ?)
SHA-256 hash: f5ca38f748a1d6eaf726b8a42fb575c3c71f1864a8143301782de13da2d9202b
A single character change completely changes the hash.
File fingerprinting process
- File selection: your file is read in your browser and never uploaded.
- Chunked processing: files up to 500 MB are read and hashed in chunks, so large files do not exhaust memory.
- Hash calculation: SHA-256 processes every byte of the file.
- Fingerprint: the result is a 32-byte digest, shown as 64 hexadecimal characters.
Any file type is accepted.
The signing process
Step-by-step
Hash the file in the browser
File -> SHA-256 -> 32-byte digestSend the hash to the server. The user must be signed in with Google. The server takes the email from the verified session and the time from its own clock.
Build the message. The signed message is this UTF-8 string:
<hash_hex>:<email>:<timestamp>hash_hexis the digest as 64 lowercase hex characters,emailis the raw Google account email, andtimestampis milliseconds since the Unix epoch in decimal.Sign with the ContentProof key
signature = Ed25519_Sign(ServerPrivateKey, message)Package the record. The verification link is:
https://<host>/verify#P=<payload>and the payload is six fields separated by tildes:
2~<kid>~<hash>~<email>~<timestamp>~<signature>
| Field | Meaning |
|---|---|
2 |
Format version |
kid |
Key id: first 16 characters of base64url(SHA-256(SPKI DER of the public key)) |
hash |
base64url, no padding, 43 characters, of the 32-byte digest |
email |
encodeURIComponent(email), with any ~ also encoded as %7E |
timestamp |
Milliseconds since the Unix epoch, decimal |
signature |
base64url, no padding, 86 characters, of the 64-byte signature |
Note the hash is hex in the signed message but base64url in the payload.
The payload sits in the URL fragment (after #). Browsers never send the fragment to the server, so opening a verification link does not disclose the record to ContentProof.
Why this design?
- Including the hash ties the signature to the exact file content.
- Including the email records which Google account the service saw submit the hash.
- Including the timestamp records when the service saw it. The time comes from the ContentProof server, not from the user.
- Including a key id lets verifiers pick the right key after rotation.
- Colon separators give an unambiguous message structure.
The verification process
Steps
An independent verifier can follow these steps without ContentProof software:
- Fetch the key set from
https://<host>/.well-known/public-keys.json:{ "current": "<kid>", "keys": { "<kid>": { "keyId": "<kid>", "algorithm": "Ed25519", "publicKey": "<base64 SPKI DER>" } } } - Look up the key. Find
keys[kid].publicKey. It is base64 SPKI DER; the raw 32-byte Ed25519 key is the last 32 bytes. Reject the record if the key id is absent. - Hash the file with SHA-256 and compare it with the payload hash.
- Rebuild the message
<hash_hex>:<email>:<timestamp>and verify the Ed25519 signature with the public key.
The Content Proof verifier does this in your browser and caches the key set so it works offline once cached. It never trusts a key carried inside the QR code, because anyone could forge a QR code containing their own key.
Results
- Valid: the signature matches a published ContentProof key and the file's hash matches the record.
- Invalid: the file was modified, the link was damaged, or the key id is not published.
The mathematics behind verification
If Verify(PublicKey, Message, Signature) = True:
Then the holder of the matching private key signed this exact Message
And the Message has not been modified since signing
For Content Proof, the holder of the private key is the ContentProof service.
Legacy QR codes
Codes created before this format used ?data=<base64url JSON>. They are still accepted only when the key they carry matches a published key, and the verifier asks for the signer's email.
Security model
What the design protects against
File tampering
- Any change to the file changes its hash and fails verification.
- Accidental corruption is caught.
Forged records
- Without the ContentProof private key, nobody can produce a record that verifies against the published key set.
- Embedding an attacker's key in a QR code does not help, because verifiers only use published keys.
Altered email or time
- The email and timestamp are inside the signed message, so changing either breaks the signature.
Eavesdropping on the link
- The payload is in the URL fragment and is not sent to the server by the browser. HTTPS protects the key set and sign-in traffic.
Security assumptions
- Google sign-in correctly identifies the account holder.
- Ed25519 and SHA-256 remain secure.
- The ContentProof private key stays confidential.
- The ContentProof server clock is accurate.
What it does not protect against
Compromised Google accounts
- Anyone who controls your Google account can obtain attestations as you. Use strong passwords and two-step verification.
Compromised service key
- If the ContentProof private key leaks, an attacker could forge records. Key rotation limits the damage going forward, but records signed before a compromise cannot be told apart from forged ones by signature alone.
Authorship and ownership disputes
- A record shows who submitted a hash and when. It does not show who created the file. Combine it with other evidence when needed.
Non-repudiation by the user
- The user holds no key, so a signature cannot be used to show the user personally signed.
Quantum computing
- Ed25519 is not quantum-resistant.
Key management
- The Ed25519 private key is supplied to the server through environment variables. In production the server refuses to sign if it is missing.
- The public keys are published at
/.well-known/public-keys.json, with acurrentkey id and a map of key ids to keys. - When keys are rotated, previous public keys stay in the published set so older records continue to verify.
- The key id in each record selects the key to use.
Client side
- File hashing uses the browser's Web Crypto API and runs locally.
- File contents are never transmitted to ContentProof.
- Verification works offline once the key set has been cached.
Signature formats at a glance
Raw signature: 64 bytes of binary data.
In the payload: base64url without padding, 86 characters.
QR code content:
https://<host>/verify#P=2~<kid>~<hash>~<email>~<timestamp>~<signature>
Comparison with other approaches
Content Proof vs C2PA
| Feature | Content Proof | C2PA |
|---|---|---|
| Where the proof lives | In a QR code or link, separate from the file | Embedded in file metadata |
| Who signs | ContentProof service key | Certificates held by the creator or tool |
| Trust anchor | ContentProof's published key | Certificate authorities |
| Survives metadata stripping | Yes, because it travels separately | Often lost in processing |
| Verification | Browser, with a QR code or link | Specialized tools |
Content Proof vs blockchain timestamping
| Feature | Content Proof | Blockchain solutions |
|---|---|---|
| Speed | Immediate | Minutes to hours |
| Cost | Free | Fees required |
| Trust anchor | A single service key | Distributed ledger |
| Timestamp | Server clock, attested by ContentProof | Block time, independent of any one party |
| User experience | Sign in with Google | Wallet management |
Technical resources
- RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
- FIPS 180-4: Secure Hash Standard (SHA-256)
- RFC 4648: Base64url encoding
- Web Crypto API: browser-native hashing
- Node.js crypto: server-side Ed25519