สถาบันข้อมูลขนาดใหญ่ (องค์การมหาชน)

Logo BDI For web

การเข้ารหัสลับ (Encryption) เบื้องต้น สำหรับนักพัฒนา

May 27, 2022

บทความ “การเข้ารหัสลับ (Encryption) เบื้องต้น สำหรับนักพัฒนา” นี้ใช้แหล่งข้อมูลส่วนใหญ่จาก e-book ชื่อ Practical Cryptography for Developers

อวจ: ที่อยู่ในบทความนั้นย่อมาจาก เอาไว้จำ จุดประสงค์ของผู้เขียนมีไว้เพื่อสรุปความสำหรับผู้อ่านที่มีความเข้าใจอยู่บ้างแล้ว หรืออ่านหัวข้อนั้นแล้วให้สามารถจำนิยามของหัวข้อนั้นได้

สาร ในบทความนี้ไม่ใช่ สารเคมี แต่เป็นข้อความ (message)

ในปัจจุบันอินเทอร์เน็ตเข้ามาเป็นส่วนหนึ่งของชีวิตทุกคน แต่กระนั้นความสะดวกสบายที่มาพร้อมกับอินเทอร์เน็ต ก็ไม่ได้นำมาเฉพาะข้อดีเพียงอย่างเดียว หากมองในด้านความปลอดภัยด้วยนั้น อินเทอร์เน็ตจึงเป็นเหมือนดาบสองคม เนื่องจากว่าหากนักพัฒนา หรือผู้ใช้งานขาดความเข้าใจ ก็จะทำให้เกิดช่องโหว่ให้ผู้ไม่ประสงค์ดีโจมตีระบบ หรือเกิดการโจรกรรมทางไซเบอร์ได้ และหนึ่งในหัวใจสำคัญของการสื่อสารระหว่างผู้ใช้งาน (Client) กับแม่ข่าย (Server) นั่นก็คือ การเข้ารหัสลับ (Encryption) โดยบทความนี้จะค่อย ๆ ปูนิยามของศัพท์ต่าง ๆ ที่เกี่ยวข้อง เช่น การเข้ารหัส (Encoding), ฟังก์ชันแฮช (Hashing function) เป็นต้น จนนำไปสู่เรื่องการเข้ารหัสลับ (Encryption) ทีละหัวเรื่อง ดังต่อไปนี้

การเข้ารหัส (Encoding)

อวจ: ด <-> d ; เสียง “ด” ในภาษาไทย คือเสียง “d” ในภาษาอังกฤษ

ผู้เขียนใช้ภาษาไทยว่า การเข้ารหัส เพื่อแปลคำว่า Encoding และ การเข้ารหัสลับ เพื่อแปลคำว่า Encryption เนื่องจากทั้งสองสิ่งนี้แตกต่างกันอย่างมาก การเข้ารหัสนั้นไม่ได้ทำเพื่อรักษาความลับของสาร แต่ทำเพื่อความสะดวกในการรับส่งสาร หรืออ่านสารได้ง่ายขึ้น ตัวอย่างเช่น การแปลงเลขฐานสองเป็นฐานสิบ หรือการแปลงเลขฐานสิบเป็นสายอักขระ (byte array) หรือการแปลงสายอักขระเป็นสาร base64 เป็นต้น

ตัวอย่างการเข้ารหัส
รูปที่ 1 ตัวอย่างการเข้ารหัส, แหล่งอ้างอิง

การเข้ารหัสนั้น สามารถย้อนกลับได้ง่ายด้วย การถอดรหัส (Decoding) ถ้าหากเรารู้ว่าสารที่เรารับมาถูกเข้ารหัสมาแบบใด เราก็จะสามารถถอดรหัสสารนั้นเป็นสิ่งที่เราต้องการได้ เช่น การแปลงสาร base64 เป็นสายอักขระ

ตัวอย่าง การเข้ารหัสจากสายอักขระเป็นสาร base64 และถอดรหัสจากสาร base64 เป็นสายอักขระ ในภาษา python

import base64
m = b"testing"

b64_m = base64.b64encode(m)
print(b64_m)
# b'dGVzdGluZw=='

_m = base64.b64decode(b64_m)
print(_m)
# b'testing'

assert (m == _m)

เกร็ด: ภาษาคาราโอเกะ ภาษาลู หรือคำผวน ก็เป็นการเข้ารหัสเช่นกัน 🙂

