
[!NOTE] Encryption is the process of converting plaintext into ciphertext to protect sensitive information from unauthorized access. It is a fundamental aspect of cybersecurity and is used to secure data in transit and at rest.
The algorithm and the key combination determines how the plain text will be modified or jumbled up, which is a process of substitution and transposition of the characters. If the algorithm and Key are weak encryption will also be weak.
| Symmetric Encryption | Asymmetric Encryption |
|---|---|
| - Uses a single key for both encryption and decryption. - Faster than asymmetric encryption. - Examples: AES, DES, RC4. | - Uses a pair of keys (public and private) for encryption and decryption. - Slower than symmetric encryption. - Examples: RSA, ECC, DSA. |
| - Key distribution can be a challenge since the same key must be shared securely between parties. - Suitable for encrypting large amounts of data. | - Key distribution is easier since the public key can be shared openly, while the private key remains secure. - Often used for encrypting small amounts of data, such as digital signatures and key exchange. |
| - Commonly used for encrypting files, databases, and communication channels. | - Commonly used for secure communication, digital signatures, and key exchange. |
| - Examples of symmetric encryption algorithms include AES (Advanced Encryption Standard), DES (Data Encryption Standard), and RC4. | - Examples of asymmetric encryption algorithms include RSA (Rivest-Shamir-Adleman), ECC (Elliptic Curve Cryptography), and DSA (Digital Signature Algorithm). |
| - Symmetric encryption is generally faster than asymmetric encryption, making it suitable for encrypting large amounts of data. | - Asymmetric encryption is generally slower than symmetric encryption, making it more suitable for encrypting small amounts of data, such as digital signatures and key exchange. |

sequenceDiagram
participant Sender
participant Receiver
Sender->>Sender: Generate Secret Key
Sender->>Sender: Encrypt Plaintext with Secret Key
Sender->>Receiver: Send Ciphertext
Receiver->>Receiver: Decrypt Ciphertext with Secret Key
Receiver->>Receiver: Obtain Original Plaintext
Symmetric encryption involves the following steps:
Note: The security of symmetric encryption relies entirely on keeping the key secret. If the key is intercepted or leaked, the encrypted data can be easily decrypted.
Example of Symmetric Encryption:
The receiver terminal will ask us for the secret key, and then it will use the decryption algorithm to decrypt the message and display it.
Here’s the SHELL code for the sender terminal:
#!/bin/sh
echo "Enter the message you want to send:"
read -r message
# Generate a random secret key
secret_key=$(openssl rand -hex 16)
# Encrypt the message using AES encryption
ciphertext=$(echo "$message" | openssl enc -aes-256-cbc -a -salt -pass pass:"$secret_key")
# Save the secret key and ciphertext to files to simulate sending
echo "$secret_key" > secret.key
echo "Secret key generated and saved to secret.key: $secret_key"
echo "$ciphertext" > message.enc
echo "Ciphertext generated and saved to message.enc: $ciphertext"
echo "Message encrypted and sent."
Here’s the SHELL code for the receiver terminal:
#!/bin/sh
echo "Waiting for message (timeout 5 minutes)..."
timeout=300
elapsed=0
while [ $elapsed -lt $timeout ]; do
if [ -f "secret.key" ] && [ -f "message.enc" ]; then
# Read the secret key and ciphertext from files
secret_key=$(cat secret.key)
echo "Secret Key: $secret_key"
ciphertext=$(cat message.enc)
echo "Ciphertext: $ciphertext"
# Decrypt the message using AES decryption
plaintext=$(echo "$ciphertext" | openssl enc -aes-256-cbc -a -d -salt -pass pass:"$secret_key")
echo "Decrypted Message: $plaintext"
# Clean up the temporary files
rm secret.key message.enc
exit 0
fi
sleep 1
elapsed=$((elapsed + 1))
done
echo "Timeout reached. No message received."
Here’s what it looks like when we run the sender terminal:

[!NOTE] AES itself only knows how to encrypt one fixed-size block of data (16 bytes) at a time. A mode of operation is the recipe for stitching many blocks together to encrypt a real message of any length. Picking the wrong mode can leak information even though the underlying cipher (AES) is perfectly secure.
GCM (Galois/Counter Mode): CTR mode plus a built-in authentication tag. It gives you confidentiality and integrity in one pass, which is why it’s the default choice for TLS, SSH, and most modern protocols.
Quick example — watch ECB leak a repeated block, and CBC hide it:
# A fixed key/IV so the demo is reproducible — generate your own with
# openssl rand -hex 32 (key) and openssl rand -hex 16 (IV) in practice
KEY=b54971c7f30209393d8f460a0e2834a2334842fa1d109856ed05565689b5cabe
IV=7e17a6f53e8066d4c8c74bbaa3687fa5
# Two identical 16-byte blocks back to back
echo -n "SECRET_BLOCK_XYZSECRET_BLOCK_XYZ" | openssl enc -aes-256-ecb -K "$KEY" | xxd
# rows 1 and 2 of the output are IDENTICAL — that's the leak
echo -n "SECRET_BLOCK_XYZSECRET_BLOCK_XYZ" | openssl enc -aes-256-cbc -K "$KEY" -iv "$IV" | xxd
# every row is different, even though the plaintext blocks repeat

