Private Docs

Data Encryption & Masking 101 — ปกป้อง column ข้อมูล 2 แบบ

อธิบายแบบง่ายที่สุด: ข้อมูลอ่อนไหว 1 field ต้องป้องกัน 2 จังหวะ — เข้ารหัสตอนเก็บลง DB (data encryption at rest — โจทย์ PDPA) กับเซ็นเซอร์/mask ตอนส่งออกไปให้คนเห็น — ต่างกันยังไง กันอะไรได้ ทำไมต้องใช้คู่กัน และทำไม mask ที่ FE = ไม่ได้ป้องกันอะไรเลย

อัปเดต: 2026-08-06

Data Encryption & Masking 101 — ปกป้อง column ข้อมูล 2 แบบ

เล่มก่อนหน้า (Encryption Field 101) พูดถึงการปกป้องข้อมูลตอนเดินทาง (browser ↔ backend) — เล่มนี้พูดถึงอีก 2 จังหวะที่เหลือของชีวิตข้อมูล: ตอนนอนอยู่ใน database และตอนถูกส่งออกไปให้คนเห็น

ทั้งสองแบบทำงานที่ backend ล้วนๆ — ไม่เกี่ยวกับ browser, ไม่เกี่ยวกับชั้น 1-3 ของขา browser (แต่เสริมกัน — ดูข้อ 5)

TL;DR — อ่าน 30 วิ

  • ข้อมูลอ่อนไหว 1 field (เลขบัตรประชาชน, เลขบัญชี) ต้องคิด 2 คำถามแยกกันเสมอ: “เก็บยังไงให้หลุดแล้วอ่านไม่ออก” (encryption at rest) กับ “ส่งออกยังไงให้เห็นเท่าที่จำเป็น” (masking)
  • Encryption at rest = เข้ารหัสก่อนเก็บลง DB, ถอดได้ด้วย key ที่อยู่ Key Vault — กัน DB dump/backup หลุด, กันคน query ตรง · เป็นมาตรการ security ตาม PDPA โดยตรง
  • Masking = ส่งค่าบางส่วน (1-2345-xxxxx-67-8) เมื่อผู้รับไม่ต้องใช้ค่าเต็ม — กันตาคน, กัน log, ลดการเปิดเผยเกินจำเป็น (data minimization)
  • สองอย่างนี้ไม่ใช่ตัวเลือกให้เลือกอันเดียว — ปกป้องคนละจุด ใช้คู่กันเสมอ
  • ⚠️ กฎเหล็กข้อเดียวที่ห้ามพลาด: mask ต้องทำที่ backend — ส่งค่าเต็มมาแล้วให้หน้าจอปิดดาวเอง = ค่าเต็มอยู่ใน Network tab เรียบร้อยแล้ว ไม่ได้ป้องกันอะไรเลย

1. ชีวิตของข้อมูล 1 field — และจุดที่แต่ละเครื่องมือปกป้อง

graph LR
    subgraph transit1["🚚 ตอนเดินทาง (ขาเข้า)"]
        A["Browser"] -->|"TLS + ชั้น 3<br/>(เล่ม Encryption Field 101)"| B["Backend"]
    end
    subgraph rest["💾 ตอนนอนอยู่"]
        B -->|"① encrypt ก่อนเก็บ<br/>(data encryption at rest)"| DB[("Database<br/>ciphertext")]
        DB -->|"ถอดด้วย key จาก KV"| B2["Backend"]
    end
    subgraph egress["📤 ตอนถูกส่งออก"]
        B2 -->|"② mask ก่อนส่ง<br/>ถ้าปลายทางไม่ต้องใช้ค่าเต็ม"| C["หน้าจอ / รายงาน / log /<br/>ระบบภายนอก"]
    end
จังหวะภัยที่เจอเครื่องมือ
เดินทาง (browser ↔ backend)คนดักฟัง / คนกลางที่ terminate TLSTLS + ชั้น 3 (เล่มก่อนหน้า)
นอนอยู่ใน DBDB dump หลุด, backup หลุด, คนมีสิทธิ์ query ตรง, SQL injection อ่านทั้ง table① Data encryption at rest
ถูกส่งออกไปให้คนเห็นตาคน (หน้าจอ, screenshot), log, admin/support ที่ไม่จำเป็นต้องเห็นค่าเต็ม② Masking (เซ็นเซอร์)

2. แบบที่ ① — Data Encryption at Rest (โจทย์ PDPA)

ทำงานยังไง

ตอนเขียน:  plaintext → [AES encrypt ด้วย key จาก Key Vault] → เก็บ ciphertext ลง column
ตอนอ่าน:   ciphertext → [AES decrypt ด้วย key เดียวกัน] → ได้ plaintext ใน memory ของ app
  • key อยู่ Key Vault เท่านั้น — ไม่อยู่ใน DB, ไม่อยู่ใน source code, ไม่อยู่ใน appsettings (อยู่เป็น KV secret)
  • คนที่ได้ DB ไปทั้งก้อน (dump/backup/query ตรง) เห็นแค่ ciphertext — ไม่มี key = อ่านไม่ออก
  • เป็นการเข้ารหัสแบบ ถอดกลับได้ (reversible) โดยตั้งใจ — เพราะ app ต้องใช้ค่าจริงทำงานต่อ (ต่างจาก password ที่ต้อง hash ทิ้งถาวร)

