คลัง
crypto

Vigenère Cipher

Vigenère เป็นรหัสแทนที่แบบหลายตัวอักษร (polyalphabetic) ที่ใช้คำกุญแจซ้ำเพื่อเลื่อนตัวอักษรไม่เท่ากันในแต่ละตำแหน่ง ทำให้ frequency analysis ตรงๆ ใช้ไม่ได้ บทนี้อธิบายการถอดรหัสจริง: หาความยาว key ด้วย Kasiski examination และ Index of Coincidence แล้วแตกเป็น Caesar หลายชุด

BeginnerIntermediate#vigenere#polyalphabetic#kasiski#ic#crypto#classical#ctf

1. หลักการทำงาน

Vigenère คือ Caesar หลายตัวต่อกัน โดยใช้คำกุญแจ (keyword) มากำหนด shift ของแต่ละตำแหน่ง เช่น key = KEY หมายถึง ตัวอักษรตำแหน่งที่ 1 เลื่อนด้วย K(=10), ตำแหน่ง 2 เลื่อนด้วย E(=4), ตำแหน่ง 3 เลื่อนด้วย Y(=24) แล้ววนซ้ำ key ไปเรื่อยๆ จุดแข็งเหนือ Caesar คือตัวอักษรเดียวกันใน plaintext อาจกลายเป็นคนละตัวใน ciphertext ทำให้ frequency analysis แบบ Caesar ใช้ไม่ได้ตรงๆ

ตำแหน่งplaintextkeyshiftciphertext
1H (7)K (10)+10R (17)
2E (4)E (4)+4I (8)
3L (11)Y (24)+24J (9)
4L (11)K (10)+10V (21)

สูตร: C_i = (P_i + K_(i mod m)) mod 26 และถอดด้วย P_i = (C_i − K_(i mod m)) mod 26 โดย m คือความยาว key — การโจมตีทั้งหมดเริ่มจากการหา m ให้ได้ก่อน

2. สังเกตอย่างไร

  • ข้อความเป็นตัวอักษรล้วน ความยาวคงที่ แต่ frequency แบนกว่าภาษาอังกฤษปกติ (ไม่มีตัวไหนเด่นชัดเหมือน E ใน Caesar)
  • ลอง Caesar brute 25 แบบแล้วไม่มีอันไหนอ่านออก — บ่งชี้ polyalphabetic
  • โจทย์ใบ้คำว่า key, keyword, passphrase, Vigenère, polyalphabetic
  • Index of Coincidence ของข้อความต่ำกว่า ~0.067 (ค่าภาษาอังกฤษ) แต่สูงกว่าข้อความสุ่ม (~0.038)

3. หาความยาว key — Kasiski + Index of Coincidence

Kasiski examination: หาลำดับตัวอักษรที่ซ้ำกันใน ciphertext (เช่น 3 ตัวขึ้นไป) แล้ววัดระยะห่างระหว่างการซ้ำ ความยาว key มักเป็นตัวประกอบร่วม (GCD) ของระยะห่างเหล่านั้น เพราะคำเดียวกันที่ตรงกับตำแหน่ง key เดียวกันจะเข้ารหัสออกมาเหมือนกัน

Index of Coincidence (IC): วัดความน่าจะเป็นที่ตัวอักษรสองตัวสุ่มจะเหมือนกัน ภาษาอังกฤษ IC ≈ 0.067, สุ่ม ≈ 0.038 วิธีใช้: แบ่ง ciphertext เป็นกลุ่มตามความยาว key ที่เดา (ทุกตัวที่ห่างกัน m ตำแหน่ง) แล้วคำนวณ IC เฉลี่ย — ความยาว key ที่ถูกต้องจะให้ IC ใกล้ 0.067

หาความยาว key ด้วย Index of Coincidence (Python)
from collections import Counter

def ic(text):
    n = len(text)
    if n < 2: return 0
    counts = Counter(text)
    return sum(c*(c-1) for c in counts.values()) / (n*(n-1))

def avg_ic_for_keylen(ct, m):
    groups = ['']*m
    for i, ch in enumerate(ct):
        groups[i % m] += ch
    return sum(ic(g) for g in groups) / m

ct = "".join(c for c in CIPHERTEXT.upper() if c.isalpha())
for m in range(1, 15):
    print(m, round(avg_ic_for_keylen(ct, m), 4))
