System Design Fundamentals

Encryption: Rest & Transit

A

Encryption: Rest & Transit

The Last Line of Defense

Imagine this: a courier is transporting backup tapes from your data center to an offsite vault. One tape gets lost. Without encryption at rest, every user record, password hash, and payment detail is readable in plain text to whoever finds that tape. In a separate scenario, your API is being called from a coffee shop WiFi network. Without encryption in transit, anyone on that same network can read every API request and response—including the authentication token that gives them access to the entire system.

Encryption doesn’t prevent attackers from stealing your data. It prevents them from reading it when they do. When your perimeter security fails—and it will, eventually—encryption becomes the final guardian standing between your users’ data and exposure.

Understanding the Two Families of Encryption

When we talk about encryption, we’re actually describing two complementary approaches, each solving different problems.

Symmetric encryption uses a single key for both encryption and decryption. AES-256 is the gold standard here—fast enough to encrypt terabytes of data, strong enough that no known attack can break it in any practical timeframe. You lock and unlock the safe with the same key. The downside? You need to securely share that key with anyone who needs to decrypt the data, which creates the classic “secure key distribution problem.”

Asymmetric encryption uses a pair of mathematically linked keys: a public key (encrypt) and a private key (decrypt). RSA and ECDSA are the workhorses here. The magic is that you can broadcast your public key to the world—anyone can encrypt a message to you, but only you can decrypt it with your private key. This solves the key distribution problem elegantly, but it’s computationally expensive. Encrypting large amounts of data with RSA is impractically slow.

In practice, we use both. TLS handshakes use asymmetric encryption to establish trust and exchange keys, then switch to symmetric encryption for the actual data transfer. It’s the best of both worlds.

The Distinction: At Rest vs. In Transit

Encryption at rest protects data when it’s sitting still—in databases, on disk, in S3 buckets, on backup tapes. Think of it as a safe in a vault. A database might encrypt every row using AES-256. Your filesystem might use LUKS (Linux) or BitLocker (Windows) to encrypt the entire disk. AWS S3 can encrypt objects automatically when you enable server-side encryption (SSE).

Encryption in transit protects data while traveling across networks. TLS/HTTPS is the primary mechanism here. Every modern API uses it. The client and server negotiate a shared symmetric key through an asymmetric handshake, then encrypt all subsequent traffic. A properly configured TLS connection is secure even on untrusted networks—your coffee shop WiFi, public cellular, even a network where attackers are present.

End-to-end encryption (E2EE) takes it further: only the sender and receiver can decrypt the message, never the server in the middle. Signal, WhatsApp, and modern email systems employ this. It’s the strongest guarantee, but it comes with trade-offs: the server can’t search encrypted messages, can’t provide certain features, and recovery is harder if you lose your keys.

The Hardest Problem: Key Management

Here’s a truth that catches many teams off guard: the strength of your encryption is irrelevant if someone can steal your keys. You can have AES-256 protecting your database, but if the decryption key is hardcoded in your application code or stored in an unprotected configuration file, an attacker who compromises your codebase gets everything.

Key management is where most real-world security failures happen.

Key Management Services (KMS) abstract this problem away. AWS KMS, Google Cloud KMS, and HashiCorp Vault are purpose-built for this:

  • Keys are stored in Hardware Security Modules (HSMs)—specialized hardware that makes key extraction virtually impossible
  • Keys are never transmitted to your application; instead, you send data to KMS for encryption/decryption
  • All key operations are audited and logged
  • You can set access policies and rotate keys without downtime

Envelope encryption is the pattern that makes this practical. Instead of encrypting your entire database with the master key (which would be slow and require sending huge amounts of data to KMS), you do this:

  1. Create a data key (often AES-256) and use it to encrypt your actual data locally
  2. Encrypt that data key with your master key (kept in KMS)
  3. Store the encrypted data key alongside the encrypted data
  4. When you need to decrypt, send the encrypted data key to KMS to get the plaintext data key back, then decrypt locally

This way, your application does the heavy encryption lifting with fast symmetric keys, while the master key stays protected and only handles small amounts of key material.

How TLS Works: The Handshake

When your browser visits https://example.com, a sophisticated dance happens in milliseconds. Understanding this dance demystifies how modern security actually works.

Client                                          Server
  |                                               |
  |-------- Client Hello (protocols, ciphers) --> |
  |                                               |
  | <---- Server Hello + Certificate + Key -----  |
  |       Signature (proves identity)             |
  |                                               |
  | ----- Change Cipher Spec + Finished -------> |
  |       (encrypted from here forward)           |
  |                                               |
  | <---- Change Cipher Spec + Finished ----------|
  |                                               |
  | <====== Encrypted TLS connection ============>|
  1. Client Hello: Browser sends supported TLS versions and cipher suites (combinations of algorithms)
  2. Server Hello: Server picks the strongest common option, sends back its certificate (public key + identity proof from a Certificate Authority), and ephemeral key material
  3. Certificate Verification: Browser verifies the certificate against trusted CAs (these CA certificates are pre-installed in your OS)
  4. Key Agreement: Using the ephemeral keys and certificate, both sides derive the same symmetric session key
  5. Finished: Both sides confirm the handshake succeeded by sending encrypted messages only the other can decrypt