[!TIP] Rule of thumb: prefer an AEAD (Authenticated Encryption with Associated Data) mode like AES-GCM or ChaCha20-Poly1305 over plain CBC or CTR. AEAD modes detect tampering automatically instead of relying on you to add a separate integrity check.
[!NOTE] Asymmetric encryption, also known as public-key cryptography, is a type of encryption that uses a pair of keys: a public key for encryption and a private key for decryption. This allows for secure communication without the need for the sender and receiver to share a secret key beforehand.
sequenceDiagram
participant Sender
participant Receiver
Note right of Receiver: 1. Key Generation
Receiver->>Receiver: Generates Key Pair (Public & Private Keys)
Note over Sender, Receiver: 2. Key Exchange
Receiver->>Sender: Sends Public Key (Open Channel)
Note left of Sender: 3. Encryption
Sender->>Sender: Encrypts Plaintext using Receiver's Public Key
Note over Sender, Receiver: 4. Transmission
Sender->>Receiver: Sends Ciphertext (Potentially Insecure Channel)
Note right of Receiver: 5. Decryption
Receiver->>Receiver: Decrypts Ciphertext using Private Key
Receiver->>Receiver: Obtains Original Plaintext
Asymmetric encryption uses 2 keys: a public key and a private key. The public key is used for encryption, while the private key is used for decryption. The sender encrypts the plaintext using the receiver’s public key, and the receiver decrypts the ciphertext using their private key.
Asymmetric encryption involves the following steps:
[!IMPORTANT] Note: The security of asymmetric encryption relies on the difficulty of certain mathematical problems, such as factoring large integers (in the case of RSA) or solving the discrete logarithm problem (in the case of ECC). As long as these problems remain computationally infeasible to solve, asymmetric encryption can provide strong security.
[!IMPORTANT]
- If you encrypt with the public key, you can only decrypt with the private key. This means confidentiality is what matters the most. This is called Open Message Confidentiality (OMC). It is used to ensure that only the intended recipient can read the message. If the sender encrypts the message with the receiver’s public key, only the receiver can decrypt it with their private key, thus ensuring confidentiality.
- If you encrypt with the private key, you can only decrypt with the public key. This means authentication is what matters the most. This is called Open Message Authentication (OMA). It is used to verify the authenticity of the sender. If the sender encrypts the message with their private key, anyone can decrypt it with the sender’s public key, but only the sender could have encrypted it in the first place, thus verifying their identity.
Why Emails don’t use Asymmetric Encryption?
[!NOTE] Diffie-Hellman (DH) is not an encryption algorithm at all — it’s a way for two parties to agree on a shared secret over a public channel that an eavesdropper, watching every message, still cannot compute. That shared secret then becomes the key for a symmetric algorithm like AES.