ฟังก์ชันแฮช (Hashing function)

อวจ: คิดถึง -> 345e28e423062ecb7dad358f1b47e4abb836a0cd77cf9f47cf6e6478f8d43403 ; ให้แฮชของความคิดถึง แทนความซาบซึ้งในใจ (เพราะจำไม่ได้ 555)

การแฮชนั้นคล้ายกับการเข้ารหัสตรงที่ ถ้าเรามีสาร และฟังก์ชันการแฮช เราก็สามารถ ผลิต เลขแฮชได้ แต่กลับกัน หากเราได้เลขแฮชมา เราจะไม่สามารถรู้ได้ว่า สารก่อนเข้าฟังก์ชันแฮชเขียนไว้ว่าอย่างไร ซึ่งจะนำไปสู่คุณสมบัติของฟังก์ชันแฮช คุณสมบัติของฟังก์ชันแฮชนั้นมีสองประการ คือ

  1. ไม่สามารถย้อนกลับได้
  2. แทบจะไม่มีสิทธิ์ชนกัน

คำว่า แทบจะไม่มีสิทธิ์ชนกัน นั้น แน่นอนว่าอาจจะเกิดการชนกันได้ แต่เกิดขึ้นยาก อภิมหาโคตรยาก เรียกว่า การพยายามจะหาสารสองอันที่จะให้ผลลัพธ์เลขแฮชเดียวกัน ก็เหมือนกับการควานหาฝุ่นในจักรวาลก็ปานนั้น แต่ในอนาคตก็ไม่แน่ว่า Quantum Computer อาจจะทำให้ SHA-2 ที่ได้รับความนิยมอยู่ในปัจจุบันถูกโค่นลงได้สักวัน อันนี้ก็เป็นเรื่องของอนาคตที่เราต้องติดตามกันต่อไป

ตัวอย่าง ฟังก์ชันแฮชในภาษา python ในที่นี้ ผู้เขียนขอใช้ตัวอย่างฟังก์ชัน sha256 หรือ SHA-2 มาเป็นตัวอย่าง

import hashlib

m = "คิดถึง"
hash_m = hashlib.sha256(m.encode("utf-8")).digest().hex()

print(hash_m)
# 345e28e423062ecb7dad358f1b47e4abb836a0cd77cf9f47cf6e6478f8d43403

เกร็ด: ยังมีกลุ่มฟังก์ชันที่มีแนวคิดคล้ายกับฟังก์ชันแฮชอีกสองอย่าง คือ check digit และ checksum แต่ต่างกันตรงความยากในการชนกัน เช่น

def calc_thai_citizen_id_check_digit(cid12: str) -> int:
  return (11 - sum([(13 - idx) * int(cid) for idx, cid in enumerate(cid12)]) % 11) % 10

assert (
  calc_thai_citizen_id_check_digit("000000000070") ==  # check digit ของ 000000000070 คือ 1
  calc_thai_citizen_id_check_digit("000000000005")     # check digit ของ 000000000005 ก็ได้ 1 ด้วยเหมือนกัน
)

หมายเหตุ: วิธีการคำนวน check digit นำมาจาก ที่นี่

จะสังเกตว่า การคำนวน check digit นั้นสามารถเกิดผลลัพธ์ที่ชนกัน (หรือเหมือนกันจากในตัวอย่างคือเลข หนึ่ง) ได้ค่อนข้างง่าย ส่วน checksum นั้นจะเกิดผลลัพธ์ที่ชนกันค่อนข้างยาก ตัวอย่างคือ SHA-1 ในอดีตฟังก์ชันนี้ถูกใช้เพื่อเป็นฟังก์ชันแฮชด้วย แต่ปัจจุบันมีผู้ที่สามารถหาผลลัพธ์ที่ทำให้เกิดผลลัพธ์ที่เหมือนกันได้แล้ว ดังนั้น SHA-1 จึงไม่ดีพอที่จะใช้เป็นฟังก์ชันแฮชเพื่อความปลอดภัยอีกต่อไป อย่างไรก็ดี SHA-1 นั้นยังมีการใช้เพื่อทำ checksum เพื่อตรวจสอบความถูกต้องของไฟล์อยู่

ฟังก์ชัน MAC

