Skip to content
Cryptography Explained — Complete Beginner's Guide

Cryptography Explained — Complete Beginner's Guide

DodaTech Updated Jun 6, 2026 12 min read

Cryptography is the science of securing information by transforming it into unreadable formats using encryption, ensuring that only authorized parties can access the original data.

What You’ll Learn

By the end of this tutorial, you’ll understand symmetric vs asymmetric encryption, how hashing works, what digital signatures are, and you’ll write Python code to encrypt data and verify file integrity.

Why Cryptography Matters

Every time you visit an HTTPS website, send a WhatsApp message, or use a password manager, you’re relying on cryptography. At DodaTech, Doda Browser uses TLS encryption for all connections, and Durga Antivirus Pro uses cryptographic signatures to verify update authenticity. Understanding cryptography helps you build secure systems and protect user data.

Cryptography Learning Path

    flowchart LR
  A[Security Basics] --> B[Network Security]
  B --> C[Web Security]
  C --> D[Cryptography]
  D --> E[Ethical Hacking]
  E --> F[Pen Testing]
  D --> G{You Are Here}
  style G fill:#f90,color:#fff
  
Prerequisites: Basic Cyber Security concepts. Some Python experience helps for the code examples.

What Is Cryptography? (The “Why” First)

Think of cryptography like a locked box with a secret message. You write a message, lock it in a box, and send it. Only the person with the right key can open the box and read the message. Cryptography does the same thing with digital data.

But here’s the challenge: how do you get the key to the other person without someone intercepting it? This is the fundamental problem cryptography solves — and it uses clever math to do it.

Symmetric Encryption — One Key to Rule Them All

Symmetric encryption uses the same key to encrypt and decrypt data. It’s like a lockbox with one key — you lock it, send it, and the receiver uses the same key to unlock it.

How It Works

Plain Text: "Hello" + Key: "secret123" → Encrypted: "hYx8s2Q="
Encrypted: "hYx8s2Q=" + Key: "secret123" → Decrypted: "Hello"

Python Example with Fernet (Symmetric)

# symmetric_encryption.py
# Requires: pip install cryptography
from cryptography.fernet import Fernet

def generate_key():
    """Generate a random encryption key."""
    key = Fernet.generate_key()
    print(f"Key (save this securely!): {key.decode()}")
    return key

def encrypt_message(key, message):
    """Encrypt a message using the key."""
    cipher = Fernet(key)
    encrypted = cipher.encrypt(message.encode())
    print(f"Original: {message}")
    print(f"Encrypted: {encrypted.decode()}")
    return encrypted

def decrypt_message(key, encrypted_data):
    """Decrypt a message using the key."""
    cipher = Fernet(key)
    decrypted = cipher.decrypt(encrypted_data)
    print(f"Decrypted: {decrypted.decode()}")
    return decrypted.decode()

# Run it
if __name__ == "__main__":
    key = generate_key()
    msg = "This is a secret message!"
    encrypted = encrypt_message(key, msg)
    decrypted = decrypt_message(key, encrypted)
    print(f"\nSuccess: {msg == decrypted}")

Expected output:

Key (save this securely!): dGhpcyBpcyBhbiBleGFtcGxlIGtleQ==
Original: This is a secret message!
Encrypted: gAAAAABmZ29...
Decrypted: This is a secret message!

Success: True

Why this matters: The encrypted output looks like random garbage. Anyone who intercepts it cannot read the original message without the key. This is how HTTPS protects your web traffic.

The Key Distribution Problem

Here’s the catch with symmetric encryption: how do you share the key securely? If you email the key, an attacker could intercept it. If you mail it on a USB drive, it could get lost. This problem led to the invention of asymmetric encryption.

Asymmetric Encryption — Two Keys Are Better Than One

Asymmetric encryption (also called public-key cryptography) uses two different keys:

  • Public key: Shared openly with everyone
  • Private key: Kept secret, never shared

Think of it like a mailbox on a street corner. Anyone can drop mail in (using the public key), but only you have the key to open it (the private key).

How It Works

  1. Alice generates a public/private key pair
  2. Alice gives her public key to Bob
  3. Bob encrypts a message using Alice’s public key
  4. Only Alice’s private key can decrypt it
  5. Even Bob cannot decrypt his own message (he used the public key)

Python Example with RSA (Asymmetric)

# asymmetric_encryption.py
# Requires: pip install cryptography
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding

def generate_keys():
    """Generate an RSA key pair."""
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
    )
    public_key = private_key.public_key()
    return private_key, public_key

def encrypt_with_public_key(public_key, message):
    ciphertext = public_key.encrypt(
        message.encode(),
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None,
        ),
    )
    return ciphertext

def decrypt_with_private_key(private_key, ciphertext):
    plaintext = private_key.decrypt(
        ciphertext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None,
        ),
    )
    return plaintext.decode()

if __name__ == "__main__":
    print("Generating RSA key pair...")
    private_key, public_key = generate_keys()

    message = "This is a secret message!"
    print(f"Original: {message}")

    encrypted = encrypt_with_public_key(public_key, message)
    print(f"Encrypted (hex): {encrypted.hex()[:50]}...")

    decrypted = decrypt_with_private_key(private_key, encrypted)
    print(f"Decrypted: {decrypted}")

    # Save keys to files (practical use)
    with open("private_key.pem", "wb") as f:
        f.write(private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        ))
    print("Private key saved to private_key.pem")

Expected output:

Generating RSA key pair...
Original: This is a secret message!
Encrypted (hex): 8f3a2b1c4d5e...
Decrypted: This is a secret message!
Private key saved to private_key.pem

Hashing — One-Way Encryption

Hashing is a one-way function: you can turn data into a hash, but you can’t turn a hash back into the original data. Think of it like a blender — you can turn fruit into a smoothie, but you can’t turn a smoothie back into whole fruit.

Hash Properties

  1. Deterministic: Same input always produces the same hash
  2. One-way: You cannot reverse a hash
  3. Collision-resistant: Two different inputs shouldn’t produce the same hash
  4. Avalanche effect: Changing one character changes the hash completely

Python Example with hashlib

# hashing_example.py
import hashlib

def hash_data(data, algorithm="sha256"):
    """Hash data using the specified algorithm."""
    h = hashlib.new(algorithm)
    h.update(data.encode())
    return h.hexdigest()

# Test different inputs
print("=== SHA-256 Hashing Demo ===")

message1 = "Hello, World!"
message2 = "Hello, World?"
message3 = "hello, world!"

h1 = hash_data(message1)
h2 = hash_data(message2)
h3 = hash_data(message3)

print(f"Input 1: {message1}")
print(f"Hash 1:  {h1}")
print(f"\nInput 2: {message2}")
print(f"Hash 2:  {h2}")
print(f"\nInput 3: {message3}")
print(f"Hash 3:  {h3}")

# Show avalanche effect
print(f"\n--- Avalanche Effect ---")
print(f"Input 1 and 2 differ by one character (! vs ?)")
diff_count = sum(1 for a, b in zip(h1, h2) if a != b)
print(f"Different hex characters: {diff_count}/64 ({diff_count*100/64:.1f}%)")

# Verify integrity
print(f"\n--- Integrity Check ---")
known_good_hash = h1
provided_data = "Hello, World!"
if hash_data(provided_data) == known_good_hash:
    print("INTEGRITY VERIFIED: Data matches the known hash.")
else:
    print("WARNING: Data has been modified!")

Expected output:

=== SHA-256 Hashing Demo ===
Input 1: Hello, World!
Hash 1:  dffd6021bb2d...

Input 2: Hello, World?
Hash 2:  8f7c8c9e5e1a...

Input 3: hello, world!
Hash 3:  4a1c2f5b7e9d...

--- Avalanche Effect ---
Input 1 and 2 differ by one character (! vs ?)
Different hex characters: 62/64 (96.9%)

--- Integrity Check ---
INTEGRITY VERIFIED: Data matches the known hash.

Notice how a single character change (! vs ? and H vs h) produces completely different hashes. This is the avalanche effect — a fundamental property of cryptographic hash functions.

Common Hashing Algorithms

AlgorithmOutput SizeSecurityUse Case
MD5128 bitsBroken (collisions found)Legacy systems only
SHA-1160 bitsWeak (deprecated)Avoid
SHA-256256 bitsStrongGeneral purpose, file verification
SHA-3VariableVery strongFuture-proof applications
bcryptVariableStrongPassword hashing
Argon2VariableVery strongModern password hashing

Never use MD5 or SHA-1 for security purposes. They’re cryptographically broken. Always use SHA-256 or SHA-3 for general hashing, and bcrypt or Argon2 for passwords.

Digital Signatures — Proving Who Sent What

