DEV Community

Nayananshu Garai
Nayananshu Garai

Posted on

Cifrasync

GitHub “Finish-Up-A-Thon” Challenge Submission

This is a submission for the GitHub Finish-Up-A-Thon Challenge

What I Built

CifraSync — a zero-dependency encrypted incremental backup tool written in C from scratch (200 KB binary, 31 source files, ~5000 lines). No Python, no Go, no npm. Just a single binary with chunk-based deduplication, RLE compression, HMAC stream cipher v2 (key separation, CSPRNG, 600K PBKDF2), incremental snapshots, remote sync, file-based mutual exclusion locks, and a colored interactive TUI.

The project started as a partially-built C backup scaffold with stubs and unfinished wiring. I completed all 7 commands (init, backup, restore, list, verify, prune, sync), added a server mode, wired compression and encryption end-to-end with key separation and cryptographically secure randomness, built an interactive menu, added file-based locking, and wrote a comprehensive README and docs. All 14 tests pass with zero compiler warnings.

Key technical features

  • Chunk-level deduplication — identical data across files/snapshots stored once
  • RLE compression — per-chunk, auto-decompressed on restore/verify
  • HMAC stream cipher v2 — Blob version 2 with key separation (enc_key ≠ mac_key), cryptographically random salt/nonce (CryptGenRandom / /dev/urandom), 600K PBKDF2 iterations, constant-time tag verification
  • Key cache — In-memory PBKDF2 cache eliminating redundant per-chunk derivation
  • Masked passphrase input — No echo on terminal (SetConsoleMode / getpass)
  • File-based mutual exclusion — flock/LockFileEx locks (EXCLUSIVE for writes, SHARED for reads)
  • Atomic manifest writes.tmp + rename() crash safety
  • Journal replay — interrupted backups auto-resume
  • Cross-platform — same codebase compiles on Windows (MinGW), Linux, macOS
  • Interactive TUI — colored numbered menu, no flags needed for daily use
  • wake up cifra — one-command launcher after one-time setup.ps1

Demo

Build & Test

git clone https://github.com/N-Garai/CifraSync.git
cd CifraSync
mingw32-make release    # builds bin/cifrasync.exe (200 KB)
mingw32-make test       # 14/14 tests pass
Enter fullscreen mode Exit fullscreen mode

Quick Demo (interactive mode)

# Create test data
mkdir C:\demo\source C:\demo\repo C:\demo\restored
"Hello world" > C:\demo\source\file1.txt
"Another file" > C:\demo\source\file2.txt
mkdir C:\demo\source\sub
"Deep file" > C:\demo\source\sub\deep.txt

# Launch interactive menu
cifrasync

Enter fullscreen mode Exit fullscreen mode

CLI Usage

cifrasync init --repo PATH
cifrasync backup --source PATH --repo PATH [--compress] [--encrypt] [--label TEXT]
cifrasync list --repo PATH
cifrasync restore --repo PATH --snapshot ID --out PATH [--source-file PATH]
cifrasync verify --repo PATH
cifrasync prune --repo PATH [--keep-last N] [--older-than DAYS]
cifrasync sync --repo PATH --remote HOST:PORT   # pushes manifests + chunks to remote
cifrasync serve --bind HOST:PORT [--repo PATH]   # receives & stores sync data
Enter fullscreen mode Exit fullscreen mode

The Comeback Story

Before : The project had the CLI scaffold, core storage and chunking layers, basic tests, and tools. But 3 of 7 commands were stubs (verify, prune, sync returned "unsupported"). Compression and encryption were logged but not applied. Snapshot listing showed bare filenames only. No journal replay, no atomic writes, no config auto-load, no single-file restore, no interactive mode, no launcher. The project needed documentation, polish, and a clear demo path.

