Encrypted File Copy

C · OpenSSL · AES-256 · HMAC-SHA256

A small scp-style program in C that copies a file from one machine to another over an encrypted channel. The point was to build the encrypted tunnel by hand with OpenSSL's libcrypto, instead of leaning on SSH, and to see what an authenticated-encryption pipeline actually involves end to end.

The pipeline

On each side the work is split across two threads that hand data to one another through a shared buffer. On the client, one thread reads the file and runs the crypto, the other handles the network. On the server the same split runs in reverse:

Client crypto read, encrypt HMAC shared buffer network base64, send produce consume netcat Server network receive, parse shared buffer crypto verify, decrypt write file produce consume Each side runs a producer and a consumer over one shared buffer

Keys, IVs, and the HMAC

The two ends share a pre-shared key. The IV is fresh per transfer and read from /dev/urandom, and it travels alongside the ciphertext because the receiver needs it to decrypt. The HMAC is keyed from the same shared secret and covers the encrypted bytes, so the server can detect any tampering on the wire before it ever touches the plaintext. The check is the important part: if a single bit of the ciphertext is flipped in flight, HMAC validation fails and the file is rejected rather than written.

Sender plaintext file chunk AES-256-CBC ciphertext with random IV HMAC-SHA256 IV · ciphertext · HMAC base64, over netcat Encrypt-then-MAC: the HMAC is computed over the ciphertext the same bytes arrive at the server Receiver received IV, cipher, HMAC verify HMAC HMAC valid? keyed check then decrypt only if valid AES-256 decrypt write file to disk invalid: rejected, nothing written Verify-then-decrypt: the MAC is checked before the ciphertext is trusted

netcat as the transport

The transport itself is deliberately dumb: the actual bytes move over netcat. The client pipes its base64 payload into an ncat process, and the server reads it back off ncat's output and parses it. That is why everything is base64-encoded first, it keeps the encrypted blob safe to pass through a plain text pipe. Pushing the raw socket work onto netcat let me keep the program focused on the encryption and authentication rather than connection management.

What I took from it

The useful lesson was the discipline of verify-then-decrypt: the HMAC has to be checked before the ciphertext is trusted, and the file must not be written if the check fails. It is a hobby reimplementation of ideas that scp and TLS give you for free, but building the IV handling, the keyed MAC, and the failure path yourself makes it obvious why those pieces exist.