มุม PDPA — ทำไมข้อนี้คือ “ต้องมี” ไม่ใช่ “ควรมี”

  • PDPA กำหนดให้ผู้ควบคุมข้อมูลมีมาตรการรักษาความมั่นคงปลอดภัยที่เหมาะสม — การเข้ารหัสข้อมูลส่วนบุคคลอ่อนไหวใน storage คือมาตรการมาตรฐานที่ผู้ตรวจถามหาเป็นข้อแรกๆ
  • ถ้าเกิดเหตุข้อมูลรั่ว: หลุดเป็น ciphertext ที่ไม่มี key ≠ หลุด plaintext — ระดับความเสียหายและภาระหน้าที่หลังเกิดเหตุต่างกันมาก
  • ช่วยบังคับ least privilege ในตัว: DBA/คนมีสิทธิ์อ่าน DB ไม่เห็นข้อมูลส่วนบุคคลอัตโนมัติอีกต่อไป — เห็นได้เฉพาะผ่าน app ที่มี key และมี audit trail

ของที่มีอยู่แล้วใน stack เรา

Backend_Package มี AES field encryption อยู่แล้ว (config Encryption:Key — เป็น KV secret) — งานนี้จึงไม่ใช่สร้างกลไกใหม่ แต่คือเลือกว่า column ไหนต้องผ่านมัน ตาม PII inventory (ตัวเดียวกับที่ชั้น 3 ใช้ — มติ 14)

จุดที่ต้องคิดก่อนเลือก column (ราคาที่ต้องจ่าย — รู้ก่อน ไม่เจ็บทีหลัง)

ประเด็นผลกระทบทางออกมาตรฐาน
ค้นหา/filter ไม่ได้เข้ารหัสแล้ว WHERE citizen_id = ? ใช้ไม่ได้ (ciphertext ไม่เหมือนกันแม้ค่าเดิม)เพิ่ม column คู่เก็บ hash (HMAC-SHA256 ด้วย key แยก) ไว้ใช้ค้นแบบ exact-match — ค้น partial/LIKE ไม่ได้โดยธรรมชาติ ต้องยอมรับตั้งแต่ออกแบบ
index / joinindex บน column ที่เข้ารหัส = ไร้ความหมายใช้ hash column เป็นตัว index/join แทน
ความยาว columnciphertext + IV ยาวกว่า plaintextขยาย column เป็น text/varbinary ตามจริง — คิดตอน migration ไม่ใช่ตอนพัง
Key rotationเปลี่ยน key = ข้อมูลเก่าถอดไม่ออกเก็บ key version กำกับ (v1:...) — rotate แล้ว re-encrypt แบบ background ได้ทีละแถว
ของเก่าใน DBแถวเดิมเป็น plaintext อยู่แล้วmigration แบบ dual-read (อ่านได้ทั้งสองแบบ) → backfill → ปิดทาง plaintext — ห้าม big-bang

3. แบบที่ ② — Masking (เซ็นเซอร์ขาส่งออก)

ทำงานยังไง

Backend แปลงค่าก่อนใส่ response — ผู้รับได้ค่าที่เอากลับเป็นค่าเต็มไม่ได้:

เลขบัตรประชาชน  1-2345-67890-12-3  →  1-2345-xxxxx-12-3
เลขบัญชี         123-4-56789-0      →  xxx-x-xx789-0
เบอร์โทร         081-234-5678       →  08x-xxx-5678
อีเมล            somchai@exim.go.th →  som***@exim.go.th

⚠️ กฎเหล็ก: mask ที่ backend เท่านั้น — ทำไม FE mask เอง = ศูนย์

ความเข้าใจผิดคลาสสิกที่สุดของเรื่องนี้: “ส่งค่าเต็มมา แล้วให้หน้าจอแสดงเป็นดาวเอง”

  • ค่าเต็มออกจาก backend แล้ว = อยู่ใน Network tab ของ DevTools เรียบร้อย (กด F12 ดูได้ทุกคน)
  • อยู่ในมือของทุกคนกลางที่มองเห็น response (จนกว่าชั้น 3 ขา response จะมา — มติ 11)
  • อยู่ใน memory/state ของ FE — extension/สคริปต์ในหน้าอ่านได้
  • สรุป: FE mask = การตกแต่งหน้าจอ ไม่ใช่มาตรการ security — ค่าที่ไม่ควรเห็นต้องไม่ออกจาก backend ตั้งแต่แรก

กันอะไร — และหลักคิดตอนตัดสินใจ

  • กันการเปิดเผยเกินจำเป็น (data minimization ตาม PDPA): จอ list รายชื่อไม่จำเป็นต้องได้เลขบัตรเต็มทุกแถว — ได้ 4 ตัวท้ายพอให้ user ยืนยันว่าใช่คนนี้
  • กันตาคน: screenshot, จอ support/admin, คนเดินผ่านหลังโต๊ะ
  • กัน log รั่ว: response ที่ mask แล้ว ต่อให้หลุดไปอยู่ใน log/monitoring ก็เสียหายจำกัด
  • หลักตัดสินต่อ endpoint มีข้อเดียว: “หน้าจอนี้ต้องใช้ค่าเต็มเพื่อทำงานไหม” — ไม่ต้อง = mask เสมอ · ต้องจริง (เช่น จอแก้ไขข้อมูลตัวเอง, เอกสารทางการ) = ส่งเต็มได้ แต่ต้องรู้ตัวว่า endpoint นั้นกลายเป็นจุดอ่อนไหว → เป็นตัวเต็งอันดับแรกที่ชั้น 3 ขา response ต้องครอบ (มติ 11)

4. เทียบกันชัดๆ — และทำไมต้องมีทั้งคู่

① Encryption at rest② Masking
ปกป้องตอนไหนข้อมูลนอนอยู่ใน DBข้อมูลออกจาก backend
กันใครคนได้ DB ไป (dump/backup/query ตรง)คนเห็น response (หน้าจอ, log, คนกลาง)
ถอดกลับได้ไหมได้ — โดย app ที่มี key (ตั้งใจ)ไม่ได้ — ผู้รับได้ค่าบางส่วนถาวร
ใครทำBackend_Package (AES + KV key) ตอนเขียน/อ่าน DBBackend ตอนประกอบ response
โจทย์หลักPDPA security safeguardData minimization + ลด exposure ระหว่างรอชั้น 3
ถ้ามีแค่ตัวเดียวDB ปลอดภัย แต่ค่าเต็มยังไหลออกทุก responseขาออกสะอาด แต่ DB หลุดครั้งเดียว = plaintext ทั้ง table

จำง่ายๆ: encryption กันขโมยงัดตู้เซฟ · masking กันคนเห็นของบนโต๊ะ — ล็อกตู้เซฟแน่นแค่ไหน ถ้าวางของเต็มโต๊ะก็ไม่มีประโยชน์ และเก็บโต๊ะเรียบร้อยแค่ไหน ตู้เซฟไม่ล็อกก็จบเหมือนกัน

5. ต่อภาพกับของที่ทำอยู่ (3 ชั้นขา browser)

  • PII inventory ตัวเดียว ใช้ร่วมทุกเรื่อง (มติ 14): field ไหนอ่อนไหว → ตัวเดียวกันนี้คือรายชื่อ column ที่ต้อง encrypt at rest, รายชื่อ field ที่ชั้น 3 ต้อง encrypt ตอนเดินทาง, และรายชื่อที่ต้องมี mask rule ขาออก — จุดตัดสินใจเดียว ไม่ต้องไล่สามรอบ
  • Masking คือกติกาชั่วคราวของมติ 11 อยู่แล้ว: ระหว่างที่ชั้น 3 ขา response ยังไม่มา backend ต้อง mask field อ่อนไหวใน response — เล่มนี้คือเนื้อหาเต็มของคำว่า “mask” ในมติน้้น และต่อให้ชั้น 3 มาแล้ว masking ก็ไม่หายไป (จอที่ไม่ต้องใช้ค่าเต็มก็ยังไม่ควรได้ค่าเต็ม — encrypt ค่าที่ไม่จำเป็นต้องส่ง แพ้การไม่ส่งเสมอ)
  • ลำดับความสำคัญตอน implement: ทั้งสองแบบเป็นงาน backend ล้วน ไม่พึ่ง browser ไม่พึ่ง ShareLib — เดินคู่ขนานกับ POC ชั้น 2 ได้โดยไม่ชนกัน (คนละไฟล์ คนละ layer: at-rest อยู่ Infrastructure/entity config, masking อยู่ response mapping)

6. Checklist ต่อ 1 field (ใช้ตอนเดิน PII inventory)

  1. field นี้เป็นข้อมูลส่วนบุคคล/อ่อนไหวไหม → ใช่ = เข้า inventory
  2. ระบบต้องค้นหาด้วยค่านี้ไหม → ใช่ = ต้องมี hash column คู่ (ค้น exact-match เท่านั้น) · ไม่ = encrypt เดี่ยวๆ จบ
  3. หน้าจอ/ผู้รับไหนบ้างต้องใช้ค่าเต็ม → ระบุเป็นรายการ endpoint ที่ส่งเต็ม (ให้สั้นที่สุด) — ที่เหลือ mask หมด
  4. ค่า mask หน้าตาเป็นยังไง (กี่ตัวท้าย/รูปแบบ) → กำหนดเป็น rule กลางต่อประเภทข้อมูล ไม่ใช่ต่อ endpoint (เลขบัตรต้อง mask เหมือนกันทุกจอ)
  5. ข้อมูลเก่าใน DB มีอยู่แล้วไหม → มี = ต้องมีแผน backfill (dual-read → migrate → ปิดทาง plaintext)