อวจ: mac_func(สาร, รู้กันแค่เรา) -> ตัวเทียบว่าสารถูกต้อง ; นายเอส่งจดหมายรัก(สาร)หาน้องบีโดยใช้กระดาษสองแผ่นเสมอ(รู้กันแค่เรา) แต่วันหนึ่งไปรษณีย์ทำปลิวหายไปแผ่นหนึ่ง น้องบีจึงรู้ว่าจดหมายนั้นไม่ครบถ้วน(ไม่ถูกต้อง)เพราะไม่ครบสองแผ่น

MAC ในที่นี้ย่อมาจาก Message Authenticaion Code ไม่ได้เกี่ยวข้องกับ MAC Address หรือ Media Access Control Address แต่อย่างใด MAC มีไว้เพื่อทำหน้าที่พิสูจน์ว่าสารที่เราได้รับมาถูกต้องสมบูรณ์หรือไม่

การทำงานของ MAC
รูปที่ 2 การทำงานของ MAC

เนื่องจากการคำนวน MAC โดยวิธีใช้ hash_func(key + msg) นั้นไม่ปลอดภัยจากการโจมตีแบบ Length Extension Attack ดังนั้นฟังก์ชัน MAC ที่ปลอดภัยและได้รับความนิยมคือ HMAC (Hash-based MAC) โดยมีหน้าตาคือ mac = HMAC(key, msg, hash_func)

ตัวอย่างฟังก์ชัน HMAC ในภาษา python

import hashlib, hmac

def hmac_sha256(key, msg):
  return hmac.new(key, msg, hashlib.sha256).digest().hex()

key = "รู้กันแค่เรา"
msg = "คำบอกรักที่ฝากถึงเธอหนา สุดเกินกว่าพรรณาด้วยคำไหน ผ่านโลกหล้าผ่านเวลาสิ่งใดใด โปรดรู้ไว้คำว่ารักจักเหมือนเดิม"
mac = hmac_sha256(key.encode("utf-8"), msg.encode("utf-8"))
print(mac)
# 241e6bdd7b1b06ca8bdff88f19515e89e4b32d984cfb22174ce9cccb3680b830

ฟังก์ชัน KDF

อวจ: kdf_func(เกลือ, รหัสผ่าน, อย่างอื่นถ้ามี) -> รหัสผ่านที่แปลงแล้ว

KDF ย่อมาจาก Key Derivation Functions ฟังจากชื่อแล้วแอบน่ากลัวว่าจะเกี่ยวกับ Derivative ในคณิตศาสตร์ใช่ไหม แต่ไม่ใช่เช่นนั้น ฟังก์ชันนี้ไม่ได้เกี่ยวกับการคำนวน Derivative ผู้อ่านจึงไม่ต้องกังวล โดยในหัวข้อนี้จะพาผู้อ่านสู่ความเข้าใจ KDF ผ่านการจัดการรหัสผ่านระดับต่าง ๆ  ในการจัดการรหัสผ่าน (password) นั้นแน่นอนว่าเราไม่สามารถหลีกหนีเรื่องความปลอดภัยไปได้ และการเก็บรหัสผ่านของผู้ใช้ที่สามารถนำไปใช้งานกับระบบได้ทันทีก็เปรียบเสมือนการเก็บระเบิดเวลาไว้ในระบบ ผู้เขียนขอกล่าวถึง การจัดการรหัสผ่านโดยเริ่มตั้งแต่ระดับ อย่าหาทำ ไปจนถึงระดับ ควรทำ

ระดับ อย่าหาทำ หรือเกรียนสุด

วิธีการที่ง่ายสุด (และเกรียนที่สุดด้วย) นั่นคือการเก็บรหัสผ่านแบบ โต้ง ๆ ได้มาอย่างไรก็เก็บไปอย่างนั้นซะเลย ตัวอย่างเช่น ผู้ใช้งานกรอกรหัสผ่านตอนลงทะเบียนมาเป็น “hello123” ก็นำรหัสผ่านนี้เขียนลงในฐานข้อมูลตารางผู้ใช้เป็น “hello123” นั่นเอง แม้ว่าการจัดการรหัสผ่านของผู้ใช้โดยวิธีนี้จะไม่มีความซับซ้อน และไม่ต้องใช้ library อื่นจากภายนอกเลย แต่ผู้เขียนกล่าวได้เลยว่าการทำแบบนี้ กรณีดีที่สุด คือ การรอให้คนอื่นมาด่าเราว่า ทำไมทำอะไรหละหลวมแบบนี้เปลี่ยนแปลงซะ หรือกรณีร้ายที่สุดคือระบบถูกเล่นงาน ผู้โจมตีระบบได้ครอบครองรหัสผ่านผู้ใช้งานระบบโดยสมบูรณ์ ถือเป็นความวิบัติใหญ่หลวงอย่างมาก ดังนั้นผู้เขียนกราบวิงวอนผู้อ่านทุกท่านว่า อย่าหาทำ วิธีนี้เลย