# ความยาว key ที่ให้ค่าใกล้ 0.067 ที่สุดคือคำตอบ
ค่าใกล้ 0.067 = ภาษาอังกฤษ; ดูตัวที่กระโดดขึ้นชัดเจนเป็นครั้งแรก

4. กู้ key — แตกเป็น Caesar หลายชุด

เมื่อรู้ความยาว key = m แล้ว ปัญหากลายเป็น Caesar m ชุดอิสระ เพราะตัวอักษรทุกตัวที่ตำแหน่ง i, i+m, i+2m, ... ถูกเลื่อนด้วย key ตัวเดียวกัน จับมันมารวมกลุ่ม แล้วแก้แต่ละกลุ่มแบบ Caesar (frequency analysis หรือ brute 26) จะได้ key ทีละตัว

กู้ key แต่ละตำแหน่งด้วย frequency (Python)
from collections import Counter

ENGLISH_FREQ = "ETAOINSHRDLCUMWFGYPBVKJXQZ"

def best_shift(group):
    best, best_score = 0, -1
    for s in range(26):
        dec = "".join(chr((ord(c)-65-s)%26+65) for c in group)
        # ให้คะแนนจากความถี่ที่ใกล้ภาษาอังกฤษ
        score = sum(dec.count(ch) * (26-i) for i, ch in enumerate(ENGLISH_FREQ))
        if score > best_score:
            best_score, best = score, s
    return best

m = KEY_LENGTH
groups = ['']*m
for i, ch in enumerate(ct):
    groups[i % m] += ch

key = "".join(chr(best_shift(g)+65) for g in groups)
print("recovered key:", key)
เครื่องมือออนไลน์ dcode.fr และ CyberChef (Vigenère Decode) ทำขั้นตอนนี้อัตโนมัติ ใส่ ciphertext แล้วมันลองหาความยาว key และ key ให้ เหมาะกับงานเร็วๆ ใน CTF

5. Decision Tree

สถานการณ์ทำต่อ
รู้ key อยู่แล้วถอดตรงๆ ด้วยสูตร P = C − K
ไม่รู้ key แต่ข้อความยาวIC/Kasiski หา m → แตก Caesar
ข้อความสั้นมาก (IC ไม่นิ่ง)ลองเดา key สั้นๆ หรือใช้ dcode/CyberChef
รู้ plaintext บางส่วน (crib)กู้ key จากส่วนที่รู้: K = C − P