Open the interactive diagram — exportable as PNG/PDF.
[!NOTE] A hash function is a mathematical function that takes an input (or “message”) and produces a fixed-size string of bytes, typically a digest that is unique to the input. Hash functions are commonly used in cryptography for various purposes, including data integrity verification, password hashing, and digital signatures.
sequenceDiagram
participant User
participant HashFunction
participant Database
User->>HashFunction: Input Data (e.g., Password)
HashFunction->>HashFunction: Compute Hash Digest
HashFunction->>Database: Store Hash Digest
User->>Database: Login Attempt with Password
Database->>HashFunction: Retrieve Stored Hash Digest
HashFunction->>HashFunction: Compute Hash Digest of Login Attempt
HashFunction->>Database: Compare Hash Digests
Database->>User: Authentication Result (Success/Failure)
Let’s see this with an example:
We will create a simple password hashing system using the SHA-256 hash function. The user will input a password, which will be hashed and stored in a database (simulated with a file). When the user attempts to log in, they will input their password again, and the system will hash it and compare it to the stored hash to verify their identity.
Here’s the SHELL code for the password hashing system:
#!/bin/sh
echo "Enter your password:"
read -r password
# Hash the password using SHA-256
hashed_password=$(echo -n "$password" | openssl dgst -sha256)
# Store the hashed password in a file (simulating a database)
echo "$hashed_password" > password.hash
echo "Password hashed and stored."
echo "Login attempt. Enter your password:"
read -r login_password
# Hash the login attempt password
hashed_login_password=$(echo -n "$login_password" | openssl dgst -sha256)
# Retrieve the stored hashed password
stored_hashed_password=$(cat password.hash)
if [ "$hashed_login_password" = "$stored_hashed_password" ]; then
echo "Authentication successful!"
else
echo "Authentication failed!"
fi
password.hash.
Crypto systems today use SHA-256 or above for hashing, and MD5 and SHA-1 are considered weak and should be avoided for secure applications.
[!NOTE] A plain hash proves data hasn’t changed, but it doesn’t prove who sent it — anyone can recompute a SHA-256 hash. An HMAC fixes this by mixing a shared secret key into the hashing process, so only someone who knows the key could have produced that specific hash.
HMAC(key, message) = hash((key XOR opad) + hash((key XOR ipad) + message)). You don’t need to memorize the formula
— just remember it’s “hash the message, but salted with a secret key in a very specific, collision-resistant way.”HS256 algorithm), securing
webhooks, and as the integrity check inside older TLS cipher suites.Quick example — generating and verifying an HMAC with OpenSSL:
# Sign a message with a shared secret
echo -n "hello world" | openssl dgst -sha256 -hmac "supersecretkey"
# The receiver, who also knows "supersecretkey", recomputes the same HMAC
# and checks it matches — if it does, the message is both untampered and
# confirmed to have come from someone who holds the shared key.
Try it yourself — same command, same key, twice in a row, then a wrong key:

[!IMPORTANT] HMAC uses a shared secret (symmetric), while digital signatures use a private/public key pair (asymmetric). That’s the key distinction: HMAC is faster but both sides must already trust each other with the same key; digital signatures are slower but let anyone with the public key verify authenticity, with no shared secret required.
[!NOTE] Generic hash functions like SHA-256 are built to be fast — great for checksums, terrible for passwords. A GPU can try billions of SHA-256 guesses per second. Key Derivation Functions (KDFs) are deliberately slow and memory-hungry hash functions designed specifically to make password cracking expensive.
Argon2: Winner of the 2015 Password Hashing Competition and the current recommended default. Tunable across three dimensions — time, memory, and parallelism — giving you fine control over the cost of cracking attempts.
Quick example — timing a fast hash against bcrypt (requires the Apache htpasswd utility, preinstalled on most
Linux/macOS systems — apt install apache2-utils if it’s missing):
# A generic hash: near-instant, which is exactly the problem for passwords
time (echo -n "password123" | openssl dgst -sha256)
# bcrypt at cost factor 12: deliberately slow, on purpose
time (htpasswd -bnBC 12 "" "password123")

[!TIP] Rule of thumb for new projects: use Argon2id if available, otherwise bcrypt. Never store passwords with a plain hash function like SHA-256 or MD5, even with a salt — they’re simply too fast to brute-force at scale.

[!IMPORTANT] Digital signatures are a cryptographic mechanism used to verify the authenticity and integrity of digital messages or documents. They provide a way to ensure that a message has not been altered and that it was indeed sent by the claimed sender.
The Digital Signatures are created when the sender generates a hash of the message and then encrypts that hash with their private key. The resulting encrypted hash is the digital signature, which is sent along with the original message.
The hash of the message is created using a hash function, which produces a fixed-size output that is unique to the input message. This hash serves as a fingerprint of the message, allowing the receiver to verify that the message has not been altered.
Let’s try to understand this through an example. We have a sender shell script that generates a digital signature for a message and a receiver shell script that verifies the digital signature.
Here’s the SHELL code for the sender terminal:
#!/bin/sh
echo "Enter the message you want to sign:"
read -r message
# Generate a hash of the message
message_hash=$(echo -n "$message" | openssl dgst -sha256)
# Generate Private and Public Key Pair (if not already generated)
if [ ! -f sender_private.key ]; then
openssl genpkey -algorithm RSA -out sender_private.key -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in sender_private.key -out sender_public.key
fi
# Encrypt the hash with the senders private key to create the digital signature
digital_signature=$(echo -n "$message_hash" | openssl rsautl -sign -inkey sender_private.key)
# Save the message and digital signature to files to simulate sending
echo "$message" > message.txt
echo "$digital_signature" > signature.bin
echo "Message signed and sent."
Here’s the SHELL code for the receiver terminal:
#!/bin/sh
echo "Waiting for message and signature (timeout 5 minutes)..."
timeout=300
elapsed=0
echo "Verifying public key exists..."
while [ $elapsed -lt $timeout ]; do
if [ -f "sender_public.key" ] && [ -f "message.txt" ] && [ -f "signature.bin" ]; then
# Read the message and digital signature from files
message=$(cat message.txt)
digital_signature=$(cat signature.bin)
# Generate a hash of the received message
message_hash=$(echo -n "$message" | openssl dgst -sha256)
# Decrypt the digital signature using the sender's public key to retrieve the original hash
decrypted_hash=$(echo -n "$digital_signature" | openssl rsautl -verify -inkey sender_public.key -pubin)
# Compare the decrypted hash with the hash of the received message
if [ "$decrypted_hash" = "$message_hash" ]; then
echo "Digital signature is valid. Message is authentic and has not been altered."
else
echo "Digital signature is invalid. Message may have been tampered with or sender's identity cannot be verified."
fi
echo "Received Message: $message"
# Clean up the temporary files
rm sender_public.key message.txt signature.bin
exit 0
fi
sleep 1
elapsed=$((elapsed + 1))
done
echo "Timeout reached. No message received."
Both of the terminals were running simultaneously, and the sender terminal was used to input a message, which was then hashed and signed with the sender’s private key. The receiver terminal waited for the message and signature, then verified the signature using the sender’s public key and compared the hash of the received message with the decrypted hash from the signature to confirm authenticity.