ระดับดีขึ้นมานิด (แต่ยังไม่พอ)

ระดับที่ดีขึ้นมาอีกขึ้นหนึ่ง คือ การใช้ฟังก์ชันแฮชมาครอบตัวรหัสผ่านของผู้ใช้ตอนลงทะเบียน และตอนลงชื่อเข้าใช้งาน ตัวอย่างเช่น หากผู้ใช้งานกรอกรหัสผ่านตอนลงทะเบียนมาเป็น “good_to_guess” ก็จะถูกนำไปผ่านฟังก์ชันแฮชก่อนนำไปเก็บลงฐานข้อมูล ตัวอย่าง ในภาษา python

input_password = b"good_to_guess"

hashed_password = hashlib.sha256(input_password).digest().hex()
# '91d9c2367ddc6dab3a36a060c232ac9c14a742b4b2c78b6a64a5c786265135a0'

การทำเช่นนี้ถือว่าดีกว่าการเก็บแบบโต้ง ๆ อย่างแน่นอน แต่ตัวอย่างด้านบนก็ยังไม่ควรทำอยู่ดี เพราะหากผู้โจมตีระบบสามารถลงทะเบียนสมัครสมาชิกได้ และสามารถเข้าถึงฐานข้อมูลตารางผู้ใช้งานได้อีก ผู้โจมตี อาจ คาดเดารหัสผ่านของผู้ใช้คนอื่นได้ ตัวอย่างเช่น บังเอิญผู้โจมตีใส่รหัสผ่านเป็น “good_to_guess” เหมือนกับผู้ใช้ด้านบน จะส่งผลให้ hashed_password นั้น เหมือนกัน นั่นก็แปลว่าผู้โจมตีรู้รหัสผ่านของผู้ใช้คนนั้นโดยปริยาย

การใช้ฟังก์ชันแฮชมาครอบนั้น ยังสามารถทำให้ดีได้อีกระดับหนึ่ง คือ เติมเกลือเข้าไปด้วยซึ่งก็ถือว่าดีกว่าเดิม เพราะผู้โจมตีไม่สามารถล่วงรู้รหัสผ่านของผู้ใช้งานคนอื่นได้ หากกรอกรหัสผ่านเหมือนกัน ตัวอย่างในภาษา python

input_password = b"good_to_guess"

salt = "".join(random.choices(string.ascii_letters + string.digits, k = 8))

hashed_password = hashlib.sha256(input_password + salt).digest().hex()

อย่างไรก็ดีวิธีการนี้ก็ยังไม่ปลอดภัยจากการโจมตีแบบ Length Extension Attack อยู่ดี ดังนั้นเราก็ควรเลี่ยงวิธีนี้เช่นกัน

ระดับพอได้ (แต่ยังดีได้อีก)

วิธีการคือใช้ HMAC เป็น KDF เลย ซึ่งเรียกว่า HKDF (HMAC-based KDF) หรือ hmac_func(salt, password, hash_func) ตัวอย่างเช่น เราสามารถใช้ตัวอย่างรหัสต้น HMAC มาเป็นแปลงเป็นตัวอย่างได้ เช่น

import hashlib, hmac
import string
import random

def hmac_sha256(key, msg):
  return hmac.new(key, msg, hashlib.sha256).digest().hex()

hkdf = hmac_sha256

salt = "".join(random.choices(string.ascii_letters + string.digits, k = 8)).encode()
input_password = b"0z0x9cbv917"

hashed_password = hkdf(salt, input_password)

อย่างไรก็ดี วิธีนี้ก็ยังปลอดภัยน้อยกว่าแนวทางสุดท้ายนั่นก็คือ

ควรทำ ทำเถอะไม่ยากเลย

