Encryption in System Design: Symmetric, Asymmetric, TLS & Key Management (Visualized)
Encryption transforms readable plaintext into unreadable ciphertext so that only holders of the correct key can recover the original data. This guide covers symmetric vs asymmetric encryption, TLS in transit, at-rest protection, envelope key management, and how encryption differs from hashing β with live animations.
Encryption is the process of transforming plaintext data into an unintelligible ciphertext using a cryptographic algorithm and a secret key, such that only a party holding the correct key can reverse the transformation and read the original data. It is the foundational mechanism that allows confidential communication and storage across untrusted networks and infrastructure.
Modern systems rely on encryption at multiple layers simultaneously: data traveling over the network is protected by TLS, data written to disk is protected by at-rest encryption, and individual fields inside databases may be encrypted at the application layer. Understanding how each layer works β and where the keys live β is a core system design skill.
Plaintext, Ciphertext, and Keys
Every encryption scheme has three components: the plaintext (the original readable message), the key (a sequence of random bits that controls the transformation), and the ciphertext (the scrambled output). The ciphertext is safe to transmit or store in public because it reveals nothing about the plaintext without the key. Decryption is the inverse: given the ciphertext and the correct key, the algorithm reconstructs the plaintext exactly.
Modern ciphers follow Kerckhoffs's principle: the security of the system must rest entirely on the secrecy of the key, not the secrecy of the algorithm. The algorithm is public and scrutinized by the global cryptography community; only the key is secret. This means AES-256 is considered secure not because its design is hidden, but because breaking it without the key requires more computational work than exists in the observable universe.
Symmetric Encryption: One Key for Everything
In symmetric encryption, the exact same key is used to both encrypt and decrypt. The sender and receiver must share this secret before they communicate. The dominant algorithm today is AES (Advanced Encryption Standard), operating with 128- or 256-bit keys. AES-256 is a block cipher: it splits data into 128-bit blocks and runs each block through 14 rounds of byte substitution, row shifting, column mixing, and key addition. The result is ciphertext that is computationally indistinguishable from random noise.
Symmetric encryption is extremely fast β modern CPUs include AES-NI hardware acceleration that can encrypt gigabytes per second. This makes it the workhorse for encrypting bulk data: database files, disk volumes (dm-crypt / BitLocker), and the actual payload in a TLS connection. The core challenge with symmetric encryption is the key distribution problem: how do two parties that have never met agree on a shared secret key over an untrusted channel? This is where asymmetric encryption enters.
Asymmetric Encryption: Public and Private Keys
Asymmetric encryption uses a mathematically linked key pair: a public key that anyone may know, and a private key that the owner keeps secret. Data encrypted with the public key can only be decrypted with the corresponding private key. This solves key distribution elegantly: publish your public key, and anyone can send you an encrypted message that only you can read.
The two dominant families are RSA (RivestβShamirβAdleman), based on the difficulty of factoring large integers, and ECC (Elliptic Curve Cryptography), based on elliptic curve discrete logarithms. ECC achieves equivalent security to RSA with much smaller key sizes: a 256-bit ECC key is roughly as strong as a 3072-bit RSA key. Smaller keys mean faster operations and less bandwidth β so ECC has become the default for TLS certificates and SSH keys. The trade-off with asymmetric encryption: it is orders of magnitude slower than AES, making it unsuitable for bulk data. In practice, asymmetric crypto is used only to exchange or derive a symmetric session key, which then does the heavy lifting.
| Symmetric (AES) | Asymmetric (RSA / ECC) | |
|---|---|---|
| Keys | One shared secret key | Public key + private key pair |
| Speed | Very fast (AES-NI GB/s) | 100β1000Γ slower than AES |
| Key size (128-bit security) | 128 bits | 3072-bit RSA or 256-bit ECC |
| Key distribution | Must be pre-shared securely | Public key can be sent openly |
| Primary use | Bulk data encryption | Key exchange, digital signatures |
| Examples | AES-128, AES-256, ChaCha20 | RSA-2048, ECDSA-P256, X25519 |
Encryption in Transit: TLS
TLS (Transport Layer Security) is the protocol that secures HTTPS, gRPC, database connections, and virtually all modern network communication. It solves the key distribution problem by combining asymmetric and symmetric encryption in a handshake: asymmetric crypto negotiates a fresh symmetric session key, and then all payload data is encrypted with that fast symmetric key.
In TLS 1.3 (the current version), the handshake uses ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) key exchange. Both the client and server generate a temporary ECC keypair, exchange public halves, and each independently computes the same shared secret β without that secret ever crossing the wire. The server's certificate (signed by a trusted CA) proves the server's identity. The derived shared secret feeds a key derivation function to produce symmetric session keys for both directions. The whole handshake takes one network round-trip in TLS 1.3 (down from two in TLS 1.2).
The key property of ECDHE is forward secrecy: because ephemeral keypairs are discarded after each session, recording today's encrypted traffic and cracking the server's long-term certificate key in the future cannot decrypt that past traffic β each session's key is gone forever.
Encryption at Rest
Encryption at rest protects data stored on disk, in object storage (S3, GCS), or in backups, against unauthorized physical access or a stolen disk. It operates at several granularities: full-disk encryption (dm-crypt / LUKS on Linux, BitLocker on Windows) encrypts every block on the device transparently to the OS; database encryption (PostgreSQL pgcrypto, MySQL InnoDB encryption) encrypts table spaces or individual columns; and application-layer encryption encrypts specific fields before they ever reach the database, so even a database administrator cannot read sensitive values like credit card numbers or health data.
Cloud providers automate much of this: AWS S3 server-side encryption (SSE-S3) uses AES-256 and manages keys automatically, while SSE-KMS routes key operations through AWS KMS so you control which principals can access data and have a full audit log of every key usage.
Key Management and Envelope Encryption
Storing the encryption key next to the ciphertext it protects defeats the purpose β it is like keeping a house key under the doormat. Key management is the discipline of generating, storing, rotating, revoking, and auditing cryptographic keys. A Hardware Security Module (HSM) is a tamper-resistant device that stores master keys in hardware; all key operations happen inside the HSM and the raw key bytes never leave it. Cloud equivalents are AWS KMS, Google Cloud KMS, and Azure Key Vault.
Envelope encryption is the standard pattern for encrypting large amounts of data without sending bulk data through the KMS. It works in two layers: a Data Encryption Key (DEK) β a fresh AES-256 key β encrypts the actual data. The DEK is then encrypted by a Key Encryption Key (KEK) stored in the KMS. Only the encrypted DEK (wrapped key) is stored alongside the data. To decrypt, the service sends the wrapped DEK to the KMS, receives back the plaintext DEK, decrypts the data, and immediately discards the DEK from memory. This limits KMS exposure, enables per-object key granularity, and makes key rotation cheap: re-encrypt the small DEK rather than re-encrypting gigabytes of data.
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import boto3
# --- Envelope Encryption Pattern ---
def encrypt_data(plaintext: bytes, kms_key_id: str) -> dict:
"""Encrypt data using envelope encryption via AWS KMS."""
kms = boto3.client('kms')
# 1. Generate a fresh DEK inside KMS (returns plaintext + ciphertext)
dek_response = kms.generate_data_key(
KeyId=kms_key_id,
KeySpec='AES_256'
)
dek_plaintext = dek_response['Plaintext'] # use then discard
dek_ciphertext = dek_response['CiphertextBlob'] # store this
# 2. Encrypt payload with DEK using AES-256-GCM
nonce = os.urandom(12) # 96-bit random nonce for GCM
aesgcm = AESGCM(dek_plaintext)
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# 3. DEK plaintext goes out of scope here (GC'd)
return {
'ciphertext': ciphertext,
'nonce': nonce,
'encrypted_dek': dek_ciphertext, # stored next to ciphertext
}
def decrypt_data(envelope: dict, kms_key_id: str) -> bytes:
"""Decrypt envelope-encrypted data."""
kms = boto3.client('kms')
# 1. Ask KMS to decrypt the wrapped DEK
dek_plaintext = kms.decrypt(
CiphertextBlob=envelope['encrypted_dek'],
KeyId=kms_key_id
)['Plaintext']
# 2. Decrypt payload with recovered DEK
aesgcm = AESGCM(dek_plaintext)
return aesgcm.decrypt(envelope['nonce'], envelope['ciphertext'], None)Encryption vs Hashing
Encryption is often confused with hashing, but they serve opposite purposes. Encryption is reversible: given the key, you can always recover the original plaintext. Hashing is one-way: a hash function (SHA-256, bcrypt, Argon2) maps arbitrary data to a fixed-length digest, and there is no algorithm to recover the original input from the digest alone. Hashing is the right tool for storing passwords (never encrypt passwords β use bcrypt or Argon2 so that a database breach does not expose them), verifying file integrity, and building HMACs for message authentication. Use encryption when you need to retrieve the original data; use hashing when you only need to verify it.
| Property | Encryption | Hashing |
|---|---|---|
| Reversible? | Yes β with the key | No β one-way function |
| Key required? | Yes | No (salts are not keys) |
| Output size | Same as or larger than input | Fixed (e.g., 32 bytes for SHA-256) |
| Use case | Confidentiality β recover the original | Integrity / password storage |
| Examples | AES-256-GCM, ChaCha20-Poly1305 | SHA-256, bcrypt, Argon2id |
| Wrong use | Storing passwords with encryption | Trying to decrypt a hash |
Choosing the Right Encryption Mode
Beyond choosing AES, you must choose an operation mode. CBC (Cipher Block Chaining) was widely used but requires separate message authentication (HMAC) to prevent tampering and is vulnerable to padding-oracle attacks when misimplemented. The modern default is AES-GCM (Galois/Counter Mode), an authenticated encryption with associated data (AEAD) mode that provides both confidentiality and integrity in a single pass β no separate MAC needed. AES-256-GCM with a 96-bit random nonce is the standard for new applications. For very high throughput or CPU-constrained environments without AES-NI hardware, ChaCha20-Poly1305 is the alternative: a stream cipher designed by Daniel Bernstein that is equally secure and fast in software.
Frequently Asked Questions
Is HTTPS the same as encryption?
HTTPS is HTTP layered on top of TLS, which provides encryption for data in transit. When you visit an HTTPS site, TLS encrypts everything between your browser and the server so that an eavesdropper on the network cannot read the request or response body, headers, or cookies. However, HTTPS alone does not encrypt data at rest on the server β that requires separate at-rest encryption. HTTPS also does not protect against a compromised server; it only secures the channel. So HTTPS is one layer of a defence-in-depth strategy, not a complete solution.
What is the difference between AES-128 and AES-256?
Both AES-128 and AES-256 use the same underlying AES block cipher structure; the difference is the key length (128 vs 256 bits) and the resulting number of rounds (10 vs 14). AES-128 is considered secure against all known classical attacks β there is no practical attack faster than brute force, and brute-forcing 128-bit keys is beyond any feasible computation. AES-256 provides a larger security margin that is relevant primarily in post-quantum threat models (Grover's algorithm halves the effective key length, making AES-128 effectively 64-bit and AES-256 effectively 128-bit against a sufficiently powerful quantum computer). For most applications today AES-128 is fine, but AES-256 is the standard recommendation for data with long secrecy requirements.
Should I implement my own encryption?
No β almost never. The canonical engineering rule in cryptography is do not roll your own crypto. Implementing encryption correctly requires avoiding dozens of subtle pitfalls: nonce reuse in GCM (which completely breaks confidentiality), timing side-channels in key comparison, improper padding, weak key derivation, and more. Instead, use well-audited libraries: libsodium (via Python's PyNaCl or Go's golang.org/x/crypto/nacl) provides safe high-level APIs; Python's cryptography package exposes AES-GCM and RSA correctly; and cloud KMS services abstract key management entirely. Reserve custom cryptographic work for the rare case where a specific algorithm is unavailable and you have deep expertise.
Encryption is not a checkbox β it is a system. Protect data in transit with TLS, at rest with AES-256-GCM, manage keys with a proper KMS, and rotate them on schedule. Every layer matters.
β alokknight Engineering
