.crin File Format
Version 1 · Status: stable
A .crin file is a self-contained encrypted container. Everything a reader
needs is in the file except the key, which lives on the dongle that wrote it.
1. .crin — the format new implementations write
Section titled “1. .crin — the format new implementations write”Header is 66 bytes.
offset size field 0 4 magic, ASCII "CRIN" 4 1 version, = 1 5 1 flags (bit 0 = payload is a ZIP of a folder; other bits reserved, 0) 6 32 K_identity public key of the encrypting dongle 38 16 HKDF salt (random, per file) 54 12 AES-GCM nonce (random, per file) 66 n ciphertext 66+n 16 AES-GCM tag- AAD is bytes
[0, 66)— the whole header, exactly as it appears on disk. Keep the slice; do not rebuild it from parsed fields. - File length is always
66 + n + 16. Anything shorter than 82 bytes cannot be valid. - Flags bit 0 means the plaintext is a ZIP archive of a directory rather than a single file. On decrypt, unzip it back into a folder.
- Reject unknown flag bits rather than ignoring them, so a future flag cannot be silently misread as “plain file”.
- The ciphertext and the tag are adjacent on disk: one run of
n + 16bytes, identical to what a combined-tag API produces.
The file name is not in the format
Section titled “The file name is not in the format”There is no name field. The only thing carrying the original name is the name on
disk, which is why the convention is to append .crin when encrypting and strip
it when decrypting. Anything cleverer loses the name.
2. Where the encryption happens
Section titled “2. Where the encryption happens”On the dongle, not on the host. Since firmware 2026-08-02 the file key never
reaches the computer: the host streams plaintext to the dongle in chunks over
HID and receives ciphertext back (CMD_ENCRYPT_BLOCK / CMD_DECRYPT_BLOCK, see
the protocol). The dongle derives the per-file key with
HKDF over the salt in the header and runs AES-256-GCM itself.
Two consequences worth knowing before you design around it:
- Encryption is gated by the licence, decryption never is. A file already encrypted opens for as long as that dongle exists — expired licence, revoked licence, no network. The licence governs the product; the dongle governs the data.
- The per-transfer ceiling of 5 000 000 bytes belongs to the Windows client
(Personal v1,
MaxEncryptBytes), not to the firmware or to the format, which carry up to ~900 MB; a developer driving the SDK directly is not subject to it. The reason it exists is time: at the measured ~27.8 KB/s of the HID transport, 5 MB is already about three minutes in each direction. Where the ceiling applies to a folder it applies to the compressed archive, not to the folder: 20 MB of text may pass where 4.8 MB of photos does not.
3. .cryptin — legacy, read-only for new implementations
Section titled “3. .cryptin — legacy, read-only for new implementations”Header is 69 bytes.
offset size field 0 8 magic, ASCII "CRYPTIN1" 8 1 version, = 1 9 32 K_identity public key of the encrypting dongle 41 16 HKDF salt 57 12 AES-GCM nonce 69 m ciphertext with the 16-byte GCM tag appended- AAD is bytes
[0, 69). m = n + 16: the writer emitted ciphertext‖tag as one buffer.- No flags byte — the folder concept does not exist in this container, so there is nothing to preserve when reading one.
New implementations read this and never write it.
4. Telling them apart
Section titled “4. Telling them apart”The two magics diverge at byte 2 (CRIN vs CRYP), so the first four bytes are
enough.
read the first 8 bytes
bytes[0..4) == "CRIN" -> .crin, header 66, §1 bytes[0..8) == "CRYPTIN1" -> .cryptin, header 69, §3 anything else -> not a Crypt-in fileDo not dispatch on the file extension. A user who renames a file must still get the right reader, and a wrong extension must not turn a legible error into a corrupt-file error.
Check the version byte after the magic and reject anything other than 1, with
a message that names the version you found — a future v2 reaching an old reader
should say so plainly.
5. Reader algorithm
Section titled “5. Reader algorithm”1. length >= 82 (.crin) or >= 85 (.cryptin), else "file too short"2. magic -> pick format (§4), else "not a Crypt-in file"3. version == 1, else "unsupported version N"4. .crin only: reject unknown flag bits5. slice pubkey, salt, nonce at the offsets for that format6. header = the bytes before the ciphertext — keep the exact slice, it is the AAD7. compare the header pubkey with the attached dongle's K_identity8. hand ciphertext to the dongle for decryptionStep 7 is not an optimisation
Section titled “Step 7 is not an optimisation”Comparing the pubkey before sending anything to the dongle is what lets you tell “this file belongs to a different dongle” from “this file is damaged”. Underneath they are the same AEAD failure, and collapsing them is the one error that makes the warning useless: the two have opposite advice — find the right dongle, versus restore from backup.
Because the header is the AAD, a tampered pubkey would fail the tag anyway. If your reader reports “damaged” for a file encrypted by another dongle, the fast comparison is not running and your users are getting the wrong advice.
The comparison runs in the client, not in the firmware: the dongle never sees the header and decrypts for whichever unlocked dongle is physically connected. Writing your own client means implementing step 7 yourself — and it means the check is worth exactly what the machine running it is worth. See dongle binding.
6. What the format deliberately does not have
Section titled “6. What the format deliberately does not have”- No file name, see §1.
- No key wrapping. There is nothing in the file that a stolen key could unwrap. Without the dongle that wrote it there is no route back to the plaintext, and that is the entire design.
- No compression, except the folder ZIP that flags bit 0 announces.
- No version negotiation. A reader that meets a version it does not know stops and says so.
The normative source for this page is CRYPTIN_FILE_FORMAT.md in the SDK
repository. Where this page and that document disagree, the document is right
and this page is the bug — please report it to
security@lake8.dev if the difference is
security-relevant, or open an issue otherwise.
Terms of ServicePrivacy PolicySecurity contact: security@lake8.devVulnerability Disclosure Policy
Alcuni contenuti sono stati redatti con il supporto di strumenti di intelligenza artificiale generativa e revisionati dall'autore. Le immagini hardware hanno scopo puramente illustrativo.
Some content was drafted with the support of generative AI tools and reviewed by the author. Hardware images are purely illustrative.
Einige Inhalte wurden mit Unterstützung generativer KI-Werkzeuge verfasst und vom Autor überprüft. Hardware-Abbildungen dienen ausschließlich illustrativen Zwecken.
Algunos contenidos han sido redactados con el apoyo de herramientas de IA generativa y revisados por el autor. Las imágenes de hardware tienen carácter meramente ilustrativo.
In caso di conflitto tra versioni linguistiche, prevale il testo in lingua italiana.