สิ่งที่ควรทำที่สุดก็คือ การใช้ library เพื่อใช้เป็น KDF ตัวอย่างเช่น Bcrypt, Scrypt หรือ Argon2 โดยหลักแล้ว สิ่งที่เราจะต้องเก็บเพิ่มนอกเหนือจาก hashed_password และ salt คือ setting ของอัลกอริทึมที่เราเลือกใช้ ซึ่งโดยส่วนใหญ่ library ใหม่ ๆ จะเก็บสามสิ่งมัดรวมไว้เป็นสายอักขระเดียว เพื่อให้เราสามารถเก็บไว้ในฐานข้อมูลได้ง่าย จากที่จะต้องเก็บข้อมูลในหลายคอลัมน์เป็นคอลัมน์เดียว ในที่นี้ขอตัวอย่าง ใน python ของ Argon2

# pip install argon2_cffi
import argon2

argon2Hasher = argon2.PasswordHasher(
    time_cost=16, memory_cost=2**15, parallelism=2, hash_len=32, salt_len=16)

hash = argon2Hasher.hash("password") # keep this thing into database

print("Argon2 hash (random salt):", hash)

verifyValid = argon2Hasher.verify(hash, "password")
print("Argon2 verify (correct password):", verifyValid)

try:
    argon2Hasher.verify(hash, "wrong123")
except:
    print("Argon2 verify (incorrect password):", False)

การเข้ารหัสลับ (Encryption)

การเข้ารหัสลับ (Encryption) นั้น ทำเพื่อรักษาความลับของสาร ดังนั้นจะมีเรื่องกุญแจ (Key) เข้ามาเกี่ยวข้อง เนื่องจากการเข้ารหัสลับจะใช้กุญแจทั้งในกระบวนการเข้ารหัสลับ และกระบวนการถอดรหัสลับ เราสามารถแบ่งประเภทการเข้ารหัสลับตามลักษณะของกุญแจที่ใช้ ออกเป็นสองลักษณะใหญ่ดังนี้

  1. การเข้ารหัสลับแบบกุญแจสมมาตร (Symmetric Key Encryption)
  2. การเข้ารหัสลับแบบกุญแจอสมมาตร (Asymmetric Key Encryption)

ในบทความนี้ จะกล่าวถึงเฉพาะกุญแจสมมาตรก่อน ส่วนกุญแจอสมมาตรจะอยู่ในบทความถัด ๆ ไป

การเข้ารหัสลับแบบกุญแจสมมาตร (Symmetric Key Encryption )

อวจ: encrypt(สาร, กุญแจ, อย่างอื่นถ้ามี) -> สารที่แปลงแล้ว ; decrypt(สารที่แปลงแล้ว, กุญแจ, อย่างอื่นถ้ามี) -> สาร

การเข้ารหัสลับ และการถอดรหัสลับโดยใช้กุญแจสมมาตรนั้น เราจะใช้กุญแจ ดอกเดียวกัน ทั้งการเข้ารหัสลับ และการถอดรหัสลับ ดังรูป

การเข้ารหัสลับแบบกุญแจสมมาตร
รูปที่ 3 การเข้ารหัสลับแบบกุญแจสมมาตร

โดยปกติแล้ว การเข้ารหัสลับแบบกุญแจสมมาตรจะไม่ได้ใช้อัลกอริทึมเข้ารหัสลับอย่างเดียว แต่จะมีการตัดสารออกเป็นก้อน (Block) ภายในกล่อง “กระบวนการเข้ารหัสลับแบบกุญแจสมมาตร” ด้านล่าง คือ P0 จนถึง Pn โดยแต่ละก้อนมีขนาดเท่า ๆ กัน แล้วจึงนำก้อนแต่ละก้อนไป ผสมผสานกับฟังก์ชันอื่น (มีรายละเอียดใน รูปที่ 5) กระบวนการของการเข้ารหัสแบบกุญแจสมมาตรโดยทั่วไปจะประกอบด้วย

  • KDF
  • กระบวนการเข้ารหัสลับแบบกุญแจสมมาตร
    • Block Cipher Mode
    • Block Cipher Algorithm
  • MAC

เรารู้จักกับ KDF และ MAC ไปแล้ว ส่วน Block Cipher Algorithm นั้น เป็นอัลกอริทึมการเข้ารหัสลับแบบกุญแจสมมาตรจริง ๆ เช่น AES และ ChaCha20 ซึ่งจะฝังอยู่ใน Block Cipher Mode ส่วน Block Cipher Mode คือ วิธีจัดการกับข้อมูลที่ตัดเป็นก้อนแล้ว ซึ่งมีหลายวิธี อาทิเช่น CBC, CTR และ GCM ใน Block Cipher Mode แต่ละตัวนั้นก็จะมีคุณสมบัติแตกต่างกันไป แต่ผู้เขียนจะไม่ลงลึกไปกว่านี้ เพราะจะทำให้บทความนี้ยาวเกินไป แต่จะขอยกบางคุณสมบัติคร่าว ๆ อาทิเช่น

  • CBC ต้องมีการทำ Padding ข้อมูลก้อนสุดท้ายด้วยอักขระพิเศษที่กำหนดเนื่องจาก CBC ใช้วิธีการตัดข้อมูลจริง ๆ ไม่ได้ใช้ XOR Operation เหมือนกับวิธีอื่น
  • GCM มีการทำ MAC ให้อยู่ภายในตัวแล้ว แบบ encrypt-then-MAC

กระบวนการเข้ารหัสลับจากผู้รับสาร แล้วส่งไปยังช่องทางสื่อสาร จนถึงผู้รับสารดำเนินกระบวนการถอดรหัสลับ สามารถแสดงออกมาเป็นภาพได้ดังนี้

กระบวนการเข้ารหัสจนถึงถอดรหัสลับแบบกุญแจสมมาตร
รูปที่ 4 กระบวนการเข้ารหัสจนถึงถอดรหัสลับแบบกุญแจสมมาตร

ซึ่งภายในกล่อง “กระบวนการเข้ารหัสลับแบบกุญแจสมมาตร” จะมีกลไกโดยภาพรวม ดังภาพด้านล่าง

แสดงกลไกภายในกล่อง  “กระบวนการเข้ารหัสลับแบบกุญแจสมมาตร” ใน รูปที่ 4
รูปที่ 5 แสดงกลไกภายในกล่อง “กระบวนการเข้ารหัสลับแบบกุญแจสมมาตร” ใน รูปที่ 4

ผลลัพธ์ที่ได้จากกล่อง “กระบวนการเข้ารหัสลับแบบกุญแจสมมาตร” คือ ก้อนสารที่เข้ารหัสแล้วคือ C0 จนถึง Cn ซึ่งถูกนำมารวมกันเป็นก้อนเดียวอีกครั้ง เป็นสารที่ถูกเข้ารหัสลับ (Ciphertext)

การเรียกชื่อของการเข้ารหัสลับแบบกุญแจสมมาตรจะใช้รูปแบบ [ชื่อ Block Cipher Algorithm ที่ใช้]-[ความยาวของกุญแจเป็น bit ที่กับ Block Cipher Algorithm]-[ชื่อ Block Cipher Mode] อาจจะจำย่อๆ เป็น [Block Cipher Algorithm]-[Key Length]-[Block Cipher Mode] ตัวอย่างเช่น ชื่อการเข้ารหัสลับ AES-256-GCM

ตัวอย่าง การใช้ library ใน python

# pip install scrypt pycryptodome

from Crypto.Cipher import AES
import scrypt, os

def encrypt_aes_gcm(message, password):
    """
    เข้ารหัสลับด้วย Block Cipher Algorithm AES และ GCM Block Cipher Mode
    """
   
    # ทำ KDF
    kdf_salt = os.urandom(16)
    secret_key = scrypt.hash(password, kdf_salt, N=16384, r=8, p=1, buflen=32)

    # ระบุ Block Cipher Mode
    aes_cipher = AES.new(secret_key, AES.MODE_GCM)

    # สร้าง Additional Authenticated Data - AAD
    aad = os.urandom(32)
    # จาก https://stackoverflow.com/questions/64381895/is-the-aes-gcm-in-python-crypto-cipher-library-can-take-care-the-aad-portion
    aes_cipher.update(aad)

    # ในที่นี้ Block Cipher Mode GCM ทำ MAC ให้ในตัวได้ผลลัพธ์เป็น auth_tag
    ciphertext, auth_tag = aes_cipher.encrypt_and_digest(message)

    # Initial Vector - IV มีชื่อเรียกอีกอย่างหนึ่งว่า nonce
    return (kdf_salt, ciphertext, aes_cipher.nonce, aad, auth_tag)

def decrypt_aes_gcm(encrypted_message, password):
    """
    ถอดรหัสลับด้วย Block Cipher Algorithm AES และ GCM Block Cipher Mode
    """

    # แกะ parameter ออกจาก encrypted_message
    (kdf_salt, ciphertext, nonce, aad, auth_tag) = encrypted_message

    # ทำ KDF
    secret_key = scrypt.hash(password, kdf_salt, N=16384, r=8, p=1, buflen=32)

    # ระบุ Block Cipher Mode
    aes_cipher = AES.new(secret_key, AES.MODE_GCM, nonce)
    aes_cipher.update(aad)

    # ถอดรหัส และทวนสอบความถูกต้องของ authTag หรือ MAC
    plaintext = aes_cipher.decrypt_and_verify(ciphertext, auth_tag)
    return plaintext

# กระบวนการทำงาน

# ผู้ส่งสารเตรียมสาร และรหัสผ่าน
message = b'Message for AES-256-GCM + Scrypt encryption'
password = b's3kr3tp4ssw0rd'

# ผู้ส่งสารเรียกฟังก์ชันการเข้ารหัสลับ และได้ผลลัพธ์
encrypted_msg = encrypt_aes_gcm(message, password)
encrypted_msg_json = {
    'kdfSalt': encrypted_msg[0].hex(),
    'ciphertext': encrypted_msg[1].hex(),
    'aesIV': encrypted_msg[2].hex(),
    'aad': encrypted_msg[3].hex(),
    'authTag': encrypted_msg[4].hex()
}
print("encryptedMsg", encrypted_msg_json)

# ผู้ส่งสารส่งสารที่เข้ารหัสให้กับผู้รับสารผ่าน Internet โดยทั่วไปคือ Restful API (HTTP)

# ผู้รับสารรับสารมาแปลงเป็น byte array
encrypted_msg = (
    bytes.fromhex(encrypted_msg_json['kdfSalt']),
    bytes.fromhex(encrypted_msg_json['ciphertext']),
    bytes.fromhex(encrypted_msg_json['aesIV']),
    bytes.fromhex(encrypted_msg_json['aad']),
    bytes.fromhex(encrypted_msg_json['authTag']),
)

# ผู้ส่งสารเรียกฟังก์ชันการเข้ารหัสลับ และได้สารจากผู้ส่งสาร
decryptedMsg = decrypt_aes_gcm(encrypted_msg, password)
print("decryptedMsg", decryptedMsg)

ผู้เขียนขอจบพื้นฐานการเข้ารหัสลับในตอนแรกไว้เพียงเท่านี้ หากผู้อ่านทุกท่านรู้สึกว่า ต้องการแสดงความเห็น ต้องการให้ข้อมูลเพิ่มเติม หรือพบข้อผิดพลาดประการใด ผู้เขียนขอน้อมรับไว้ทุกสิ่ง เมื่อผู้อ่านเดินทางมาถึงตรงนี้แล้ว ผู้เขียนคิดว่าบทความนี้จะทำให้ผู้อ่านเข้าใจเรื่องการเข้ารหัสลับมากขึ้นไม่มากก็น้อย และจะเป็นการดียิ่งขึ้น หากผู้อ่านสามารถนำไปต่อยอดในการพัฒนาโปรแกรมให้ดียิ่งขึ้น และในตอนต่อไป (อาจจะไม่ใช่เร็ว ๆ นี้) ผู้เขียนจะกล่าวถึง การเข้ารหัสลับแบบกุญแจอสมมาตร กันโดยละเอียดอีกครั้ง ขอบคุณครับ

อีกสักนิดก่อนจากกัน ผู้เขียนขอแนะนำบทความด้านล่างนี้ สำหรับผู้ที่สนใจเกี่ยวกับความลับของข้อมูล และการเข้ารหัสเพิ่มเติม
การจัดทำข้อมูลนิรนาม (Data Anonymization)
Searchable Encryption กลไกการปกป้องข้อมูลบน Cloud ที่คุณไว้วางใจได้

เนื้อหาโดย ประณิธาน ธรรมเจริญพร
ตรวจทานและปรับปรุงโดย เมธิยาภาวิ์ ศรีมนตรินนท์

Pranithan Thamcharoenporn

Developer and Data Engineer Government Big Data Institute (GBDi)

© Big Data Institute | Privacy Notice