The session key is ephemeral—it’s deleted after the connection closes. Even if someone later steals your long-term private key, they can’t decrypt old sessions. This is called “perfect forward secrecy.”

TLS 1.3 streamlined this to just 1 round trip (TLS 1.2 needed 2), making it faster without sacrificing security.

Practical Encryption Strategies

Different data storage systems offer different encryption options:

Database Encryption:

  • Transparent Data Encryption (TDE): SQL Server, Oracle, and some cloud databases offer this. The database engine automatically encrypts data on disk and decrypts on read. Transparent to the application.
  • Column-level encryption: Some databases let you encrypt specific columns. Queries that filter on encrypted columns need careful design.
  • Application-level encryption: Your application encrypts data before sending to the database. The database stores ciphertext. More control, but more complexity.

Storage Encryption:

  • S3 Server-Side Encryption (SSE): Amazon encrypts your objects at rest. SSE-S3 uses Amazon-managed keys. SSE-KMS lets you manage keys with AWS KMS. SSE-C lets you bring your own key (you manage rotation).
  • EBS encryption: Encrypts EC2 block storage. Automatic, with minimal performance overhead.
  • Disk encryption: LUKS for Linux, BitLocker for Windows. Encrypts entire filesystems.

The Password Exception: Hashing vs. Encryption

Here’s a critical distinction that trips up many engineers: passwords must be hashed, not encrypted.

Encryption is reversible—decrypt the ciphertext and you get the plaintext. For passwords, you don’t want the plaintext back. You hash it with a one-way function like bcrypt or Argon2, then store only the hash. When the user logs in, you hash their input and compare hashes.

// Secure password hashing with bcrypt
const bcrypt = require('bcrypt');

// Registration
const plainPassword = 'user_supplied_password';
const hashedPassword = await bcrypt.hash(plainPassword, 12); // 12 = cost factor
// Store hashedPassword in database

// Login
const inputPassword = 'user_supplied_password';
const matches = await bcrypt.compare(inputPassword, storedHash);
// Timing attacks are prevented by design

Even if an attacker steals your entire user database, they can’t recover passwords (only brute-force guess them). Bcrypt and Argon2 include work factors that make brute-forcing slow—intentionally.

Security Headers: Invisible Protection

While TLS encrypts in-flight data, a layer of vulnerability still exists in the application itself (especially for XSS, as we’ll discuss next chapter). Security headers add an extra layer by instructing browsers how to behave:

HeaderPurpose
Strict-Transport-SecurityForce HTTPS only, prevent downgrade attacks
Content-Security-PolicyRestrict where scripts can load from, prevent XSS
X-Content-Type-Options: nosniffPrevent MIME-type sniffing
X-Frame-Options: DENYPrevent clickjacking (embedding your site in an iframe)
X-XSS-ProtectionLegacy, but still set for older browsers
# Sample nginx configuration with security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' cdn.example.com" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;

Trade-Offs in the Real World

Performance: Encryption has CPU overhead. AES is fast (modern CPUs have hardware acceleration), but it’s not free. A typical system sees 5-15% performance impact. For most applications, this is acceptable. For high-frequency trading or real-time systems, you might encrypt selectively—maybe only the PII fields, not every column.

Complexity: Every additional layer of encryption adds operational complexity. Key rotation, certificate management, secure key distribution—these require discipline and tooling. Many teams choose “encrypt everything with AWS KMS” because the simplicity is worth more than the performance cost.

Compliance: Regulations like PCI-DSS (payment cards), HIPAA (healthcare), and GDPR (EU data) mandate encryption for sensitive data. In these cases, the decision is made for you—encryption isn’t optional, it’s legally required.

Pro tip: Don’t encrypt everything at the cost of operational nightmare. Target your encryption: identify your sensitive data (PII, payment info, secrets), and protect that ruthlessly. Log files, internal metrics, and non-sensitive data can live unencrypted if bandwidth is constrained.

Key Takeaways

  1. Encryption is the last line of defense, not the first. Secure your perimeter, but assume it will fail and encrypt accordingly.
  2. Symmetric encryption (AES-256) handles bulk data. Asymmetric encryption (RSA/ECDSA) handles key exchange and authentication. TLS combines both.
  3. Encryption at rest (databases, disk, backups) and encryption in transit (TLS/HTTPS) solve different problems and both are necessary.
  4. Key management is harder than encryption. Use KMS services, not hardcoded keys. Envelope encryption lets you encrypt large amounts of data efficiently.
  5. Passwords are an exception—hash them with bcrypt or Argon2, never encrypt them. One-way functions are required.
  6. Security headers are cheap insurance against application-layer attacks. Deploy them universally.

Practice Scenarios

Scenario 1: The Forgotten Backup Tape Your company ships nightly database backups to a secure offsite facility via courier. The tape gets lost. Your backup contains customer records, password hashes, and payment history. What do you have in place to ensure the tape is useless to whoever finds it?

Scenario 2: Encryption Budget Your team has limited CPU budget. Adding encryption will consume 10% of throughput. Your encryption requirements mandate protecting customer PII. Which fields would you encrypt, and which would you leave unencrypted?


Next: Common vulnerabilities like SQL Injection, XSS, and CSRF require a different defense strategy—input validation and output encoding. Encryption protects data; the next section protects your applications.