Security model

What TinyShare protects, what it cannot, and exactly how a package is built. Read this before you rely on it.

Security profile

Enginetinyshare-engine 1.0.0 / TSPK v1
ContainerTSPK version 1 (76-byte authenticated header)
CipherAES-256-GCM, 96-bit nonces, 128-bit tags (Web Crypto)
Key derivationHKDF-SHA256 with a per-package 128-bit salt; separate outputs for the encryption key and a 256-bit key commitment
Random keys256 bits from crypto.getRandomValues
PassphrasesPBKDF2-HMAC-SHA256, 600,000 iterations by default; packages outside 100,000–5,000,000 are refused before any work
Chunked profileSTREAM-style: nonce = 7-byte random prefix ‖ 32-bit chunk counter ‖ last-chunk flag; header as AAD on every chunk
Offline decoderv1.0.0, SHA-256 df8f80acfa3fa16d…
Independent reviewNone yet. This build has not been audited by a third party.

Using standard primitives through Web Crypto is not the same as a reviewed protocol. The design below follows well-known constructions and is covered by known-answer, differential and tamper tests in the source repository, but those tests are ours, not an audit.

What it protects against

  • Someone who gets the package but not the key reading file contents, names, count, individual sizes or your note.
  • Undetected changes: any altered, added, removed, reordered or truncated byte makes decryption fail with no output.
  • A package crafted to decrypt differently under two keys (prevented by the key commitment).
  • Hostile packages that try to exhaust your device with huge work factors, oversized lists or decompression bombs: these are bounded and refused.
  • Hostile file names that try to write outside a folder or hide their real extension.

What it cannot protect against

  • A key sent alongside the package in the same channel.
  • A weak passphrase: anyone with the package can guess offline, at their own speed.
  • Malware, browser extensions or someone with access to your device or the recipient’s.
  • The recipient keeping, copying or forwarding the files. Nothing can revoke a copy.
  • Learning the approximate total size, and whether a passphrase was used.
  • Impersonation: a valid package proves the key, not the person.
  • A compromised copy of this website serving altered code. The offline decoder with a checked hash reduces this risk for opening packages.

Promises we deliberately do not make

An earlier idea for TinyShare was a hosted, self-destructing link. That needs a server, and even a server cannot unsend what was already downloaded. So these are excluded, not hidden behind local timers:

  • Expiry enforcement. You can store a delete-after date inside the package. It is displayed as a request; the package still opens afterwards.
  • Download caps. No client-side code can count downloads across other people’s devices.
  • Revocation. Re-packaging with a new key protects future recipients only.
  • Hosted links or storage. There is no upload endpoint. You choose how to move the package.
  • Malware-free guarantee. Encryption and checksums say nothing about whether a file is safe.

Container format, TSPK v1

Header (76 bytes, public, used as AES-GCM additional data): signature TSPK, version, profile, key mode, reserved byte, PBKDF2 iterations (u32, big-endian), 16-byte salt, 12-byte nonce field, chunk size (u32), 32-byte key commitment. The inspector shows each field.

Keys. Input key material is the 32-byte random key, or PBKDF2-HMAC-SHA256(NFC passphrase, salt, iterations, 32 bytes). Then K_enc = HKDF-SHA256(ikm, salt, "TinyShare TSPK v1 aes-256-gcm key") and commitment = HKDF-SHA256(ikm, salt, "TinyShare TSPK v1 key commitment").

Payload (encrypted): TSMF ‖ manifest length (u32) ‖ manifest JSON ‖ file bytes in manifest order. The manifest lists each file’s ID, sanitised name, original size, stored length, compression, type hint and SHA-256 (for files up to 64 MiB). Decoders reject unknown fields, unsafe names, duplicate names or IDs, and any mismatch between stored lengths and payload size.

Whole profile: one AES-256-GCM operation over the payload with the header nonce. Chunked profile: the payload is cut into fixed-size chunks; chunk i uses nonce prefix‖i‖last, where last is 1 only for the final chunk. This is the STREAM construction of Hoang, Reyhanitabar, Rogaway and Vizár (2015), also used by tools such as age. Each package has a fresh salt and therefore a fresh key, so nonces never repeat under a key, and a restarted encryption always starts a new package.

Failures. Wrong key and tampering produce the same message and no output. Malformed headers and unsupported versions are reported specifically, because those reveal nothing secret.

Memory and storage

Keys, passphrases and files live only in this tab’s memory. Nothing is written to cookies, localStorage or IndexedDB, apart from your theme choice. “Clear everything” drops our references and zero-fills key buffers we control, but JavaScript engines can keep copies, so this is not forensic erasure. Close the tab when you are done.

Network

The site is static files with a strict Content-Security-Policy. The encryption and decryption pages make no network requests with your data, load no analytics or ad scripts and use no remote fonts. The only fetch is the public sample package, when you ask for it. Direct transfer is the one feature that opens a network connection, and only to the other browser.

Reporting a problem

If you find a flaw, please describe it with a synthetic example package rather than real files. Fixes that change the format will ship as a new container version with a new decoder; version 1 packages will stay readable.