| Version | Supported |
|---|---|
| 1.x | ✅ Active development |
| < 1.0 | ❌ Unreleased |
If you discover a security vulnerability in cipherlock, please do not open a public GitHub issue. Instead, email the maintainer directly at valon.mulolli@outlook.com.
We will acknowledge receipt within 48 hours and aim to provide a detailed response (including a fix timeline) within 7 days. Once a fix is released, we will publish an advisory via GitHub Security Advisories.
| Function | Algorithm | Standard / Reference |
|---|---|---|
| Symmetric cipher | AES-256-GCM | NIST SP 800-38D |
| Key derivation (pwd) | Argon2id | RFC 9106 / IETF |
| Key agreement | X25519 + HKDF-SHA256 | RFC 7748, RFC 5869 |
| Random nonces | 12 bytes | crypto/rand |
| Checksum | SHA-256 | FIPS 180-4 |
| Compression (opt) | zstd | RFC 8878 / klauspost/compress |
All cryptographic operations use the Go standard library (crypto/aes,
crypto/cipher, crypto/sha256, crypto/rand) with the exception of
golang.org/x/crypto/argon2 and golang.org/x/crypto/curve25519 which
are maintained by the Go team under the golang.org/x/crypto umbrella.
cipherlock is designed for confidentiality and integrity of data at rest under the following adversary model:
In scope:
- Ciphertext-only attacks: An adversary who has obtained encrypted files cannot recover plaintext or the encryption key without the password or identity private key.
- Integrity attacks: AES-GCM authenticates encrypted chunks. New streaming formats (v0x09-v0x0B) also bind the format version and interpretation flags as associated data, so changing compression, metadata, or checksum flags is rejected. Legacy v0x06-v0x08 files remain readable but do not authenticate those header flags.
- Dictionary attacks against password-based encryption: Argon2id's memory-hard KDF raises the cost per guess. Default parameters (time=3, memory=64MB, threads=4) follow OWASP recommendations.
- Metadata confidentiality: When using v0x06+ formats with the
FileMetaoption, the original filename and size are encrypted and not visible without the password. - Forward secrecy for asymmetric encryption (v0x08 and v0x0B): Each encryption generates an ephemeral X25519 key pair. Compromise of a recipient's long-term private key does not reveal past ciphertexts (the ephemeral private key is discarded after encryption).
- Side-channel resistance: The Go runtime's constant-time big-number routines are used for field operations. AES-GCM is implemented in assembly/aarch64 hardware instructions on supported platforms.
Out of scope (assumed secure):
- The user's operating system (keystroke logging, process memory
inspection by privileged processes, swap/file-backed memory). cipherlock
explicitly clears derived keys with
clear()after use, but this is a best-effort mitigation — a kernel-level attacker can bypass it. - Key distribution: X25519 public/private key pairs generated by
dialare as secure as the user's file permissions and backup practices. Private keys protected with a passphrase are encrypted with AES-256-GCM + Argon2id. - Timing side channels: The Go runtime does not guarantee constant-time execution for all operations. We rely on the runtime's existing protections and the fact that attackers must decrypt at least one full AES-GCM block to verify a password guess.
- Quantum computing: AES-256 and SHA-256 are believed to maintain ~128-bit security against a quantum adversary using Grover's algorithm. X25519 is vulnerable to Shor's algorithm and should be replaced with a post-quantum KEM in a future version when standards mature.
- Compression side channels: When
--compressis enabled, the size of the compressed ciphertext may leak information about the plaintext content (the CRIME/BREACH class of attacks applies when an attacker controls part of the plaintext). This is not exploitable when the plaintext is static or when the attacker cannot influence it.
| Surface | Description |
|---|---|
| Encrypted files | All format versions (v0x02 through v0x0B) must be parseable without OOM. Header fields (salt length, chunk size, recipient counts, KDF parameters) and decompressed output are bounded. |
| CLI arguments | File paths are passed through to os.Open/os.Create. No shell injection risk. |
| Password input | Passwords are read via terminal (no echo), file descriptor, environment variable, stdin, or system keychain. They are cleared after use where possible. |
| Identity files | X25519 private keys stored on disk. Passphrase-protected with AES-256-GCM + Argon2id. |
| Version | Integrity | Checksum | Meta Encrypted | Streaming | Multi-Recipient |
|---|---|---|---|---|---|
| v0x02 | GCM tag | No | No meta | No | No |
| v0x03 | GCM tag | Optional | No meta | No | No |
| v0x04 | GCM tag | Optional | No | No | Yes (pwd-based) |
| v0x05 | GCM tag | Optional | No | Yes | No |
| v0x06 | GCM tag | Optional | Yes | Yes | No |
| v0x07 | GCM tag | Optional | Yes | Yes | Yes (pwd-based) |
| v0x08 | GCM tag | Optional | Yes | Yes | Yes (X25519) |
| v0x09 | GCM tag + header AAD | Optional | Yes | Yes | No |
| v0x0A | GCM tag + header AAD | Optional | Yes | Yes | Yes (pwd-based) |
| v0x0B | GCM tag + header AAD | Optional | Yes | Yes | Yes (X25519) |
All versions use AES-256-GCM authenticated encryption. The GCM tag (16 bytes
per chunk in streaming formats, 16 bytes total in v0x02/v0x03) provides
integrity protection. Versions v0x09-v0x0B additionally authenticate the
format version and flags as GCM associated data. Decompression is limited to
1 GiB by default; library callers can set a different limit with
DecryptOptions.
Password-based: Argon2id with configurable time, memory, and
parallelism. Defaults (time=3, memory=64MB, threads=4) are based on OWASP
recommendations. The salt is generated from crypto/rand and stored in
the file header. Each recipient in multi-recipient mode has an independent
salt and Argon2id derivation.
Asymmetric (X25519): An ephemeral key pair is generated per encryption. ECDH is performed with each recipient's public key to derive a shared secret, which is then passed through HKDF-SHA256 to produce a 32-byte AES-256 key. This file key encrypts the plaintext via AES-256-GCM, and is sealed with each recipient's derived shared secret for storage.
12-byte nonces are generated from crypto/rand for each GCM seal
operation (one per chunk in streaming modes, one for the checksum
trailer in v0x02/v0x03). For uniformly random 96-bit nonces, the
birthday-bound collision probability after 2³² uses of one key is
approximately 2⁻³³. Cipherlock generates a fresh file key per encrypted
file; implementations should still avoid approaching 2³² nonce uses
under any single key.
When --compress is enabled, the plaintext is compressed with zstd
before encryption and decompressed after decryption. The compression
is transparent to the caller: encryption sets the flagCompressed bit,
and decryption auto-detects it.
Compression is beneficial for text-heavy payloads (JSON, logs, source code) and can reduce ciphertext size by 5–10×. It adds negligible overhead for incompressible binary data (zstd detects incompressible blocks and stores them in their original form).
-
No deniable encryption: cipherlock files begin with a fixed 4-byte magic (
CV2\0). An adversary who inspects encrypted files can identify them as cipherlock output. Version 0x06+ encrypts the filename metadata, but the magic prefix is still present. -
No recipient hiding in multi-recipient mode: The number of recipients and the per-recipient KDF salt are in the cleartext header. An adversary can determine how many passwords/keys are required and can verify whether a given password was used (by deriving the key and attempting to unseal each entry).
-
No passphrase change detection: If a file was encrypted with a weak password, there is no mechanism to detect this other than attempting to decrypt it.
-
Argon2id memory-hardness on constrained systems: With 64MB memory and 3 passes, the KDF can take 2–10 seconds on modern hardware. Embedded systems or containers with memory limits may need to reduce these parameters. The
config set-profilecommand and--profileflag allow tuning. -
SSD shredding limitations: The
shredcommand overwrites files with random data on spinning disks. On SSDs with wear-leveling, the Flash Translation Layer may preserve old copies of the data. Full-disk encryption is recommended for SSD environments. -
Compression oracles: As noted in the threat model, the compressed ciphertext size may leak information when an attacker controls part of the plaintext (CRIME/BREACH class attacks). Do not use
--compresswhen encrypting data that includes attacker-controlled content in the same compression context. -
Time-gated files are not cryptographically enforced: An expired file's expiration time is authenticated metadata, and the high-level decrypt entry points (
Decrypt,DecryptFile,DecryptWithMeta) refuse to decrypt expired files withErrExpired. However, anyone who holds the password can still recover the contents using the low-level streaming functions (DecryptStreamV2,DecryptStreamMultiFromReader), which intentionally bypass the check for archival recovery. Treat expiration as a policy reminder, not a cryptographic guarantee.
| Config field | Default | Recommendation |
|---|---|---|
SaltLen |
16 | Do not change. |
Time |
3 | Increase on modern hardware if KDF latency is acceptable. |
Memory (KiB) |
65536 (64 MiB) | Increase to 128MiB+ on desktops/servers. |
Threads |
4 | Match CPU core count. |
KeyLen |
32 | Do not change (AES-256 requires 32 bytes). |
Checksum |
false | Enable for integrity verification. |
Compression |
false | Enable for text-heavy payloads. |
The default KDF parameters follow OWASP recommendations (2023). Users who
need stronger protection against GPU/ASIC attackers should increase
Memory to 256MiB+.
cipherlock takes the following measures to limit key material exposure:
- Derived keys (Argon2id output) are cleared with Go's
clear()builtin after use (defer clear(key)). - File keys in multi-recipient and asymmetric modes are cleared after all chunks are processed.
- ECDH shared secrets in X25519 encryption are cleared immediately after the AES-GCM key is derived.
- Passwords retrieved from environment variables, file descriptors, or stdin are cleared after use.
- The password strength estimator copies the password and clears the copy after evaluation.
Caveat: Go is a garbage-collected language. While clear() overwrites
the backing array of a slice, the GC may have moved the data before
clearing. On systems with MADV_FREE (Linux), memory pages containing
key material may persist in the page cache. These are limitations of the
runtime environment, not of cipherlock's implementation.
-
Use
--checksumto verify plaintext integrity on decryption. This detects corruption of the ciphertext that GCM alone may miss (truncation + forced end-of-stream). -
Use
--in-placewith--backupfor atomic file replacement. The original file is renamed to.cipherlock-bakbefore writing, and restored if encryption fails. -
Use v0x06+ formats (automatic with
FileMetaor--compress) for metadata confidentiality. The plaintext filename is not visible in the encrypted output. -
Verify recipient public keys out-of-band when using asymmetric encryption. cipherlock does not implement a Public Key Infrastructure; users are responsible for key distribution and verification.
-
Set appropriate file permissions on identity files and key files: 0600 for private keys, 0644 for public keys.