A digital signature proves that a message came from a specific person and hasn’t been tampered with. Think of it like a handwritten signature on a legal document — it verifies both the signer’s identity and the document’s integrity.

How Digital Signatures Work

  1. Alice writes a message
  2. Alice creates a hash of the message
  3. Alice encrypts the hash with her private key — this is the signature
  4. Alice sends the message + signature to Bob
  5. Bob decrypts the signature using Alice’s public key to get the hash
  6. Bob computes the hash of the received message
  7. If both hashes match, the message is authentic and untampered
# digital_signature.py
# Requires: pip install cryptography
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding

def sign_message(private_key, message):
    signature = private_key.sign(
        message.encode(),
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH,
        ),
        hashes.SHA256(),
    )
    return signature

def verify_signature(public_key, message, signature):
    try:
        public_key.verify(
            signature,
            message.encode(),
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH,
            ),
            hashes.SHA256(),
        )
        return True
    except Exception:
        return False

# Example usage
private_key, public_key = generate_keys()  # Using our previous function

message = "Transfer $1000 to Alice"
signature = sign_message(private_key, message)
print(f"Signature (hex): {signature.hex()[:40]}...")

# Test verification
print(f"Verification (original): {verify_signature(public_key, message, signature)}")

# Tampered message
tampered = "Transfer $9999 to Eve"
print(f"Verification (tampered): {verify_signature(public_key, tampered, signature)}")

Expected output:

Signature (hex): a1b2c3d4e5f6...
Verification (original): True
Verification (tampered): False

Common Cryptography Mistakes

1. Rolling Your Own Crypto

Never write your own encryption algorithm. Professional cryptographers spend years designing and testing algorithms. Use well-audited libraries like cryptography (Python), libsodium, or OpenSSL.

2. Using ECB Mode (Electronic Codebook)

ECB mode encrypts identical plaintext blocks into identical ciphertext blocks. Patterns leak through. Always use CBC or GCM mode.

3. Hardcoding Encryption Keys

Keys should never be in source code. Use environment variables, key management services (AWS KMS, HashiCorp Vault), or secure hardware.

4. Using Broken Algorithms

MD5, SHA-1, DES, and RC4 are all broken. Stick to modern algorithms: SHA-256/3, AES-256-GCM, ChaCha20-Poly1305.

5. Not Authenticating Encrypted Data

Encryption ensures confidentiality but not integrity. An attacker can modify encrypted data without knowing the key. Always use authenticated encryption (GCM, ChaCha20-Poly1305).

6. Storing Passwords with Simple Hash

Never store passwords with just SHA-256. Use a slow, salted hashing algorithm like bcrypt (cost factor 10+) or Argon2id.

7. Forgetting to Rotate Keys

Keys should be rotated regularly. If a key is compromised, all data encrypted with it is at risk. Implement key rotation policies.

Common Mistakes Beginners Make

1. Skipping the Fundamentals

Many beginners jump straight to advanced topics without mastering the basics. Take time to understand the core concepts before moving on.

2. Not Practicing Enough

Reading tutorials without writing code leads to shallow understanding. Code along with every example and experiment on your own.

3. Ignoring Error Messages

Error messages tell you exactly what went wrong. Read them carefully — they usually point to the line and type of issue.

4. Copy-Pasting Without Understanding

It’s tempting to copy code from tutorials, but typing it yourself and understanding each line builds real skill.

5. Giving Up Too Early

Every developer hits frustrating bugs. Take breaks, ask for help, and remember that struggling is part of learning.

Practice Questions

1. What’s the difference between symmetric and asymmetric encryption?

Symmetric uses one key for both encryption and decryption. Asymmetric uses a public key for encryption and a private key for decryption. Symmetric is faster; asymmetric solves the key distribution problem.

2. Why can’t you “decrypt” a hash?

Hashing is a one-way function that discards information. There’s no mathematical operation to reverse it. This is different from encryption, which is designed to be reversible with the correct key.

3. What’s a digital signature?

A cryptographic technique that proves a message’s origin and integrity. The sender signs a hash of the message with their private key; anyone can verify it with the sender’s public key.

4. Why is AES-256-GCM better than AES-256-ECB?

GCM mode provides authenticated encryption (integrity + confidentiality) and uses a unique initialization vector. ECB mode leaks patterns because identical plaintext blocks produce identical ciphertext blocks.

5. Challenge: Write a script that hashes a file and compares it to a provided checksum.

import hashlib
import sys