6. Quick Reference

  • C = (P + K) mod 26 · P = (C − K) mod 26
  • หา key length ด้วย Index of Coincidence (เป้าหมาย ~0.067) หรือ Kasiski (GCD ของระยะซ้ำ)
  • รู้ key length แล้ว → แตกเป็น Caesar m ชุด แก้ทีละกลุ่ม
  • เครื่องมือเร็ว: dcode.fr, CyberChef Vigenère Decode
  • ข้อความยิ่งยาว ยิ่งถอดง่าย (สถิตินิ่ง)

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติ brute Caesar ทั้ง 25 shift แล้วไม่มีอันไหนอ่านออกเลย สงสัยว่าน่าจะเป็น Vigenère มีแค่เครื่อง Kali เปล่าๆ ทำตามนี้ทีละขั้น

  1. 1ยืนยันก่อนว่าไม่ใช่ Caesar ธรรมดา: brute 25 shift ไม่มีอันไหนอ่านออกเลย และ histogram ตัวอักษรแบนกว่าภาษาอังกฤษปกติ
  2. 2ทางลัดที่เร็วที่สุด: เอา ciphertext ไปวางที่ https://www.dcode.fr/vigenere-cipher แล้วกดปุ่ม 'Automatic' — มันจะลองหา key length และ key ให้อัตโนมัติ
  3. 3ถ้า dcode หาไม่ได้ (ข้อความสั้นไป) → เขียน python เอง: ตัดอักขระที่ไม่ใช่ a-z ออกก่อน (`ct = ''.join(c for c in raw.upper() if c.isalpha())`)
  4. 4หาความยาว key ด้วย Index of Coincidence: รัน loop m=1..15 ตามโค้ดด้านบน (`avg_ic_for_keylen`) — ดูว่า m ไหนให้ค่าใกล้ 0.067 (ภาษาอังกฤษ) ชัดเจนที่สุด
  5. 5ได้ m แล้ว → transpose ciphertext เป็น m กลุ่มตามตำแหน่ง (`i % m`) แล้วรัน `best_shift()` กู้ shift/ตัวอักษร key ทีละตำแหน่ง
  6. 6ประกอบตัวอักษร key ที่ได้เป็นคำ — ถ้าออกมาเป็นคำที่มีความหมาย (เช่น SECRET, KEYWORD) ยิ่งมั่นใจว่าถูก
  7. 7ใช้ key ที่ได้ถอดทั้งข้อความด้วยสูตร P = (C − K) mod 26 แล้วดูว่าอ่านออกเป็นภาษาอังกฤษ/มี flag{ ไหม
  8. 8รู้ plaintext บางส่วนอยู่แล้ว (crib เช่นขึ้นต้นด้วย 'flag{' หรือ 'the') → กู้ key ตรงๆ จากส่วนนั้นด้วย K = C − P แล้วเช็คว่า pattern ซ้ำเป็น key เดียวกันไหม
  9. 9ผลไม่อ่านออก → ลองความยาว key อันดับรองจาก IC (อันดับ 2, 3 ที่ได้จาก loop) แทนอันดับ 1
  10. 10ยังไม่ได้เลยหลังลองทุกทาง → อาจไม่ใช่ Vigenère มาตรฐาน (key ซ้ำ) แต่เป็น Autokey/running-key cipher (key ไม่ซ้ำ ยากกว่ามาก) — ลองหา context อื่นในโจทย์ (ชื่อไฟล์ คำใบ้) ก่อนลงมือคำนวณหนักๆ
brute Caesar ไม่ออก สงสัย Vigenère — ไล่ตามนี้
ยืนยันว่าไม่ใช่ Caesar ธรรมดา
brute 25 shift ไม่มีอันไหนอ่านออก + histogram แบนกว่าปกติ
ลอง dcode.fr/vigenere-cipher ปุ่ม Automatic (ทางลัดเร็วสุด)
✅ ได้ key + อ่านออก→ จบ ส่ง flag
❌ ไม่ได้ / ข้อความสั้นเกินไป→ ทำ IC เอง
หาความยาว key ด้วย Index of Coincidence (python loop 1-15)
✅ เจอ m ที่ IC พุ่งสูงชัดเจน (~0.067)→ transpose แล้วกู้ key ทีละตัว
❌ ไม่มี m ไหนพุ่งชัด→ ข้อความอาจสั้นเกินไป / ไม่ใช่ Vigenère มาตรฐาน
transpose + best_shift() หา key แต่ละตำแหน่ง (frequency scoring)
✅ key เป็นคำที่มีความหมาย→ ถอดทั้งข้อความ ได้ flag
❌ key ดูเป็นขยะสุ่ม→ ลอง keylength อันดับรอง
มี crib (รู้ plaintext บางส่วน เช่น 'flag{') ไหม?
✅ มี→ กู้ key ตรงๆ ด้วย K = C − P
❌ ไม่มี→ กลับไปลอง keylength อื่นจาก IC
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ยืนยันว่าไม่ใช่ Caesarpython3--
auto-crack หา key ทันที--dcode.fr/vigenere-cipher (ปุ่ม Automatic)
หาความยาว key (IC/Kasiski)python3--
ถอดเมื่อรู้ key แล้วpython3-CyberChef (Vigenère Decode)
ทดลอง encoding ซ้อนชั้นก่อนถึง cipherbase64, xxd-CyberChef (operation Magic)
เทียบแนวคิด repeating-key แบบบิต (คล้ายกัน)python3--
🚑 ถ้าตันสนิท ลองท่าถัดไป: Caesar — ถ้าจริงๆ แล้ว key ยาวแค่ 1 ตัว (m=1) ก็คือ Caesar ธรรมดา · XOR — ถ้าข้อมูลเป็น hex/byte ไม่ใช่ตัวอักษร a-z แนวคิด repeating-key เหมือนกันแต่ทำงานบนบิตแทนตัวอักษร (ใช้ Hamming distance หา keysize) · ROT13/ROT47 — ถ้า key length ที่ได้จาก IC ออกมาเป็น 1 เสมอ ลองพวก ROT ก่อน · Hash Cracking — ถ้าสิ่งที่ได้มาจริงๆ คือ hash คงที่ ไม่ใช่ cipher

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้