After : Every command is fully implemented. Compression and encryption are wired end-to-end (RLE before store, HMAC seal v2 with key separation, auto-decompress on restore/verify). Encryption uses cryptographically random salt+nonce, 600K PBKDF2 iterations, masked passphrase input, and key separation (enc_key ≠ mac_key). File-based mutual exclusion locks prevent concurrent corrupting writes. Journal replay resumes interrupted backups. Atomic .tmp + rename() prevents partial snapshots. Config auto-loads on startup. The snapshot list shows rich details (ID, timestamp, file count, size, label). There's a colored interactive TUI, a serve command for remote sync, and a wake up cifra one-command launcher. The README is comprehensive with a full interactive mode reference table. All 14 tests pass with zero warnings.

Sync Protocol (full data transfer)

The sync now implements bidirectional chunk transfer via a custom TCP frame protocol:

  1. HELLO — client announces itself, server responds with repo path
  2. MANIFEST — client sends full snapshot metadata + serialized manifest file (all file/chunk references)
  3. Missing chunk negotiation — server compares manifest chunk hashes against its local chunk store, returns list of only the missing hashes
  4. CHUNK — client reads each missing chunk from local store and sends it as binary frame (64-byte SHA-256 hash + raw data)
  5. BYE — clean disconnect

Only chunks the server doesn't already have are transferred, making re-syncs incremental. The server writes .snapshot metadata files, .manifest files, and stores chunks using the existing repository format. The client reads manifests from its local repo and sends chunk data on demand.

Key changes made:

  • Implemented verify (chunk re-hash), prune (snapshot deletion + orphan GC), sync (full manifest + chunk data transfer), serve (server mode with --repo for storage)
  • Wired RLE compression and HMAC encryption into the backup pipeline
  • Blob v2 with key separation — enc_key/mac_key derived via HMAC-SHA256 from master key; authentication tag computed with mac_key, stream cipher uses enc_key
  • Cryptographically secure randomness — replaced time()+clock() salt/nonce with CryptGenRandom (Windows) / /dev/urandom (POSIX)
  • PBKDF2 iterations: 10K → 600K — OWASP 2023 recommendation for SHA-256
  • Wired in key cache — per-session static cache avoids recomputing PBKDF2 per chunk
  • Masked passphrase input — SetConsoleMode disables echo on Windows; getpass on POSIX
  • Passphrase zeroization — passphrase buffer cleared in backup cleanup path
  • Wrong-passphrase now fails loudly — restore/verify previously silently produced garbage; now they return errors with log messages
  • File-based mutual exclusion — EXCLUSIVE lock for backup/prune/server-sync, SHARED lock for restore/verify/client-sync; cross-platform via flock / LockFileEx
  • Fixed Windows shared lock — LockFileEx now called for shared mode too (was a no-op)
  • Added journal replay on backup start
  • Added atomic manifest writes
  • Added rich snapshot listing with formatted columns
  • Added single-file restore (--source-file)
  • Added config auto-load
  • Built interactive TUI with colored ASCII menu
  • Created wake up cifra launcher (wake_up_cifra.cmd + setup.ps1)
  • Fixed platform detection (dynamic #ifdef instead of hardcoded)
  • Fixed verify with compressed chunks (decompress before hash check)
  • Added POSIX fallbacks for cross-platform support
  • Deduplicated 5 copies of path_join into shared cs_path_join
  • Fixed \r carriage return bug in interactive input (was corrupting paths on Windows)
  • Fixed NULL callback crash in journal.c
  • Wrote comprehensive README, after.md, and submission docs

My Experience with GitHub Copilot

I used GitHub Copilot throughout the completion process. Copilot was most helpful for:

  • Boilerplate code generation — writing repetitive CLI option parsing, path joining, and error handling patterns
  • Test writing — generating test cases and edge cases for unit tests
  • Documentation — drafting README sections, the submission post, and code comments
  • Bug finding — suggesting fixes for the \r carriage return issue and the verify decompression logic
  • Refactoring — suggesting how to deduplicate the path_join functions across 5 files into a single shared implementation

Every suggestion was reviewed, adjusted for the project's coding style and safety constraints, and validated against the test suite before being committed.


Demo Video

CifraSync Demo

Watch the full walkthrough: https://youtu.be/IqonIOfXhFs


Repository: N-Garai/CifraSync

Top comments (0)