def file_hash(filepath):
    h = hashlib.sha256()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            h.update(chunk)
    return h.hexdigest()

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python check_hash.py <file> <expected_sha256>")
        sys.exit(1)
    actual = file_hash(sys.argv[1])
    expected = sys.argv[2].lower()
    if actual == expected:
        print("HASH MATCH — File integrity verified")
    else:
        print(f"HASH MISMATCH!\nExpected: {expected}\nActual:   {actual}")

Real-World Task: Password Hashing

# password_storage.py
# For Python 3.10+
import hashlib
import secrets

def hash_password(password: str) -> str:
    """Hash a password with a random salt using SHA-256."""
    salt = secrets.token_hex(16)
    hash_obj = hashlib.sha256()
    hash_obj.update((password + salt).encode())
    return f"{salt}${hash_obj.hexdigest()}"

def verify_password(password: str, stored: str) -> bool:
    """Verify a password against a stored hash."""
    salt, expected_hash = stored.split("$")
    hash_obj = hashlib.sha256()
    hash_obj.update((password + salt).encode())
    return hash_obj.hexdigest() == expected_hash

# Example
stored = hash_password("my_secure_password")
print(f"Stored hash (salt+hash): {stored}")
print(f"Verify correct: {verify_password('my_secure_password', stored)}")
print(f"Verify wrong:    {verify_password('wrong_password', stored)}")

Note: In production, use bcrypt or argon2-cffi instead of SHA-256 for passwords. These algorithms are designed to be slow, making brute-force attacks impractical. This is exactly how Durga Antivirus Pro stores its configuration passwords.

FAQ

What is TLS and how does it relate to cryptography?
TLS (Transport Layer Security) uses asymmetric cryptography to establish a secure connection, then switches to symmetric cryptography for speed. This is what powers HTTPS encryption.
Is quantum computing going to break cryptography?
Quantum computers threaten current public-key systems (RSA, ECC) but not symmetric encryption (AES) or hashing (SHA-256). Post-quantum cryptography standards are being developed by NIST.
What’s the difference between encoding, encryption, and hashing?
Encoding (Base64) transforms data for compatibility — no security. Encryption transforms data with a key — reversible with the key. Hashing transforms data one-way — not reversible.
How long should an encryption key be?
AES-128 is sufficient for most applications. AES-256 provides extra margin. For RSA, use at least 2048 bits (4096 recommended). For ECC, use at least 256 bits.
Can encrypted data be hacked?
Encryption can be broken via brute force (trying every key), side-channel attacks (timing, power analysis), or exploiting implementation bugs. All modern algorithms are resistant to brute force when used correctly.

Try It Yourself

Create a simple file encryption tool in Python:

# file_crypt.py
# Requires: pip install cryptography
from cryptography.fernet import Fernet
import sys
import os

def generate_key():
    key = Fernet.generate_key()
    with open("secret.key", "wb") as f:
        f.write(key)
    print("Key saved to secret.key — keep this safe!")

def load_key():
    return open("secret.key", "rb").read()

def encrypt_file(filename):
    key = load_key()
    cipher = Fernet(key)
    with open(filename, "rb") as f:
        data = f.read()
    encrypted = cipher.encrypt(data)
    with open(filename + ".encrypted", "wb") as f:
        f.write(encrypted)
    print(f"Encrypted: {filename}{filename}.encrypted")

def decrypt_file(filename):
    key = load_key()
    cipher = Fernet(key)
    with open(filename, "rb") as f:
        data = f.read()
    decrypted = cipher.decrypt(data)
    output_name = filename.replace(".encrypted", ".decrypted")
    with open(output_name, "wb") as f:
        f.write(decrypted)
    print(f"Decrypted: {filename}{output_name}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python file_crypt.py <command> [filename]")
        print("Commands: genkey, encrypt, decrypt")
        sys.exit(1)

    cmd = sys.argv[1]
    if cmd == "genkey":
        generate_key()
    elif cmd == "encrypt" and len(sys.argv) == 3:
        encrypt_file(sys.argv[2])
    elif cmd == "decrypt" and len(sys.argv) == 3:
        decrypt_file(sys.argv[2])
    else:
        print("Invalid command or missing filename.")

This tool uses the same principles that Doda Browser uses to encrypt your saved passwords and Durga Antivirus Pro uses to securely update its virus definitions.

What’s Next

What’s Next

Congratulations on completing this Cryptography tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro