Skip to content
Credo

Security

What actually happens to your secret

No hand waving. This is the full pipeline, the exact storage schema, and an honest list of the things this design does not protect you from.

01Key derivation

Argon2id stretches the passphrase against a fresh 16 byte random salt to produce a 256 bit key, using 46 MiB of memory per attempt. Memory hardness is the point: a graphics card can run thousands of simple hashes in parallel, but it cannot hold thousands of 46 MiB working sets at once, so mass guessing stops being cheap. All the cost parameters are written into the payload, so raising them later never breaks links already in circulation.

02Encryption

AES-256-GCM seals the bytes using a fresh 12 byte random nonce. GCM is authenticated, so a modified payload fails to open rather than decrypting into something plausible but wrong.

03Envelope

A magic header, the format version, the round count, the salt and the nonce are packed in front of the ciphertext and the whole thing is base64 encoded. Everything needed to decrypt is present except the one thing that matters, which is the passphrase.

04Storage and expiry

The envelope is written to a single database record along with a creation time and an expiry time. Every share has one, thirty days by default and thirty days at most, and the ceiling is applied in the write path as well as in the picker. Reads are refused once the expiry passes, and a scheduled cleanup deletes the record.

What the database is allowed to do

The rules are enforced by Firestore itself, not by the interface, so they hold even if someone talks to the database directly with their own script.

  • A record can be read one at a time by exact id, and only while its expiry is still in the future.
  • Listing the collection is refused outright, so nobody can enumerate shares or count them.
  • Updating and deleting are refused, so a stored record cannot be swapped for a different one.
  • Creating a record is only accepted with the exact five field shape, which keeps anything unexpected out of the database.
The whole record
{
  encrypted_data: string   // base64 envelope, unreadable without the passphrase
  created_at:     timestamp
  expires_at:     timestamp
  file:           boolean  // present only for file shares
  metadata:       { name, type }  // present only for file shares
}

No user id. No IP address. No recipient. No passphrase, no hash of one, and no hint. A file share does reveal its original name and MIME type, so avoid putting anything sensitive in a file name.

What this does not protect you from

Any tool that claims to have no weaknesses is hiding them. These are the ones that matter here.

A weak passphrase is still a weak passphrase

Key stretching buys time, it does not create entropy. If the passphrase is a dictionary word, someone with the ciphertext can eventually get through. Use the dice button.

Both halves in one thread defeats the point

If the link and the passphrase land in the same chat, anyone reading that chat has the secret. Send them over different channels.

A compromised browser sees plaintext

Encryption in the browser means the browser holds the plaintext for a moment. Malware, a hostile extension or someone watching the screen sits inside that moment.

Credo trusts the code you are served

Like every web application that encrypts in the client, you are trusting that the page you loaded is the page that was published. The source of the original project is public, and the deployment is static.

Reads are not counted

The database rules block writes after creation, which also means Credo cannot mark a share as opened. A link works as many times as needed until it expires. For a single handoff, choose the shortest expiry.

A note for people arriving from Credenstore

The earlier version of this tool used TripleSec, which stacks three ciphers. The cascade is the memorable part, but it is not where the security of a tool like this lives. The only secret here is a passphrase a person chose, so what matters is the cost of one guess to somebody holding the ciphertext, and that is set by the key derivation function rather than by the number of ciphers. A cascade guards against a future break in AES-256, which nobody has. A weak derivation function is exploitable today with a rented graphics card. So the cipher is one well studied authenticated mode running in the browser's own native code, and the effort went into Argon2id instead.

Reporting something

If you find a flaw, please report it rather than publishing it first. The fastest route is a message through shrinath.me. Fixes are shipped quietly and quickly.

Still curious? The questions page covers the practical side.