Skip to content

Latest commit

 

History

History
251 lines (200 loc) · 12.6 KB

File metadata and controls

251 lines (200 loc) · 12.6 KB

Security Policy

Supported Versions

Version Supported
1.x ✅ Active development
< 1.0 ❌ Unreleased

Reporting a Vulnerability

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.


Cryptographic Design

Algorithms

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.

Threat Model

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: Any modification of the ciphertext (bit-flip, truncation, extension) is detected on decryption. AES-GCM provides authenticated encryption; a tampered ciphertext fails with ErrAuthFailed.
  • 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 FileMeta option, the original filename and size are encrypted and not visible without the password.
  • Forward secrecy for asymmetric encryption (v0x08): 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 dial are 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 --compress is 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.

Attack Surface

Surface Description
Encrypted files All format versions (v0x02 through v0x08) must be parseable without OOM. Header fields (salt length, chunk size, number of recipients, KDF parameters) are bounded by hard limits (maxSaltLen, maxChunkSize, maxRecipients, etc.).
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.

Format Version Security Properties

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)

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.

Key Derivation

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.

Nonce Generation

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). The probability of nonce collision is negligible (< 2⁻⁸⁸ after 2³² encryptions per key).

Compression

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).


Known Limitations

  1. 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.

  2. 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).

  3. 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.

  4. 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-profile command and --profile flag allow tuning.

  5. SSD shredding limitations: The shred command 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.

  6. 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 --compress when encrypting data that includes attacker-controlled content in the same compression context.

  7. 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 with ErrExpired. 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.

Security-Relevant Configuration

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+.

Memory Safety

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.

Secure Use Recommendations

  1. Use --checksum to verify plaintext integrity on decryption. This detects corruption of the ciphertext that GCM alone may miss (truncation + forced end-of-stream).

  2. Use --in-place with --backup for atomic file replacement. The original file is renamed to .cipherlock-bak before writing, and restored if encryption fails.

  3. Use v0x06+ formats (automatic with FileMeta or --compress) for metadata confidentiality. The plaintext filename is not visible in the encrypted output.

  4. 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.

  5. Set appropriate file permissions on identity files and key files: 0600 for private keys, 0644 for public keys.