การเข้ารหัสลับแบบสัมพรรค
จากสองตอนที่ผ่านมา เราได้เห็นว่าการเข้ารหัสลับแบบเลื่อนนั้น เป็นกรณีพิเศษกรณีหนึ่งในการเข้ารหัสลับแบบจับคู่
แต่อาจเนื่องด้วยความยุ่งยากของการต้องใช้กุญแจขนาดใหญ่เพื่อไขรหัสให้ได้ บวกกับความง่ายเกินไปของการเข้ารหัสแบบเลื่อน
ทำให้เกิดการเข้ารหัสแบบสัมพรรค (affine cipher) ซึ่งดัดแปลงเพิ่มเติมจากการเข้ารหัสลับแบบเลื่อนขึ้นมา
เขียนอธิบายเป็นภาษาทั่วไปยากหน่อย วิธีนี้ดูสมการแล้วน่าจะเข้าใจง่ายกว่าจริงๆ
ให้
สำหรับ
อย่างที่บอกไว้ว่าวิธีนี้ดัดแปลงเพิ่มเติมจากการเข้ารหัสแบบเลื่อนแค่นิดเดียว
ลองมองผ่านๆ จะเห็นว่าวิธีการนี้เพียงแค่เพิ่ม
รู้แค่นี้ก็เอาไปใช้ได้แล้ว แต่ถ้าอยากใช้อย่างไม่ผิดพลาด ลองมาวิเคราะห์อะไรที่มันยากๆ ในนั้นกัน
เริ่มจาก
แต่จะยากตั้งแต่ตรงนี้ไปคือ
เพราะถ้าไม่เป็นเช่นนั้นแล้ว ฟังก์ชัน
ลองพิจารณาตัวอย่าง สมมติให้ iamaboy
จะแปลงได้เป็น QAYACCW
ซึ่งเห็นได้ว่า b, o
ทั้งสองตัวแปลงเป็น C
เหมือนกัน ทำให้การแปลง QAYACCW
กลับเป็นข้อความตั้งต้น iamaboy
นั้นไม่สามารถทำได้ (หรือไม่งั้นก็ต้องอาศัยการตีความเพิ่มว่าตัวอักษรตัวไหนคืออะไรกันแน่)
ตรงนี้เขียนเป็นทฤษฎีบทได้คือ
สำหรับวิธีพิสูจน์จะขอละไว้ก่อน เนื่องจากใช้ความรู้ด้านทฤษฎีจำนวนที่ผมยังไม่ค่อยถนัดนัก
ขั้นต่อไปที่เราจะพิจารณา นั่นคือมี
จาก
ดังนั้นค่า
เห็นได้ว่าวิธีนี้มี
ในด้านจำนวนของกุญแจที่เป็นไปได้นี้ ก็มีทฤษฎีบทมารองรับเช่นกัน
สำหรับ
เมื่อเราสามารถถอดตัวประกอบของ
ลองใช้ดีกว่า จากข้างต้น
ดังนั้น
แต่ปัญหาก็ยังไม่หมดเท่านี้ เพราะแม้เราจะมี
แต่อย่าลืมว่าตอนนี้เรากำลังพิจารณาเลขใน
เราจึงต้องใช้สมบัติพื้นฐานที่ว่า
งานนี้ถึกก่อนได้เปรียบ ลองไล่หาดูเลยว่า
และในที่สุด เราก็ทราบเงื่อนไขทั้งหมดเพื่อใช้การเข้ารหัสแบบสัมพรรคอย่างถูกวิธีแล้ว
ต่อไปลองมาเขียนโค้ดกัน!
อย่างที่บอกไปแล้วว่า วิธีนี้ดัดแปลงเพิ่มจากการเข้ารหัสลับแบบเลื่อนเพียงนิดส์เดียว
ดังนั้นเราจะเริ่มด้วยการก๊อปปี้โค้ดเก่ามาเปลี่ยนรายละเอียดครับ (ข้อดีของการเขียนโค้ดให้แก้ง่าย)
- ที่บรรทัด 1 เปลี่ยนชื่อ และเพิ่มเงื่อนไขของการใช้กุญแจเป็น 2 องค์ประกอบ
- ที่บรรทัด 5 เปลี่ยน logic ของการคำนวณให้เข้ากับสมการคณิตศาสตร์ที่พึ่งเรียนไป
def affine(pain_text, a, b):
cipher_text = ''
for i in range(len(pain_text)):
char_num = lower_to_number(pain_text[i])
char_num = a*char_num + b
char_num %= 26
cipher_text += number_to_upper(char_num)
return cipher_text
เราก็จะได้โค้ดของการเข้ารหัสลับแบบสัมพรรคมาใช้อย่างถูไถละครับ
ก่อนที่เราจะเขียนโค้ดส่วนนี้ต่อ สังเกตุว่า
ดังนั้น เราจึงต้องเขียนป้องกันไม่ให้
วิธีที่จะเขียนตรวจสอบ
สำหรับวิธีที่จะนำเสนอนี้ เป็นการใช้ขั้นตอนวิธีของยูคลิด โดยอยู่ในรูป recursion ครับ
def gcd(a, b):
if a%b == 0:
return b
else:
return gcd(b, a%b)
เรียบร้อยแล้วก็กลับมาปรับปรุงโค้ดของเราครับ
def affine(pain_text, a, b):
if gcd(26, a) != 1:
raise ValueError('gcd(26, {}) must be 1'.format(a))
cipher_text = ''
for i in range(len(pain_text)):
char_num = lower_to_number(pain_text[i])
char_num = a*char_num + b
char_num %= 26
cipher_text += number_to_upper(char_num)
return cipher_text
สองตอนที่แล้วปล่อยให้คิดวิธีเขียน decode เอง แต่คราวนี้ค่อนข้างยากเพราะมีเรื่องของ
เราอาจจะใช้ตัวแปรดิกชันนารีเพื่อจับคู่
ถ้าอย่างนั้นแล้วเรามาดูฟังก์ชันที่มันสนุกๆ กันดีกว่าครับ
แต่ก่อนอื่น เพื่อให้เรื่องต่างๆ เป็นระเบียบและจัดการง่าย เราจะสร้างไฟล์ใหม่ที่ชื่อว่า cryptologic.py
ครับ
และจากตอนที่ผ่านๆ มา จะเห็นว่ามีฟังก์ชันสั้นๆ ที่ไม่ใช่ฟังก์ชันที่เราจะเรียกใช้โดยตรง จะใช้แค่ประกอบในฟังก์ชันหลัก
เช่นพวก lower_to_number(text), gcd(a,b)
เราย้ายมันมาไว้ที่นี่ครับ
แล้วจึงเพิ่มโค้ดอันแสนสนุกสนานนี้เข้าไปร่วมกับเพื่อนๆ ครับ
def mul_inv(a, b, s=0, t=1):
if a%b == 0:
if b == 1:
return t
else:
raise ValueError('no inverse')
else:
return mul_inv(b, a%b, t, (s-t*(a//b))%26)
จะเห็นว่า ฟังก์ชันนี้เป็น recursion อีกแล้ว และที่บรรทัดแรกนั้นมีตัวแปร 4 ตัว คือ a, b, s, t
แต่เวลาจะใช้งานนั้น s
เริ่มที่ 0
และ t
เริ่มที่ 1
เสมอ หลังจากนั้นจึงค่อยเปลี่ยนแปลงค่าของมันโดยส่งค่าที่คำนวณระหว่างทางเข้าไป
เราจึงสามารถกำหนด s = 0
และ t = 1
ได้ตั้งแต่เริ่ม และเรียกใช้ฟังก์ชันโดยใส่ค่าเพียง a
กับ b
ก็พอ
อย่าลืมเขียนโค้ด import ที่ไฟล์ encrypt.py เพื่อเรียกใช้งานฟังก์ชันที่อยู่ต่างไฟล์ออกไปนะครับ
from cryptologic import *
ส่วนการโค้ดรับรหัสลับเพื่อนำมาถอดเป็นข้อความก็จะเป็นดังนี้ครับ (ไฟล์ใหม่ decrypt.py
ครับ)
def affine(cipher_text, a, b):
a_inv = mul_inv(26, a)
pain_text = ''
for i in range(len(cipher_text)):
char_num = upper_to_number(cipher_text[i])
char_num = a_inv*(char_num - b)
char_num %= 26
pain_text += number_to_lower(char_num)
return pain_text
เดี๋ยวตอนหน้าจะมันส์กว่านี้อีกครับ ยังไงก็ติดตามกันด้วยนะครับ 💪

author