[!NOTE] The One-Time Pad (OTP) is the only encryption scheme ever mathematically proven to be unbreakable — not just “hard to break with today’s computers,” but unbreakable even with infinite computing power. It’s also, in practice, almost never used. Understanding why is a great lesson in the gap between theoretical and practical security.
[!NOTE] Asymmetric encryption solves how to encrypt without a shared secret, but it leaves one big question open: when you receive someone’s public key, how do you know it actually belongs to them and not to an attacker impersonating them? PKI is the trust system that answers that question — it’s what makes the padlock icon in your browser meaningful.

Open the interactive diagram — exportable as PNG/PDF.
Quick example — inspecting a live website’s certificate chain from the terminal:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates

[!TIP] This is the missing piece that connects everything in this lesson: Diffie-Hellman establishes a shared secret, AES/ChaCha20 encrypts the data with it, HMAC/AEAD modes check integrity, and PKI/certificates confirm you negotiated that secret with the right party in the first place, not an attacker. TLS, covered next, wires all of these pieces together into one protocol.
[!IMPORTANT] SSL stands for Secure Sockets Layer, and TLS stands for Transport Layer Security. Both SSL and TLS are cryptographic protocols designed to provide secure communication over a computer network. TLS is the successor to SSL and is more secure and efficient than its predecessor.
TLS v1.3 is the latest version of the TLS protocol, which was finalized in 2018. It offers improved security and
performance compared to previous versions of TLS and SSL. TLS v1.3 removes support for older, less secure
cryptographic algorithms and introduces new features to enhance security and reduce latency.
TLS is the most important protocol for securing communication on the internet. It not only encrypts the data but also ensures data integrity and authentication. TLS is an End to End encryption protocol, which means that the data is encrypted from the sender to the receiver, and only the intended recipient can decrypt it. This makes it an essential component of secure communication on the internet, especially for sensitive transactions such as online banking, e-commerce, and secure email communication.
[!NOTE] The best Authentication & Key Exchanging Algorithm to use are ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) for key exchange and ECDSA (Elliptic Curve Digital Signature Algorithm) for authentication. These algorithms provide strong security while also being efficient in terms of performance. ECDHE allows for perfect forward secrecy, which means that even if the server’s private key is compromised in the future, past communications remain secure. ECDSA provides a secure method for verifying the identity of the communicating parties without the need for a trusted third party.

[!WARNING] But the problem is you don’t always get the choice. A server will support only certain authentication * key exchange algorithms only.

Now, this property ensures that your session keys are not compromised even if the server’s private key is compromised in the future. This is because the session keys are generated using a key exchange algorithm that does not rely on the server’s private key, such as Diffie-Hellman.
As a result, even if an attacker gains access to the server’s private key, they would not be able to decrypt past communications that were encrypted with session keys generated through a secure key exchange process. This provides an additional layer of security and helps protect against future attacks on the server’s private key.
Even if compromise of a single session key will not affect any data other than that exchange in that specific session protected by that particular key. PFS represents a big step forward in protecting data on the transport layer.
[!WARNING] Everything asymmetric covered in this lesson — RSA, Diffie-Hellman, ECDHE, ECDSA — relies on math problems (factoring large integers, discrete logarithms) that are hard for classical computers. A sufficiently powerful quantum computer running Shor’s Algorithm could solve those same problems efficiently, breaking all of them at once.
[!TIP] You don’t need to change anything in your own projects today, but it’s worth knowing the direction the industry is moving: symmetric crypto (AES-256, SHA-256/384) is already considered quantum-resistant enough; asymmetric crypto is in the middle of a multi-year migration to lattice-based algorithms.