Encrypting Files and Connections
eCryptfs, mutual TLS with OpenSSL and NFS wrapped in stunnel.
Contents
Encryption usually reaches us as a setting: a checkbox for disk encryption, a padlock in the browser, a tls: true in a config file. I wanted to see what each of those actually does, so I set up three small experiments on a couple of VMs, each protecting data in a different place:
- At rest, sitting in a file on disk.
- In transit, between two programs that have to prove who they are to each other.
- Underneath a protocol that has no encryption of its own, in this case NFS.
None of the tools here are exotic. They’re the standard ones: eCryptfs, the OpenSSL command line and stunnel. The point was to use them by hand and then check the raw bytes to confirm each one really worked.
Background: three questions#
Each experiment answers a different question.
| Question | Threat it handles | |
|---|---|---|
| At rest | Can someone with the disk read my files? | A stolen laptop, a disk pulled from a server, a leaked backup |
| In transit | Can someone on the network read or change my traffic? And is the other end who it says it is? | Anyone between the two machines |
| Wrapping a protocol | Can I protect a protocol that sends everything in plaintext, without changing it? | The same as in transit, for software you can’t modify |
Two ideas come up throughout:
- Symmetric encryption uses one shared key to both encrypt and decrypt. It’s fast and it’s what actually protects the data in all three experiments.
- Public-key cryptography gives each party a key pair: a private key they keep and a public key anyone can have. It solves the question of who you’re talking to and it’s how two sides agree on a symmetric key without ever sending it in the clear.
Experiment 1: encrypting a directory with eCryptfs#
How it works#
eCryptfs is a stacked filesystem. You mount it on top of an ordinary directory and everything written through the mount is encrypted before it reaches the directory underneath. Read it back through the mount and it’s decrypted on the fly. Programs using the mounted path never notice.
Both the contents of each file and its name are encrypted, using a key derived from a passphrase you supply when mounting.
Doing it#
Mount a directory on top of itself. eCryptfs asks a few questions, including the passphrase and whether to encrypt filenames:
mkdir -p ~/vault
sudo mount -t ecryptfs ~/vault ~/vault
# choose a passphrase, aes, and "y" to filename encryption when prompted
echo "the launch code is 0000" > ~/vault/notes.txt
cat ~/vault/notes.txt # readable, because we're reading through the mount
sudo umount ~/vault
Checking it#
The test that makes it real is looking at the file with the mount gone:
ls ~/vault # the filename is now a long encrypted string
cat ~/vault/ECRYPTFS_FNEK_ENCRYPTED.* # the contents are ciphertext
With eCryptfs unmounted, both the name and the contents are unreadable. Mount it again with the same passphrase and cat prints the plaintext straight back. The plaintext only exists while the mount is live. On disk, there is nothing but encrypted bytes.
eCryptfs is effectively unmaintained now and Ubuntu has dropped it from its installer. For new setups, full-disk encryption with LUKS, or per-directory encryption with
fscryptorgocryptfs, are the better choices. The idea is the same.
Experiment 2: a private CA and mutual TLS#
Background: certificates and CAs#
A TLS certificate binds a public key to a name and it’s signed by a certificate authority (CA). If you trust the CA, you can trust any certificate it has signed. Browsers ship with a list of public CAs they trust, but nothing stops you from creating your own CA and trusting only that.
Normal HTTPS is one-way: the server proves its identity and the client stays anonymous. Mutual TLS (mTLS) makes the client present a certificate as well, so each side has to prove who it is before any data flows. It’s widely used between services and for locking down admin interfaces.
Building the CA and certificates#
A CA is just a key pair and a certificate that signs itself:
# 1. The CA: a private key and a self-signed root certificate
openssl req -x509 -newkey rsa:4096 -nodes -days 365 \
-keyout ca.key -out ca.crt -subj "/CN=Lab Root CA"
# 2. A server key and a certificate signing request (CSR)
openssl req -newkey rsa:2048 -nodes \
-keyout server.key -out server.csr -subj "/CN=server.lab"
# 3. The CA signs the server's request
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 365 -out server.crt
# 4. Same again for the client
openssl req -newkey rsa:2048 -nodes \
-keyout client.key -out client.csr -subj "/CN=client.lab"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 365 -out client.crt
Once ca.key exists it can sign anything, which is why real CAs keep that key offline and heavily guarded.
Connecting with mutual authentication#
openssl s_server and openssl s_client are enough to stand up a TLS server and connect to it. -Verify 1 (capital V) tells the server to require a client certificate, not just ask for one:
# Server: present server.crt, require a client certificate signed by our CA
openssl s_server -accept 4433 -cert server.crt -key server.key \
-CAfile ca.crt -Verify 1
# Client: present client.crt, verify the server against our CA
openssl s_client -connect server.lab:4433 -cert client.crt -key client.key \
-CAfile ca.crt
The debug output shows each side’s certificate chain being checked against the CA, in both directions. Leave out -cert on the client and the server refuses the handshake. Neither end trusts the other until the certificate checks out.
Adding -www to s_server makes it answer HTTP requests, so a browser can connect as well. Import the client certificate into the browser (as a PKCS#12 bundle made with openssl pkcs12 -export) and the browser prompts you to pick a certificate before the page loads. It’s the same client-certificate login that some real services use to gate access.
Experiment 3: wrapping NFS in TLS with stunnel#
The problem#
NFS lets one machine mount a directory from another over the network. Out of the box it sends file contents in the clear. That’s acceptable on a trusted LAN and alarming anywhere else.
Instead of switching to a different protocol, you can tunnel it. stunnel is a small program that accepts plain TCP on one side and speaks TLS on the other. Put one at each end and the program in the middle never knows its traffic is encrypted.
Setting it up#
The trick is where each side points:
- On the server, stunnel accepts TLS on a new port and forwards the decrypted traffic to the local NFS server. Because the NFS server now sees every connection coming from
127.0.0.1, it exports to localhost, not to the client’s address. - On the client, stunnel listens on a local port and the client mounts NFS from its own localhost. stunnel carries that connection across the network in TLS.
The configuration is short. On the server:
[nfs]
accept = 20490
connect = 127.0.0.1:2049
cert = /etc/stunnel/server.crt
key = /etc/stunnel/server.key
On the client, with the server’s certificate copied over so it can verify it:
[nfs]
client = yes
accept = 127.0.0.1:2049
connect = <server-ip>:20490
CAfile = /etc/stunnel/server.crt
verify = 2
Then mount through the local end of the tunnel:
sudo mount -t nfs4 127.0.0.1:/export /mnt/remote
Checking it#
A packet capture on the link between the two machines tells the whole story. Before the tunnel, the same mount showed readable NFS operations in the capture, including file contents. With the tunnel up, the capture shows nothing but a TLS handshake followed by encrypted records.
Newer Linux kernels can do this natively. RPC-with-TLS lets NFS use TLS directly through the
xprtsec=tlsmount option and thetlshddaemon from ktls-utils. The stunnel approach still works everywhere and it shows the idea more clearly.
What the three have in common#
| Tool | What protects the data | How trust is established | |
|---|---|---|---|
| At rest | eCryptfs | A symmetric key derived from a passphrase | Knowing the passphrase |
| In transit | OpenSSL, mTLS | A session key agreed during the TLS handshake | Certificates signed by a CA both sides trust |
| Wrapped protocol | stunnel | The same TLS session keys | The server’s certificate, pinned on the client |
All three answer the same two questions: who can read this data and who am I trusting? Doing each one by hand, then capturing or dumping the raw bytes to confirm it worked, is what turned them from settings I switch on into mechanisms I understand.
Further reading#
- OpenSSL
req,x509ands_server - stunnel manual
- RFC 9289: Towards Remote Procedure Call Encryption by Default, the RPC-with-TLS spec