Mathematical Techniques for Information Security: Cryptography with Python

Cryptography with Python

Mathematical Techniques for Information Security: Cryptography with Python

A practical walkthrough of the number theory and algorithms that keep data safe — with working Python code for each technique.


1. Introduction

Cryptography is applied mathematics. Every secure channel, password hash, and digital signature you rely on is built on a handful of mathematical ideas — modular arithmetic, prime numbers, discrete logarithms, and one-way functions. This post walks through the core techniques used in modern information security, explains the math behind each one, and shows a working Python implementation you can run and experiment with.

We'll cover:

  • Modular arithmetic and the Caesar cipher (the foundation)
  • RSA — public-key encryption based on prime factorization
  • Diffie–Hellman key exchange — based on discrete logarithms
  • Cryptographic hash functions (SHA-256)
  • AES — symmetric encryption used for bulk data

2. Modular Arithmetic: The Foundation

Almost every cryptographic algorithm relies on modular arithmetic — arithmetic that "wraps around" after reaching a fixed number, called the modulus. We write this as:

a ≡ b (mod n)   means   a and b leave the same remainder when divided by n

The simplest cipher built on this idea is the Caesar cipher, which shifts each letter by a fixed amount k:

E(x) = (x + k) mod 26   |   D(x) = (x - k) mod 26

def caesar_encrypt(text, shift):
    result = ""
    for ch in text:
        if ch.isalpha():
            base = ord('A') if ch.isupper() else ord('a')
            result += chr((ord(ch) - base + shift) % 26 + base)
        else:
            result += ch
    return result

def caesar_decrypt(text, shift):
    return caesar_encrypt(text, -shift)

message = "ATTACK AT DAWN"
encrypted = caesar_encrypt(message, 3)
print("Encrypted:", encrypted)          # DWWDFN DW GDZQ
print("Decrypted:", caesar_decrypt(encrypted, 3))

This cipher is trivially breakable (only 25 possible shifts), but it introduces the core idea every modern scheme builds on: transforming data using a reversible mathematical function governed by a secret key.


3. RSA: Public-Key Cryptography

RSA is an asymmetric algorithm — it uses two different keys: a public key to encrypt, and a private key to decrypt. Its security rests on one fact: it is computationally easy to multiply two large primes, but extremely hard to factor their product back into the original primes.

The Math

  1. Choose two large primes p and q. Compute n = p × q.
  2. Compute Euler's totient: φ(n) = (p-1)(q-1).
  3. Choose public exponent e such that gcd(e, φ(n)) = 1.
  4. Compute private exponent d, the modular inverse of e: d ≡ e⁻¹ (mod φ(n)).
  5. Public key: (n, e). Private key: (n, d).
  6. Encryption: c = mᵉ mod n. Decryption: m = c^d mod n.
from sympy import randprime, gcd, mod_inverse

def generate_keys(bits=16):
    p = randprime(2**(bits-1), 2**bits)
    q = randprime(2**(bits-1), 2**bits)
    while q == p:
        q = randprime(2**(bits-1), 2**bits)

    n = p * q
    phi = (p - 1) * (q - 1)

    e = 65537
    while gcd(e, phi) != 1:
        e += 2

    d = mod_inverse(e, phi)
    return (n, e), (n, d)   # public, private

def rsa_encrypt(message, public_key):
    n, e = public_key
    return [pow(ord(ch), e, n) for ch in message]

def rsa_decrypt(cipher, private_key):
    n, d = private_key
    return ''.join(chr(pow(c, d, n)) for c in cipher)

public_key, private_key = generate_keys(bits=32)
msg = "HELLO"
cipher = rsa_encrypt(msg, public_key)
print("Ciphertext:", cipher)
print("Decrypted:", rsa_decrypt(cipher, private_key))

Note: real-world RSA uses 2048+ bit primes and proper padding (OAEP). The code above is simplified for teaching the math — for production use, always use a vetted library like cryptography or PyCryptodome.


4. Diffie–Hellman Key Exchange

Diffie–Hellman lets two parties agree on a shared secret over an insecure channel, without ever transmitting the secret itself. It relies on the discrete logarithm problem: given g, p, and gË£ mod p, it's hard to recover x.

Alice picks secret a → sends A = gᵃ mod p
Bob picks secret b → sends B = gᵇ mod p
Shared secret: s = Bᵃ mod p = Aᵇ mod p = g^(ab) mod p

import random

# Publicly agreed values
p = 23   # small prime (use 2048-bit primes in real systems)
g = 5    # primitive root mod p

def generate_private_key():
    return random.randint(2, p - 2)

def generate_public_key(private_key):
    return pow(g, private_key, p)

def compute_shared_secret(their_public, my_private):
    return pow(their_public, my_private, p)

alice_priv = generate_private_key()
bob_priv = generate_private_key()

alice_pub = generate_public_key(alice_priv)
bob_pub = generate_public_key(bob_priv)

alice_shared = compute_shared_secret(bob_pub, alice_priv)
bob_shared = compute_shared_secret(alice_pub, bob_priv)

print("Alice's shared secret:", alice_shared)
print("Bob's shared secret:  ", bob_shared)
print("Match:", alice_shared == bob_shared)

5. Cryptographic Hash Functions

A hash function maps data of any size to a fixed-size digest. Good cryptographic hashes are one-way (can't reverse the digest to get the input) and collision-resistant (hard to find two inputs with the same digest). Hashes are used for password storage, integrity checks, and digital signatures.

import hashlib

def sha256_hash(data):
    return hashlib.sha256(data.encode()).hexdigest()

password = "MySecretPassword123"
print("SHA-256:", sha256_hash(password))

# Demonstrating the avalanche effect: one character change
print("SHA-256:", sha256_hash("MySecretPassword124"))

Notice how a single-character change produces a completely different digest — this "avalanche effect" is a key design goal of secure hash functions.

For password storage specifically, never use a raw hash like SHA-256 alone — use a slow, salted algorithm designed for passwords, such as bcrypt, scrypt, or Argon2.


6. AES: Symmetric Encryption

Where RSA is slow and used mainly to exchange keys, AES (Advanced Encryption Standard) is fast and used to encrypt the actual bulk data. It operates on fixed-size blocks (128 bits) through multiple rounds of substitution, permutation, and mixing based on operations in a finite field, GF(2⁸).

# pip install pycryptodome
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes

def aes_encrypt(plaintext, key):
    cipher = AES.new(key, AES.MODE_CBC)
    ct_bytes = cipher.encrypt(pad(plaintext.encode(), AES.block_size))
    return cipher.iv, ct_bytes

def aes_decrypt(iv, ct_bytes, key):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    pt = unpad(cipher.decrypt(ct_bytes), AES.block_size)
    return pt.decode()

key = get_random_bytes(32)   # AES-256
iv, ciphertext = aes_encrypt("Top secret battle plans", key)
print("Ciphertext (hex):", ciphertext.hex())

decrypted = aes_decrypt(iv, ciphertext, key)
print("Decrypted:", decrypted)

In practice, HTTPS and most secure messaging apps use a hybrid approach: Diffie–Hellman or RSA to establish a shared key, then AES to encrypt the actual traffic — combining the key-exchange strength of asymmetric math with the speed of symmetric ciphers.


7. Summary Table

Technique Math Basis Used For
Caesar Cipher Modular addition Teaching only
RSA Prime factorization, modular exponentiation Key exchange, digital signatures
Diffie–Hellman Discrete logarithm problem Establishing shared secrets
SHA-256 One-way compression functions Integrity checks, signatures
AES Finite field (GF(2⁸)) operations Bulk data encryption

8. Closing Thoughts

Every layer of modern digital security — from the padlock icon in your browser to encrypted messaging apps — is built from these same mathematical primitives: modular arithmetic, prime numbers, discrete logarithms, and one-way functions. Understanding the math doesn't just satisfy curiosity — it's what lets you evaluate whether a security claim actually holds up, and avoid classic mistakes like rolling your own crypto.

Disclaimer: The code in this post is written for educational purposes to illustrate the underlying mathematics. For production systems, always use audited, well-maintained libraries (e.g. cryptography, PyCryptodome) rather than custom implementations.

Post a Comment

0 Comments