Foundationপ্রথম নীতি থেকে
LEVEL 10লেসন ৫/২০অ্যাডভান্সড৫৫ মিনিট

Asymmetric Cryptography — Diffie-Hellman ও RSA, চাবি ছাড়াই চাবি বিনিময়

Asymmetric Cryptography: Diffie-Hellman and RSA

Symmetric encryption-এর অচেনা-দুইজনের-key-ভাগাভাগি সমস্যা সমাধান করে asymmetric crypto — Diffie-Hellman প্রকাশ্য চ্যানেলেই একটা গোপন সংখ্যায় একমত হতে দেয়, RSA public key দিয়ে encrypt আর private key দিয়ে decrypt-এর অসাম্য তৈরি করে; দুইটাই discrete-log/factoring trapdoor-এর ওপর দাঁড়িয়ে, আর বাস্তবে ব্যবহার হয় শুধু একটা ছোট session-key wrap করতে — bulk data-র জন্য এখনও symmetric cipher-ই লাগে।

এই লেসন শেষে আপনি পারবেন

  • Symmetric encryption-এর key-exchange সমস্যা (chicken-and-egg) কেন সমাধানহীন থেকে যায় তা যুক্তি দিয়ে ব্যাখ্যা করতে পারবেন
  • Diffie-Hellman key exchange-এর discrete-log-trapdoor যুক্তি ছোট বাস্তব সংখ্যায় হাতে-কলমে trace করে দেখাতে পারবেন
  • Toy RSA key-generation (p, q, n, φ(n), e, d) থেকে শুরু করে encrypt-decrypt round-trip পর্যন্ত পুরো প্রক্রিয়া কোড চালিয়ে যাচাই করতে পারবেন
  • Hybrid encryption-এ asymmetric ও symmetric primitive কীভাবে একসাথে কাজ করে (session-key wrap বনাম bulk-data encrypt) তা ডিজাইন করে ব্যাখ্যা করতে পারবেন
  • RSA বনাম ECC-এর key-size trade-off আর কেন আধুনিক system ECC-কে default হিসেবে বেছে নেয় তা যুক্তি দিতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

আগের লেসনের শেষে একটা প্রশ্ন তুলে রাখা হয়েছিল, ইচ্ছাকৃতভাবে অনুত্তরিত: Alice আর Bob-এর দুজনেরই symmetric encryption-এর জন্য একই চাবি লাগে — কিন্তু তারা যদি আগে কখনো সামনাসামনি না দেখা করে থাকে, adversary যে channel শুনছে ঠিক সেই channel দিয়েই তারা প্রথমবার এই চাবিতে কীভাবে একমত হবে?

এই প্রশ্নটা তুচ্ছ শোনায়, কিন্তু আসলে একটা মৌলিক দ্বন্দ্ব। চাবি পাঠাতে হলে একটা secure channel লাগে; secure channel বানাতে হলে একটা চাবি লাগে — এই বৃত্ত symmetric primitive দিয়ে ভাঙা যায় না। ফোনে বা সামনাসামনি একবার দেখা করে চাবি বিনিময় করা একটা সমাধান, কিন্তু প্রতিদিন কোটি কোটি নতুন HTTPS connection, নতুন WhatsApp চ্যাট, নতুন SSH session — কোনোটাতেই এই “আগে থেকে দেখা করা” বাস্তবসম্মত না।

Alice আর Bob কখনো দেখা করেনি। Eve পুরো channel শুনছে।
প্রশ্ন: প্রকাশ্য চ্যানেলেই কথা বলে তারা কীভাবে একটা গোপন সংখ্যায় একমত হবে,
        যেটা Eve শুনেও বের করতে পারবে না?

আজকের লেসন এই প্রশ্নের উত্তর — দুইটা ভিন্ন কিন্তু সম্পর্কিত যন্ত্র দিয়ে। প্রথমটা Diffie-Hellman key exchange: Alice আর Bob প্রকাশ্যেই কথা বলে, তবু একটা common secret-এ পৌঁছায় যা Eve বের করতে পারে না। দ্বিতীয়টা RSA: একটা চাবি-জোড়া যেখানে একটা দিয়ে শুধু lock করা যায়, আরেকটা দিয়ে শুধু unlock — আর lock-করার চাবিটা প্রকাশ্যে দিয়ে দিলেও কোনো সমস্যা নেই। দুইটা ভিন্ন mechanism, কিন্তু একই গাণিতিক ভিত্তির ওপর দাঁড়ানো: এমন একটা ফাংশন যেটা এক দিকে সহজ, উল্টো দিকে অসম্ভব-রকম কঠিন — আজকের ভাষায় trapdoor function।

মূল ধারণা

Asymmetric encryption — দুইটা চাবি, দুইটা ভিন্ন ক্ষমতা

Symmetric encryption-এ একটাই key — encrypt আর decrypt দুইটাতেই সেটাই লাগে। Asymmetric (public-key) encryption-এ দুইটা আলাদা key তৈরি হয়, গাণিতিকভাবে জোড়া-বাঁধা কিন্তু ভিন্ন ক্ষমতাসম্পন্ন:

public key   -- সবাইকে দেওয়া যায়, শুধু "lock" করতে পারে (encrypt)
private key  -- গোপন রাখতে হয়, শুধু এটাই "unlock" করতে পারে (decrypt)

c = E(public_key, m)
m = D(private_key, c)      -- শুধু private-key-হোল্ডার এটা করতে পারে

এই একটা বাক্য পুরো মডেলটা বদলে দেয়: public key প্রকাশ্যে পাঠানো যায়, adversary সেটা দেখলেও কোনো ক্ষতি নেই — কারণ lock করার ক্ষমতা আর unlock করার ক্ষমতা এক জিনিস না। Symmetric-এ একই চাবি দুই দিকেই কাজ করে বলে চাবি ফাঁস হওয়া মানেই সব শেষ; asymmetric-এ public key ফাঁস হওয়াটাই তো নকশা — শুধু private key গোপন থাকলেই যথেষ্ট।

আজকের দুইটা যন্ত্র এই মডেলকে দুইভাবে বাস্তবায়ন করে:

  • Diffie-Hellman (DH) — কোনো message encrypt করে না। এটা একটা key-agreement protocol: দুইজন মানুষ প্রকাশ্য চ্যানেলে গণনা চালিয়ে একটা identical shared secret-এ পৌঁছায়, যেটা পরে symmetric key হিসেবে ব্যবহার হয়।
  • RSA — সরাসরি একটা encrypt/decrypt scheme। Public key দিয়ে encrypt, private key দিয়ে decrypt — একই সংখ্যাগত trapdoor অন্যভাবে ব্যবহার করে।

Trapdoor function — এক দিকে সহজ, উল্টো দিকে অসম্ভব

দুইটা যন্ত্রই একটা common ধারণার ওপর দাঁড়িয়ে: trapdoor function — এমন একটা গাণিতিক অপারেশন যা এক দিকে (forward) computationally সহজ, কিন্তু উল্টো দিকে (backward) — বিশেষ কোনো “trapdoor” তথ্য (private key) ছাড়া — computationally infeasible।

forwardbackward
DH-এর trapdoorg^x mod p গণনা করাy থেকে x বের করা (discrete log)
RSA-এর trapdoorm^e mod n গণনা করাc থেকে m বের করা d ছাড়া (factoring-নির্ভর)

দুইটাতেই forward দিক দ্রুত (fast modular exponentiation, O(log exponent) multiplication), backward দিক বিশাল কষ্টসাধ্য (কোনো known efficient classical algorithm নেই)। এই অসাম্যটাই — কেন এই অসাম্য থাকে, কীভাবে সংখ্যায় প্রমাণ করা যায় — আজকের বাকি লেসনের মূল বিষয়।

Hybrid encryption — একটা বার্তা পাঠাতে আসলে কোন স্তরে কোন primitive কাজ করে
  1. applicationপাঠাতে চাওয়া আসল ডেটা — টেক্সট, ফাইল, ভিডিও, যেকোনো সাইজ
  2. session key negotiationDiffie-Hellman বা RSA — একটা ছোট, ফিক্সড-সাইজ symmetric key স্থাপন করা
  3. bulk data encryptionAES/ChaCha20-Poly1305 (আগের লেসন) — আসল payload, যত বড়ই হোক
  4. transportTCP/UDP — ciphertext বহন করে নিয়ে যায়, বিষয়বস্তু জানে না

উপরের ট্রেসটাই আজকের লেসনের থিসিস আগে থেকে বলে দিচ্ছে: asymmetric crypto bulk data-তে কাজ করে না, করে key negotiation-এ। কেন — সেটা “build” অংশে সংখ্যায় দেখানো হবে।

ভেতরে কী ঘটছে

Diffie-Hellman — discrete log trapdoor, ভেতরে কী ঘটছে

DH-এর সবটুকু গণিত একটা প্রশ্নের ওপর দাঁড়িয়ে: একটা মৌলিক সংখ্যা p আর একটা generator g (দুইটাই প্রকাশ্য) থাকলে, y = g^x mod p গণনা করা তুচ্ছ — কিন্তু y, g, p দেখে x বের করা কঠিন। এই সমস্যার নাম discrete logarithm problem (DLP)।

কেন forward সহজ? কারণ modular exponentiation “square-and-multiply” কৌশলে হয় — x-এর প্রতিটা বিটে সর্বোচ্চ দুইটা multiplication, তাই মোট কাজ x-এর বিট-সংখ্যার সমানুপাতিক (logarithmic)। কেন backward কঠিন? কারণ x বের করার কোনো shortcut জানা নেই — সেরা classical algorithm-ও p-এর আকারে sub-exponential, আর naive brute-force সরাসরি p-এর সমানুপাতিক (linear)। এই asymmetry — logarithmic বনাম (sub-)exponential — ই DH-কে ব্যবহারযোগ্য বানায়: legitimate পক্ষরা forward দিক ব্যবহার করে কাজ সারে মিলিসেকেন্ডে, attacker backward দিক আটকে থাকে বছরের পর বছর।

DH প্রোটোকলটা এই trapdoor-কে একটা চমৎকার trick-এ রূপান্তর করে — commutativity: (g^a)^b mod p = (g^b)^a mod p = g^(ab) mod p। দুইজনেই নিজের গোপন exponent দিয়ে অন্যজনের public value-কে আবার exponentiate করলে, দুইজনেই একই সংখ্যায় পৌঁছায় — অথচ কেউ কারো গোপন exponent জানে না।

নিচের কোডটা আসলেই চালানো — ছোট, বাস্তব সংখ্যা দিয়ে সম্পূর্ণ প্রক্রিয়া:

# dh.py — Diffie-Hellman key exchange, ছোট বাস্তব সংখ্যায় সম্পূর্ণ হাতে-কলমে
p = 23   # public: prime modulus
g = 5    # public: generator (primitive root mod p)

print(f"সবার জানা (public parameters): p = {p}, g = {g}")

# Alice এবং Bob স্বাধীনভাবে একটা গোপন সংখ্যা বেছে নেয় — কেউ কাউকে বলে না
alice_private = 6
bob_private = 15

print(f"\nAlice-এর গোপন সংখ্যা a = {alice_private}  (কাউকে পাঠানো হয় না)")
print(f"Bob-এর গোপন সংখ্যা   b = {bob_private}  (কাউকে পাঠানো হয় না)")

# প্রত্যেকে নিজের public value গণনা করে channel-এ পাঠায় (attacker এইটুকু দেখে)
alice_public = pow(g, alice_private, p)   # A = g^a mod p
bob_public = pow(g, bob_private, p)       # B = g^b mod p

print(f"\nAlice পাঠায় (public):  A = g^a mod p = {g}^{alice_private} mod {p} = {alice_public}")
print(f"Bob পাঠায় (public):    B = g^b mod p = {g}^{bob_private} mod {p} = {bob_public}")

# প্রত্যেকে অন্যজনের public value আর নিজের private দিয়ে shared secret বানায়
alice_shared = pow(bob_public, alice_private, p)   # B^a mod p
bob_shared = pow(alice_public, bob_private, p)     # A^b mod p

print(f"\nAlice গণনা করে: B^a mod p = {bob_public}^{alice_private} mod {p} = {alice_shared}")
print(f"Bob গণনা করে:   A^b mod p = {alice_public}^{bob_private} mod {p} = {bob_shared}")

print(f"\nদুইজনের shared secret মেলে কিনা: {alice_shared == bob_shared}")
print(f"shared secret = {alice_shared}")

print("\n--- attacker যা দেখেছে (channel-এ যা গিয়েছে) ---")
print(f"p = {p}, g = {g}, A = {alice_public}, B = {bob_public}")
print("attacker-কে a বা b বের করতে হবে discrete log সমাধান করে — brute force:")
for x in range(p):
    if pow(g, x, p) == alice_public:
        print(f"  brute-force খুঁজে পেল: g^{x} mod {p} == A == {alice_public}  ->  a = {x}")
        break

প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):

সবার জানা (public parameters): p = 23, g = 5

Alice-এর গোপন সংখ্যা a = 6  (কাউকে পাঠানো হয় না)
Bob-এর গোপন সংখ্যা   b = 15  (কাউকে পাঠানো হয় না)

Alice পাঠায় (public):  A = g^a mod p = 5^6 mod 23 = 8
Bob পাঠায় (public):    B = g^b mod p = 5^15 mod 23 = 19

Alice গণনা করে: B^a mod p = 19^6 mod 23 = 2
Bob গণনা করে:   A^b mod p = 8^15 mod 23 = 2

দুইজনের shared secret মেলে কিনা: True
shared secret = 2

--- attacker যা দেখেছে (channel-এ যা গিয়েছে) ---
p = 23, g = 5, A = 8, B = 19
attacker-কে a বা b বের করতে হবে discrete log সমাধান করে — brute force:
  brute-force খুঁজে পেল: g^6 mod 23 == A == 8  ->  a = 6

লক্ষ্য করুন কী গুরুত্বপূর্ণ: p = 23 এত ছোট যে brute-force মুহূর্তেই a = 6 বের করে ফেলল — এই toy example-এ DH আসলে নিরাপদ না, শুধু mechanism দেখানোর জন্য। বাস্তব DH-এ p প্রায় ২০৪৮ বা তার বেশি বিটের — তখন এই একই brute-force loop মহাবিশ্বের বয়সের চেয়ে বেশি সময় নেবে। গণিতটা হুবহু এক, শুধু p-এর আকার বদলায় — আর সেই আকার বদলানোর প্রভাব ঠিক কতটা নাটকীয়, তা “experiment” অংশে সংখ্যায় মাপা হবে।

উদাহরণ

RSA — key generation থেকে round-trip পর্যন্ত, সংখ্যায়

RSA-এর trapdoor DH-এর চেয়ে ভিন্ন গাণিতিক ভিত্তির ওপর — discrete log নয়, বরং বড় সংখ্যা factor করার কঠিনতা। ধারণাটা RSA গ্লসারি এন্ট্রিতে (mathematics module-এ) সংক্ষেপে আছে; এখানে পুরো প্রক্রিয়াটা আসলেই চালিয়ে, প্রতিটা ধাপ দেখা যাক — key generation থেকে encrypt-decrypt round-trip পর্যন্ত।

মূল ধারণা চার ধাপে: (১) দুইটা মৌলিক সংখ্যা p, q বেছে n = p*q বানানো; (২) φ(n) = (p-1)(q-1) গণনা করা — Euler’s totient; (৩) একটা public exponent e বেছে নেওয়া যেন gcd(e, φ(n)) = 1; (৪) d বের করা যেন e*d ≡ 1 (mod φ(n)) — modular inverse। Public key (n, e), private key (n, d)।

# rsa.py — toy RSA: key generation, encrypt, decrypt — সবকিছু ছোট বাস্তব সংখ্যায়
from math import gcd

def modinv(e, phi):
    # extended Euclidean algorithm দিয়ে modular inverse: e*d ≡ 1 (mod phi)
    g, x, _ = extended_gcd(e, phi)
    assert g == 1, "e আর phi coprime না — modular inverse নেই"
    return x % phi

def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    g, x1, y1 = extended_gcd(b % a, a)
    x = y1 - (b // a) * x1
    y = x1
    return g, x, y

# ---------- Key generation ----------
p, q = 17, 11                      # দুইটা ছোট মৌলিক সংখ্যা (toy — বাস্তবে ~1024+ বিট লাগে)
n = p * q
phi = (p - 1) * (q - 1)

print(f"p = {p}, q = {q}")
print(f"n = p*q = {n}")
print(f"phi(n) = (p-1)*(q-1) = {phi}")

e = 7
assert gcd(e, phi) == 1, "e আর phi(n) coprime হতে হবে"
d = modinv(e, phi)

print(f"public exponent  e = {e}   (gcd(e, phi) = {gcd(e, phi)})")
print(f"private exponent d = {d}   (e*d mod phi = {(e * d) % phi})")

print(f"\npublic key  (n, e) = ({n}, {e})  -- এটাই সবাইকে দেওয়া যায়")
print(f"private key (n, d) = ({n}, {d})  -- এটা গোপন রাখতে হবে")

# ---------- Encrypt / Decrypt ----------
message = 88
assert message < n, "message n-এর চেয়ে ছোট হতে হবে"

ciphertext = pow(message, e, n)      # c = m^e mod n  -- যে কেউ public key দিয়ে করতে পারে
recovered = pow(ciphertext, d, n)    # m = c^d mod n  -- শুধু private key-হোল্ডার পারে

print(f"\nplaintext message  m = {message}")
print(f"encrypt: c = m^e mod n = {message}^{e} mod {n} = {ciphertext}")
print(f"decrypt: m = c^d mod n = {ciphertext}^{d} mod {n} = {recovered}")
print(f"round-trip সফল (m == recovered): {message == recovered}")

# ---------- একাধিক ভিন্ন message দিয়ে যাচাই ----------
print("\n--- আরও কিছু message দিয়ে round-trip যাচাই ---")
for m in [2, 42, 100, 150]:
    c = pow(m, e, n)
    r = pow(c, d, n)
    print(f"  m={m:3d} -> c={c:3d} -> decrypt={r:3d}  ok={m == r}")

# ---------- attacker-এর দৃষ্টিকোণ: শুধু (n, e) আর c দেখে ----------
print(f"\nattacker যা দেখে: public key (n={n}, e={e}), ciphertext c={ciphertext}")
print("attacker-কে d বের করতে n = p*q factor করতে হবে -- ছোট n বলে brute-force সম্ভব:")
for cand_p in range(2, n):
    if n % cand_p == 0:
        cand_q = n // cand_p
        print(f"  factor খুঁজে পেল: n = {cand_p} * {cand_q}")
        break

প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):

p = 17, q = 11
n = p*q = 187
phi(n) = (p-1)*(q-1) = 160
public exponent  e = 7   (gcd(e, phi) = 1)
private exponent d = 23   (e*d mod phi = 1)

public key  (n, e) = (187, 7)  -- এটাই সবাইকে দেওয়া যায়
private key (n, d) = (187, 23)  -- এটা গোপন রাখতে হবে

plaintext message  m = 88
encrypt: c = m^e mod n = 88^7 mod 187 = 11
decrypt: m = c^d mod n = 11^23 mod 187 = 88
round-trip সফল (m == recovered): True

--- আরও কিছু message দিয়ে round-trip যাচাই ---
  m=  2 -> c=128 -> decrypt=  2  ok=True
  m= 42 -> c= 15 -> decrypt= 42  ok=True
  m=100 -> c=144 -> decrypt=100  ok=True
  m=150 -> c=  6 -> decrypt=150  ok=True

attacker যা দেখে: public key (n=187, e=7), ciphertext c=11
attacker-কে d বের করতে n = p*q factor করতে হবে -- ছোট n বলে brute-force সম্ভব:
  factor খুঁজে পেল: n = 11 * 17

e*d mod phi = 1 — এই একটা লাইনই RSA-এর পুরো গাণিতিক গোপন কথা। Euler’s theorem বলে m^φ(n) ≡ 1 (mod n) (যখন m আর n coprime), আর যেহেতু e*d = 1 + k*φ(n) কোনো পূর্ণসংখ্যা k-এর জন্য, তাই:

c^d = (m^e)^d = m^(ed) = m^(1 + k*φ(n)) = m * (m^φ(n))^k ≡ m * 1^k ≡ m  (mod n)

encrypt আর decrypt তাই একই অপারেশনের (modular exponentiation) দুইটা ভিন্ন exponent দিয়ে প্রয়োগ — e দিয়ে “লক”, d দিয়ে “আনলক”, আর গণিত নিশ্চিত করে দুইটা একে অপরকে বাতিল করে দেয়। attacker-এর জন্য একমাত্র পথ: n factor করে φ(n) বের করা, তারপর d গণনা করা — আর factoring, n যথেষ্ট বড় হলে (কয়েকশো digit), কোনো known efficient classical algorithm নেই।

ছবিতে

Alice (a গোপন)                   প্রকাশ্য চ্যানেল (Eve শুনছে)              Bob (b গোপন)
                                   p, g  (সবার জানা)
A = g^a mod p  ─────────────────────────► A ─────────────────────────►
                    ◄───────────────────── B ◄───────────────────── B = g^b mod p

shared = B^a mod p                                              shared = A^b mod p
       = g^(ba) mod p                                                  = g^(ab) mod p
       ═══════════════════════ একই সংখ্যা (commutativity) ═══════════════════════

Eve দেখেছে: p, g, A, B          Eve দেখেনি: a, b, shared          Eve করতে পারেনি: discrete log
Diffie-Hellman — Alice আর Bob প্রকাশ্য চ্যানেলে শুধু A ও B পাঠায়; shared secret কখনো channel-এ যায় না, শুধু commutativity-র কারণে দুইজনেই আলাদাভাবে গণনা করে একই সংখ্যায় পৌঁছায়।
                          Bob-এর key-pair: public (n, e)   private (n, d)
                                              │                    │
Alice (Bob-এর public key জানে)               ▼                    │
   m ──[ c = m^e mod n ]──► c ──(প্রকাশ্য চ্যানেলে পাঠায়)──► c        │
                                                                     ▼
                                                    Bob: m = c^d mod n ──► m ফেরত

যে কেউ (n, e) জেনে encrypt করতে পারে — কিন্তু decrypt করতে d লাগে,
আর d বের করতে n = p*q factor করা লাগে — n যথেষ্ট বড় হলে infeasible।
RSA — public key দিয়ে যে কেউ লক করতে পারে, শুধু private-key-হোল্ডার আনলক করতে পারে; দুইটা অপারেশনই একই modular exponentiation, শুধু exponent ভিন্ন (e বনাম d)।

নিজে চালিয়ে দেখুন

EXPERIMENT

Forward বনাম backward — সংখ্যায়: trapdoor asymmetry হাতে গোনা

macOS / Linux / Windows (Python 3)· ১৫ মিনিট
# trapdoor.py — forward (fast modular exponentiation) বনাম backward (discrete log, brute-force)
# সময় নয়, অপারেশন-সংখ্যা গুনে তুলনা — deterministic, মেশিন-নির্বিশেষে একই ফল
def modpow_counted(base, exp, mod):
    # square-and-multiply: প্রতিটা multiplication গোনা হচ্ছে
    result, b, e, mults = 1, base % mod, exp, 0
    while e > 0:
        if e & 1:
            result = (result * b) % mod
            mults += 1
        b = (b * b) % mod
        mults += 1
        e >>= 1
    return result, mults

def discrete_log_bruteforce(g, y, p):
    # forward-এর মতো shortcut নেই — একটা একটা করে চেষ্টা করা ছাড়া উপায় নেই
    tries = 0
    for cand in range(p):
        tries += 1
        if pow(g, cand, p) == y:
            return cand, tries

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def find_prime_near(n):
    x = n
    while not is_prime(x):
        x += 1
    return x

g = 5
print(f"{'p':>9} {'bits':>5} | {'x (secret)':>11} | {'forward: multiplications':>26} | {'backward: candidates tried':>27}")
print("-" * 92)

for k in (8, 12, 16, 20):
    p = find_prime_near(2 ** k)
    bits = p.bit_length()
    x = p // 3

    y, fwd_mults = modpow_counted(g, x, p)
    found_x, bwd_tries = discrete_log_bruteforce(g, y, p)
    assert found_x == x

    print(f"{p:>9} {bits:>5} | {x:>11} | {fwd_mults:>26} | {bwd_tries:>27,}")

print("\nফলাফল: forward-এর multiplication-সংখ্যা p-এর সাথে **লগারিদমিকভাবে** বাড়ে")
print("(square-and-multiply — প্রতি বিটে সর্বোচ্চ ২টা multiplication)।")
print("backward-এর candidate-সংখ্যা p-এর সাথে প্রায় **সরলরৈখিকভাবে** বাড়ে —")
print("p প্রতি ~16 গুণ বড় হলে (৮ বিট বাড়লে) backward-এর কাজ প্রায় ~16 গুণ বাড়ে,")
print("অথচ forward-এর কাজ মাত্র কয়েকটা multiplication বাড়ে। এই asymmetry-ই")
print("discrete-log-based trapdoor-এর ভিত্তি — বাস্তব DH-এ p প্রায় ২০৪৮-বিট,")
print("forward তখনও কয়েক-হাজার multiplication, backward সেরা-জানা algorithm")
print("(general number field sieve family) দিয়েও সাব-এক্সপোনেনশিয়াল কিন্তু বিশাল।")

প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):

        p  bits |  x (secret) |   forward: multiplications |  backward: candidates tried
--------------------------------------------------------------------------------------------
      257     9 |          85 |                         11 |                          86
     4099    13 |        1366 |                         17 |                       1,367
    65537    17 |       21845 |                         23 |                      21,846
  1048583    21 |      349527 |                         30 |                     349,528

ফলাফল: forward-এর multiplication-সংখ্যা p-এর সাথে **লগারিদমিকভাবে** বাড়ে
(square-and-multiply — প্রতি বিটে সর্বোচ্চ ২টা multiplication)।
backward-এর candidate-সংখ্যা p-এর সাথে প্রায় **সরলরৈখিকভাবে** বাড়ে —
p প্রতি ~16 গুণ বড় হলে (৮ বিট বাড়লে) backward-এর কাজ প্রায় ~16 গুণ বাড়ে,
অথচ forward-এর কাজ মাত্র কয়েকটা multiplication বাড়ে। এই asymmetry-ই
discrete-log-based trapdoor-এর ভিত্তি — বাস্তব DH-এ p প্রায় ২০৪৮-বিট,
forward তখনও কয়েক-হাজার multiplication, backward সেরা-জানা algorithm
(general number field sieve family) দিয়েও সাব-এক্সপোনেনশিয়াল কিন্তু বিশাল।

সংখ্যাগুলো নিজেই থিসিসটা বলে দেয়: p মাত্র 21-বিটে পৌঁছাতেই forward মাত্র 30টা multiplication লাগল, backward-এর candidate-সংখ্যা 349,528 — প্রায় ১১,৬৫০ গুণ বেশি কাজ। বাস্তব DH-এ p ২০৪৮-বিট (এখানকার toy p-এর চেয়ে ~১০০ গুণ বড়) — forward তখনও কয়েক হাজার multiplication-এ থাকে, কিন্তু backward-এর brute-force loop 2^2048-এর কাছাকাছি, যা মহাবিশ্বে থাকা পরমাণুর সংখ্যার চেয়েও বেশি। সেরা known algorithm (general number field sieve) এই ব্যবধান কমায় কিন্তু মুছে দেয় না — তাই ২০৪৮-বিট (বা তার বেশি) DH এখনও নিরাপদ ধরা হয়।

এটা কী প্রমাণ করে

একই discrete-log গণিতে forward (exponentiation) দিক p-এর বিট-সংখ্যায় লগারিদমিকভাবে বাড়ে, backward (discrete log, brute-force) দিক p-এর সাথে প্রায় সরলরৈখিকভাবে — মাত্র কয়েক বিট বড় p-তেই এই দুই খরচের অনুপাত হাজার-গুণ ছাড়িয়ে যায়, আর এই asymmetry-ই trapdoor function-এর ব্যবহারিক ভিত্তি।

নিজে বানান

BUILD IT

Hybrid encryption — RSA দিয়ে শুধু চাবি wrap, AES-স্টাইল cipher দিয়ে আসল payload

Python · ●●●●○
  1. Bob-এর জন্য একটা বড় toy RSA key-pair বানান (আগেরটার চেয়ে বড় prime, যাতে session-key-এর জায়গা থাকে n-এ)
  2. Alice একটা random session key বানাবে — একটা সংখ্যা হিসেবে, যেটা n-এর চেয়ে ছোট থাকবে
  3. সেই session key-টাই RSA দিয়ে wrap করুন (encrypt) — পুরো payload না, শুধু key
  4. Bob নিজের private key দিয়ে session key ফেরত পাবে, তারপর সেই key দিয়ে toy CTR-স্টাইল cipher দিয়ে bulk payload en/decrypt করুন (আগের লেসনের প্যাটার্ন পুনর্ব্যবহার করে)
  5. দেখান কেন payload সরাসরি RSA-তে যায় না — payload-কে সংখ্যা বানালে তার bit-length RSA modulus n-এর bit-length ছাড়িয়ে যায়

লক্ষ্য: RSA আর AES-স্টাইল symmetric cipher-কে একসাথে ব্যবহার করা — প্রতিটা নিজের সবচেয়ে ভালো কাজে। RSA স্লো, আর একটা নির্দিষ্ট maximum message-size-এ সীমাবদ্ধ (modulus n-এর বিট-সংখ্যার নিচে); symmetric cipher দ্রুত এবং যেকোনো length-এ কাজ করে। বাস্তব TLS, PGP, S/MIME — সবাই এই একই প্যাটার্ন অনুসরণ করে।

# hybrid.py — Hybrid encryption: toy RSA শুধু session key wrap করতে, bulk data-তে toy symmetric cipher
import hashlib
from math import gcd

def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    g, x1, y1 = extended_gcd(b % a, a)
    return g, y1 - (b // a) * x1, x1

def modinv(e, phi):
    g, x, _ = extended_gcd(e, phi)
    assert g == 1
    return x % phi

# ---------- Bob-এর toy RSA keypair (আগের অংশের মতোই ছোট, শুধু demo-র জন্য) ----------
p, q = 61, 53                      # আগের চেয়ে বড় যাতে session-key-এর জায়গা থাকে n-এ
n = p * q
phi = (p - 1) * (q - 1)
e = 17
d = modinv(e, phi)
print(f"Bob-এর public key  (n, e) = ({n}, {e})")
print(f"Bob-এর private key (n, d) = ({n}, {d})  -- গোপন")

# ---------- ধাপ ১: Alice একটা random session key বানায় (n-এর চেয়ে ছোট হতে হবে RSA-wrap-এর জন্য) ----------
import random
random.seed(42)
session_key_int = random.randrange(1000, n)   # toy: বাস্তবে os.urandom(32)-ধাঁচ, তারপর n-এর নিচে map করা লাগত
print(f"\nAlice-এর random session key (সংখ্যা হিসেবে) = {session_key_int}")

# ---------- ধাপ ২: Alice এই session key-টাই RSA দিয়ে wrap করে, পুরো payload না ----------
wrapped_key = pow(session_key_int, e, n)     # শুধু session key encrypt হচ্ছে, payload না
print(f"RSA-wrapped session key = {wrapped_key}   (এটাই channel দিয়ে পাঠানো হয়)")

# ---------- ধাপ ৩: Bob নিজের private key দিয়ে session key ফেরত পায় ----------
recovered_key_int = pow(wrapped_key, d, n)
print(f"Bob RSA-unwrap করে ফেরত পেল session key = {recovered_key_int}")
print(f"session key মিলল: {recovered_key_int == session_key_int}")

# ---------- ধাপ ৪: session key দিয়ে toy symmetric (CTR-স্টাইল keystream) দিয়ে bulk payload encrypt ----------
def derive_sym_key(key_int):
    return hashlib.sha256(str(key_int).encode()).digest()

def keystream_block(key, counter):
    return hashlib.sha256(key + counter.to_bytes(4, "big")).digest()

def ctr_crypt(key, data):
    counter = 0
    ks = b""
    while len(ks) < len(data):
        ks += keystream_block(key, counter)
        counter += 1
    return bytes(a ^ b for a, b in zip(data, ks))

sym_key = derive_sym_key(session_key_int)
payload = ("Subject: Q3 report\n"
           "এই পুরো ইমেইলটা bulk payload — কয়েক KB, কয়েক MB হতে পারে। "
           "RSA দিয়ে সরাসরি এটা encrypt করা বাস্তবসম্মত না, "
           "কিন্তু AES-এর মতো symmetric cipher-এর জন্য এটা তুচ্ছ কাজ।").encode("utf-8")
print(f"\nbulk payload length = {len(payload)} bytes")

ciphertext = ctr_crypt(sym_key, payload)
decrypted = ctr_crypt(sym_key, ciphertext)   # CTR: encrypt আর decrypt একই অপারেশন
print(f"ciphertext (প্রথম ৩২ byte hex) = {ciphertext[:32].hex()}")
print(f"decrypt সফল (round-trip): {decrypted == payload}")

# ---------- কেন সরাসরি RSA-তে পুরো payload যায় না, দেখানো ----------
print("\n--- কেন RSA সরাসরি bulk payload-এ ব্যবহার হয় না ---")
oversized_block = int.from_bytes(payload[:64], "big")
print(f"payload-এর প্রথম অংশকে সংখ্যা বানালে (৬৪ byte) = একটা বিশাল integer,")
print(f"যার bit-length = {oversized_block.bit_length()} বিট, কিন্তু n-এর bit-length মাত্র {n.bit_length()} বিট।")
print(f"m >= n হলে RSA-এর mod n reduction তথ্য হারিয়ে ফেলে (m mod n != m) —")
print(f"তাই RSA-block-size (n-এর বিট-সংখ্যা) থেকে বড় কোনো ডেটা সরাসরি এক-ধাপে RSA-encrypt করা যায় না।")
print(f"এই জন্যই RSA শুধু session-key wrap-এ ব্যবহার হয় — key সবসময় n-এর চেয়ে ছোট রাখা যায়,")
print(f"bulk payload যত বড়ই হোক, session key-র সাইজ স্থির থাকে।")

প্রত্যাশিত output (deterministic — fixed seed, হুবহু এটাই আসবে):

Bob-এর public key  (n, e) = (3233, 17)
Bob-এর private key (n, d) = (3233, 2753)  -- গোপন

Alice-এর random session key (সংখ্যা হিসেবে) = 1456
RSA-wrapped session key = 2900   (এটাই channel দিয়ে পাঠানো হয়)
Bob RSA-unwrap করে ফেরত পেল session key = 1456
session key মিলল: True

bulk payload length = 367 bytes
ciphertext (প্রথম ৩২ byte hex) = 98652308d778027bf9d7c9e1e77ae72587094cc5737f9734c788adc355eb1012
decrypt সফল (round-trip): True

--- কেন RSA সরাসরি bulk payload-এ ব্যবহার হয় না ---
payload-এর প্রথম অংশকে সংখ্যা বানালে (৬৪ byte) = একটা বিশাল integer,
যার bit-length = 511 বিট, কিন্তু n-এর bit-length মাত্র 12 বিট।
m >= n হলে RSA-এর mod n reduction তথ্য হারিয়ে ফেলে (m mod n != m) —
তাই RSA-block-size (n-এর বিট-সংখ্যা) থেকে বড় কোনো ডেটা সরাসরি এক-ধাপে RSA-encrypt করা যায় না।
এই জন্যই RSA শুধু session-key wrap-এ ব্যবহার হয় — key সবসময় n-এর চেয়ে ছোট রাখা যায়,
bulk payload যত বড়ই হোক, session key-র সাইজ স্থির থাকে।

মূল মুহূর্ত দুইটা জায়গায়: প্রথম, session key মিলল: True — Bob কখনো session_key_int channel দিয়ে দেখেনি, শুধু wrapped_key দেখেছে, তবু নিজের private key দিয়ে হুবহু ফেরত পেয়েছে। দ্বিতীয়, bit-length তুলনা — 367 byte-এর payload-এর একটা ছোট অংশও (64 byte) 511-বিটের সংখ্যা হয়ে যায়, যেখানে toy RSA-এর n মাত্র 12-বিট — বাস্তব ২০৪৮-বিট RSA-তেও এই অনুপাত একই থিমে থাকে: RSA modulus একটা fixed-size সীমা, payload তা সহজেই ছাড়িয়ে যায়। এই জন্যই hybrid approach — RSA শুধু session key wrap করে, key-র সাইজ সবসময় modulus-এর নিচে রাখা যায়, payload যতই বড় হোক সমস্যা হয় না।

নিজে বাড়ান:

  1. session_key_int যদি n-এর চেয়ে বড় বা সমান হয়ে যায়, কী ঘটবে দেখুন — কেন এটা একটা silent bug হতে পারে, আর বাস্তব library এটা কীভাবে এড়ায় (হিন্ট: key সবসময় fixed, ছোট byte-length-এ generate করা হয়)
  2. RSA encrypt-এর গতি বনাম CTR-cipher-এর গতি তুলনা করুন — %timeit বা time.perf_counter() দিয়ে একই payload-এর জন্য দুইটা approach-এর সময় মাপুন (RSA সরাসরি byte-by-byte encrypt করলে কল্পনা করুন)
  3. Diffie-Hellman দিয়ে একই hybrid pattern রিরাইট করুন — RSA-wrap-এর বদলে DH shared-secret থেকে সরাসরি session key derive করুন; কোন পার্থক্যটা লক্ষ্যণীয় (হিন্ট: RSA-তে Alice key বানায় ও পাঠায়, DH-এ দুইজনেই স্বাধীনভাবে derive করে)

বাস্তব সিস্টেমে

বাস্তব জগতে Diffie-Hellman, RSA, ও ECC

TLS 1.3 handshake। আধুনিক HTTPS connection শুরু হয় একটা (elliptic-curve) Diffie-Hellman exchange দিয়ে — ECDHE (Ephemeral ECDH) — যা প্রতিটা নতুন connection-এ একটা fresh session key স্থাপন করে। RSA বা ECDSA তখনও লাগে, কিন্তু ভিন্ন কাজে: server-এর certificate সাইন করতে (পরের লেসনের বিষয়), key exchange-এ নয়। TLS 1.3 স্পেসিফিকেশন non-ephemeral (static) RSA key exchange পুরোপুরি বাদ দিয়েছে, কারণ ephemeral DH দেয় forward secrecy — একটা session-এর key ফাঁস হলেও আগের বা পরের session-গুলো নিরাপদ থাকে, কারণ প্রতিটা session-এর জন্য নতুন ephemeral key-pair তৈরি হয়।

PGP ও S/MIME email encryption। দুইটাই আজকের build অংশের pattern সরাসরি বাস্তবায়ন করে — একটা random session key generate, RSA (বা ECC-variant) দিয়ে recipient-এর public key দিয়ে সেই session key wrap, তারপর AES দিয়ে আসল email body encrypt। Email attachment কয়েক MB হতে পারে — RSA দিয়ে সরাসরি সেটা encrypt করলে হাজার গুণ ধীর হতো।

SSH। Connection স্থাপনের সময় একটা DH (বা curve25519-sha256) key-exchange চলে, session key স্থাপন হয়, তারপর সব ট্রাফিক symmetric cipher (AES-GCM বা ChaCha20-Poly1305) দিয়ে যায় — ঠিক TLS-এর মতো একই hybrid নীতি।

ECC — একই trapdoor ধারণা, অনেক ছোট key

Elliptic Curve Cryptography (ECC) একই মৌলিক ধারণা ব্যবহার করে — forward দিকে সহজ, backward দিকে কঠিন (elliptic curve discrete logarithm problem, ECDLP) — কিন্তু একটা ভিন্ন গাণিতিক structure-এ (একটা curve-এর ওপর বিন্দুর addition, integer modular exponentiation না)। আজকের লেসনে সেই গণিত derive করার দরকার নেই — গুরুত্বপূর্ণ শুধু ফলাফল:

Security level (সমতুল্য brute-force খরচ)RSA / DH key sizeECC key size
~৮০-বিট (পুরনো, আজ অপর্যাপ্ত)১০২৪-বিট১৬০-বিট
~১২৮-বিট (আজকের সাধারণ default)৩০৭২-বিট২৫৬-বিট
~১৯২-বিট৭৬৮০-বিট৩৮৪-বিট

একই নিরাপত্তা স্তরে ECC key RSA/DH key-এর প্রায় এক-দশমাংশ বা তার কম আকারের — কারণ ECDLP-এর সেরা known attack RSA-এর factoring বা classic DLP-এর সেরা attack-এর চেয়ে proportionally বেশি কঠিন, তাই একই নিরাপত্তা পেতে কম বিট লাগে। ছোট key মানে সরাসরি ব্যবহারিক সুবিধা: কম network overhead (TLS handshake দ্রুত), কম CPU (mobile/IoT device-এ গুরুত্বপূর্ণ), ছোট certificate। এই জন্যই Curve25519/P-256-জাতীয় ECC curve আজ TLS, SSH, Signal — প্রায় সব আধুনিক protocol-এ RSA/classic-DH-এর তুলনায় default।

যে ভুলগুলো সবাই করে

“Asymmetric encryption 'বেশি secure', তাই bulk data-তেও এটা সবসময় ব্যবহার করা উচিত, symmetric-এর দরকার নেই।”

“বেশি secure” আর “বেশি ব্যবহারযোগ্য” এক কথা না। Build অংশেই দেখা গেছে: RSA-এর modulus n-এর একটা fixed bit-length সীমা আছে — payload তার চেয়ে বড় হলে সরাসরি এক-ধাপে encrypt করা যায় না (m >= n হলে mod-reduction তথ্য হারায়)। এছাড়া modular exponentiation symmetric cipher-এর XOR-ভিত্তিক keystream অপারেশনের চেয়ে বহুগুণ ধীর। তাই বাস্তব system asymmetric crypto ব্যবহার করে শুধু session-key স্থাপনে — bulk data-র জন্য এখনও symmetric cipher (AES/ChaCha20) লাগে।

“Diffie-Hellman একটা encryption scheme — এটা দিয়েও RSA-র মতো ciphertext পাঠানো যায়।”

DH কোনো message encrypt করে না — এটা শুধু একটা key-agreement protocol। dh.py-এর output লক্ষ্য করুন: channel দিয়ে শুধু p, g, A, B গিয়েছে — কোনো encrypted message নয়। Alice আর Bob শুধু একটা shared secret-এ পৌঁছেছে, যেটা পরে একটা symmetric cipher-এর key হিসেবে ব্যবহার হয়। DH নিজে কোনো ciphertext তৈরি বা পাঠায় না — RSA-র সাথে এই পার্থক্যটাই সবচেয়ে বেশি গুলিয়ে যায়।

“Diffie-Hellman নিরাপদ মানে man-in-the-middle attack থেকেও নিরাপদ।”

DH শুধু passive eavesdropper (Eve শুধু শোনে) থেকে রক্ষা করে — discrete log ভাঙা কঠিন বলে সে shared secret বের করতে পারে না। কিন্তু একটা active attacker যদি চ্যানেলের মাঝখানে বসে Alice-এর সাথে একটা আলাদা DH exchange চালায় আর Bob-এর সাথে আরেকটা (দুইজনের কেউই জানে না তারা আসলে attacker-এর সাথে কথা বলছে), সে দুইটা আলাদা shared secret স্থাপন করে ফেলবে — DH নিজে এটা ধরতে পারে না, কারণ DH কোনো পক্ষের পরিচয় যাচাই করে না, শুধু একটা secret স্থাপন করে। পরিচয় যাচাইয়ের জন্য signature আর certificate লাগে — পরের লেসনের বিষয়।

“RSA-তে বড় সংখ্যা factor করা 'গাণিতিকভাবে প্রমাণিত অসম্ভব', তাই RSA চিরকাল নিরাপদ থাকবে।”

কোনো প্রমাণ নেই যে factoring efficiently করা classical computer-এ impossible — শুধু এই মুহূর্তে কোনো known efficient algorithm নেই। আরও গুরুত্বপূর্ণ: ১৯৯৪ সালে Peter Shor দেখান, একটা যথেষ্ট শক্তিশালী quantum computer polynomial সময়ে বড় সংখ্যা factor করতে পারে (Shor’s algorithm) — RSA আর discrete-log-ভিত্তিক DH দুইটাই একইভাবে ঝুঁকিতে। এই কারণেই post-quantum cryptography নিয়ে সক্রিয় গবেষণা চলছে — RSA/DH/ECC তিনটাই একই ধরনের গাণিতিক সমস্যার (factoring, discrete log) ওপর দাঁড়িয়ে বলে quantum computer আসলে তিনটাই একসাথে ঝুঁকিতে পড়বে।

বুঝেছেন কি না দেখুন

1

Asymmetric encryption-এ দুইটা key থাকে — প্রতিটার কাজ কী, আর সেগুলোর মধ্যে কোনটা গোপন রাখতে হয়?

স্মরণ

Public key দিয়ে শুধু encrypt (lock) করা যায় — এটা প্রকাশ্যে দেওয়া যায়, কারও কাছে থাকলে কোনো ক্ষতি নেই। Private key দিয়ে শুধু decrypt (unlock) করা যায় — এটাই গোপন রাখতে হয়, কারণ শুধু এটা জানলেই ciphertext থেকে আসল plaintext ফেরত পাওয়া যায়।

2

dh.py-এর output-এ Eve p, g, A, B — চারটা সংখ্যাই দেখেছে, তবু (বড় p-তে) shared secret বের করতে পারে না। ঠিক কোন গাণিতিক ধাপটা তাকে আটকায়?

যুক্তি

Shared secret বের করতে Eve-কে হয় A = g^a mod p থেকে a, অথবা B = g^b mod p থেকে b বের করতে হবে — এটাই discrete logarithm problem। Forward দিক (exponentiation) দ্রুত, কিন্তু backward দিক (discrete log) কোনো known efficient classical algorithm নেই — p যথেষ্ট বড় হলে এই ধাপটাই ব্যবহারিকভাবে infeasible, যদিও Eve বাকি সব সংখ্যা (public value) দেখেছে।

3

rsa.py-তে n = 187 এত ছোট যে attacker সরাসরি factor করে p=11, q=17 বের করে ফেলল। যদি n কয়েকশো digit-এর হতো (বাস্তব RSA-এর মতো), attacker-এর কৌশল কী হতো এবং কেন কঠিন হয়ে যেত?

প্রয়োগ

কৌশল একই থাকত — n factor করে p, q বের করা, তারপর φ(n) আর d গণনা করা — কিন্তু বড় n-এ trial-division (rsa.py-এর মতো একটা একটা candidate চেষ্টা করা) ব্যবহারিকভাবে অসম্ভব হয়ে যেত। সেরা known factoring algorithm (general number field sieve) ব্যবহার করলেও, ২০৪৮+ বিট n-এর জন্য এই কাজ classical computer-এ কয়েক দশক থেকে মহাবিশ্বের বয়সের বেশি সময় নিতে পারে — trapdoor asymmetry-র বাস্তব প্রমাণ।

4

trapdoor.py-এর experiment-এ forward-এর multiplication-সংখ্যা p-এর সাথে লগারিদমিকভাবে বাড়ে, কিন্তু backward-এর candidate-সংখ্যা প্রায় সরলরৈখিকভাবে। এই দুই ভিন্ন growth-rate-এর মূল কারণ কী?

যুক্তি

Forward দিক square-and-multiply কৌশল ব্যবহার করে — exponent-এর প্রতিটা বিটে সর্বোচ্চ দুইটা multiplication, তাই মোট কাজ exponent-এর বিট-সংখ্যার (অর্থাৎ log(p)-এর) সমানুপাতিক। Backward দিকে (brute-force discrete log) এমন কোনো shortcut নেই — প্রতিটা সম্ভাব্য candidate আলাদাভাবে চেষ্টা করতে হয়, তাই কাজ সরাসরি p-এর (সংখ্যার আকারের, বিট-সংখ্যার না) সমানুপাতিক — logarithmic বনাম linear-এর এই পার্থক্যই p বাড়ার সাথে সাথে দ্রুত exponentially divergent হয়ে যায়।

5

hybrid.py-তে কেন RSA দিয়ে শুধু session key wrap করা হলো, পুরো email payload না — output-এর কোন সংখ্যা দুইটা সরাসরি এই সিদ্ধান্তের কারণ ব্যাখ্যা করে?

প্রয়োগ

Output দেখায় payload-এর একটা ছোট (৬৪-byte) অংশও সংখ্যা হিসেবে 511-বিট হয়ে যায়, অথচ toy RSA-এর n-এর bit-length মাত্র 12-বিট — payload RSA-এর modulus-কে বহুগুণ ছাড়িয়ে যায়, তাই সরাসরি এক-ধাপে encrypt করা গাণিতিকভাবেই সম্ভব না (m >= n হলে mod-reduction তথ্য হারায়)। Session key ছোট, fixed-size রাখা যায় (n-এর নিচে), তাই সেটাই RSA-wrap করা হয়েছে — payload যত বড়ই হোক, key-র সাইজ বদলায় না।

6

একটা নতুন secure-messaging app ডিজাইন করছেন যেখানে দুইজন ব্যবহারকারী প্রথমবার চ্যাট শুরু করছেন, আগে কখনো দেখা করেননি। Session key স্থাপনে Diffie-Hellman না RSA বাছবেন, আর কেন? Forward secrecy বিবেচনা করে যুক্তি দিন, আর কোন সমস্যাটা এখনো অমীমাংসিত থেকে যায় যা পরের লেসনে সমাধান হবে।

ডিজাইন

Ephemeral Diffie-Hellman (বা ECDHE) বাছা উচিত, RSA-encryption-based key exchange না — প্রতিটা নতুন session-এ fresh ephemeral key-pair generate করলে forward secrecy পাওয়া যায়: কোনো একটা session-এর key পরে ফাঁস হলেও আগের বা ভবিষ্যতের session-এর message নিরাপদ থাকে, কারণ প্রতিটা session-এর shared secret আলাদাভাবে derive হয়। যেটা এখনো অমীমাংসিত: DH নিজে কোনো পক্ষের পরিচয় যাচাই করে না — misconception অংশেই দেখা গেছে man-in-the-middle এই ফাঁক কাজে লাগাতে পারে। তাই দরকার একটা উপায় প্রমাণ করার যে “এই public value সত্যিই সেই ব্যক্তির কাছ থেকে এসেছে যার সাথে কথা বলতে চাই” — সেটাই পরের লেসনের বিষয়: digital signature আর PKI।

এরপর কী

এরপর কী

আজ দেখলাম কীভাবে দুইজন অচেনা মানুষ — Diffie-Hellman দিয়ে বা RSA দিয়ে — একটা প্রকাশ্য চ্যানেলে কথা বলেই একটা গোপন কিছুতে একমত হতে পারে, আর কেন বাস্তব system asymmetric আর symmetric crypto দুইটাকেই hybrid encryption-এ একসাথে ব্যবহার করে। এখন আমরা encrypt করতে পারি যেন শুধু উদ্দিষ্ট প্রাপক পড়তে পারে। কিন্তু একটা প্রশ্ন পুরো লেসন জুড়েই misconception-এ বারবার ফিরে এসেছে, উত্তর ছাড়াই: প্রাপক কীভাবে জানবে কে পাঠাল, আর বার্তাটা পথে বদলায়নি তা কীভাবে প্রমাণ হবে?

পরের লেসন — digital signatures ও PKI — ঠিক এই ফাঁকটা পূরণ করে, আর চমকপ্রদভাবে, আজকের একই গণিত দিয়েই। RSA-তে d (private) দিয়ে “sign” আর e (public) দিয়ে “verify” — encrypt/decrypt-এর ঠিক উল্টো দিকে ব্যবহার করে। দেখব কীভাবে এই flip একটা সম্পূর্ণ নতুন গ্যারান্টি দেয় — authentication আর integrity — আর কীভাবে certificate authority-র মতো প্রতিষ্ঠান এই স্বাক্ষরের একটা chain-of-trust বানিয়ে পুরো internet-এর “এই public key সত্যিই তার, যাকে দাবি করা হচ্ছে” প্রশ্নের উত্তর দেয়।

আরও পড়ুন

  • New Directions in Cryptography — Whitfield Diffie, Martin E. Hellman — IEEE Transactions on Information Theory, vol. 22, no. 6, 1976 · Diffie-Hellman key exchange-এর মূল প্রকাশনা — প্রথম প্রকাশ্য public-key protocol
  • A Method for Obtaining Digital Signatures and Public-Key Cryptosystems — Ronald L. Rivest, Adi Shamir, Leonard M. Adleman — Communications of the ACM, vol. 21, no. 2, 1978 · RSA-এর মূল paper — key generation, encrypt/decrypt, এবং কেন এটা কাজ করে তার আনুষ্ঠানিক প্রমাণ
  • Curve25519: New Diffie-Hellman Speed Records — Daniel J. Bernstein — PKC 2006 · আধুনিক ECC key-exchange-এর ভিত্তি — TLS 1.3-এ default DH curve
  • Recommendation for Pair-Wise Key-Establishment Schemes Using Discrete Logarithm Cryptography — SP 800-56A Rev. 3 — National Institute of Standards and Technology (NIST) · DH/ECDH-এর আনুষ্ঠানিক সরকারি সুপারিশ — key-size guideline-সহ