Private Docs

Key 101 — กุญแจทุกดอกในระบบ: สร้างที่ไหน เก็บที่ไหน ใช้ตอนไหน

แผนที่กุญแจฉบับเดียวจบ — ระบบเรามีกุญแจ 6 ตระกูล อยู่คนละที่ ทำคนละหน้าที่ · หน้านี้ตอบว่าดอกไหนของใคร สร้างเมื่อไหร่ เก็บที่ไหน และเวลามันพังจะรู้ได้ยังไง

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

Key 101 — กุญแจทุกดอกในระบบ

หน้านี้เป็นแผนที่ ไม่ใช่ตำรา — แต่ละกลไกมีเล่ม 101 ของตัวเองอยู่แล้ว หน้านี้ตอบคำถามที่เล่มพวกนั้นตอบไม่ได้: “ตกลงเรามีกุญแจกี่ดอก ดอกไหนของใคร แล้วทำไมบางดอกอยู่ในเบราว์เซอร์ บางดอกอยู่ใน Key Vault”

กุญแจทั้งหมดในระบบ — 6 ตระกูล

#กุญแจเป็นของใครสร้างเมื่อไหร่ส่วนตัวเก็บที่ไหนส่วนสาธารณะเก็บที่ไหนใช้ทำอะไร
1ลายเซ็นผู้ใช้ (ECDSA P-256)ผู้ใช้ 1 คน ต่อ 1 เครื่องเบราว์เซอร์สร้างเองครั้งแรกที่ต้องใช้IndexedDB ในเบราว์เซอร์ (ดึงค่าออกมาไม่ได้)Redis ฝั่ง serverพิสูจน์ว่า ใครส่ง และเนื้อหาไม่ถูกแก้กลางทาง
2เข้ารหัส payload (RSA-OAEP-256)server — ทั้งระบบมีคู่เดียวทีมสร้างแล้ววางไว้ใน Key VaultKey VaultKey Vault + ประกาศออกทาง GET v1/security/jwksล็อกเนื้อคำขอ ไม่ให้คนกลางที่ถอด TLS อ่านได้
3เข้ารหัสข้อมูลใน DB (AES-256-GCM)serverทีมสร้างKey Vault (บนเครื่อง dev = user-secrets)ไม่มี — ดอกเดียวทั้งล็อกและไข ห้ามออกจาก server เด็ดขาดเข้ารหัส field อ่อนไหวก่อนลง DB
4ลิงก์ยกเลิกรับข่าว (HMAC)serverทีมสร้างKey Vaultไม่มี — ดอกเดียวเหมือน #3ทำ token ในลิงก์ให้ปลอมไม่ได้
5API key / Subscription keyservice ↔ serviceทีมออกให้Key Vault / APIMไม่มี — เป็น string ลับกันไม่ให้ service ปลอมเรียกกันเอง · รายละเอียด
6กุญแจเซ็น JWTEntra ID — ไม่ใช่ของเราMicrosoft จัดการไม่เคยอยู่ในมือเราEntra JWKS สาธารณะbackend เอา public ของ Entra มาตรวจ token · Sentinel แค่เก็บ/ส่งต่อ token ไม่ได้เซ็นเอง

#1-2 คือกุญแจคู่ (2 ดอกที่เกิดมาคู่กัน) · #3-5 คือกุญแจดอกเดียว (ล็อกและไขด้วยดอกเดียวกัน) — ความต่างนี้คือรากของทุกกฎด้านล่าง

กฎเดียวที่ต้องจำ

ส่วนตัวอยู่ฝั่งที่ต้อง “ลงมือทำ” · ส่วนสาธารณะอยู่ฝั่งที่แค่ “ตรวจ” หรือ “ล็อก”

  • เซ็น = ผู้ส่งลงมือ ⇒ ส่วนตัวต้องอยู่ที่ เบราว์เซอร์ ถ้า server ถือด้วย server ก็ปลอมลายเซ็นแทนผู้ใช้ได้ ⇒ ลายเซ็นหมดค่าทันที
  • ถอดรหัส = ผู้รับลงมือ ⇒ ส่วนตัวต้องอยู่ที่ server ถ้าเบราว์เซอร์ถือด้วย เท่ากับแจกให้ทุกคน (โค้ดหน้าเว็บใครก็เปิดอ่านได้) ⇒ เข้ารหัสไปก็ไม่มีความหมาย
  • กุญแจดอกเดียว (#3-5) ⇒ ห้ามให้เบราว์เซอร์เห็นทุกกรณี เพราะเห็น = ทั้งล็อกและไขได้

เทียบเป็นของจริง: กุญแจ #2 คือ แม่กุญแจของ server (แจกตัวล็อกให้ทุกคน แต่คนไขมีคนเดียว) · กุญแจ #1 คือ ตราประทับของผู้ใช้ (ผู้ใช้ประทับเอง server แค่ดูว่าลายตรงไหม)

ในคำขอเดียวใช้ 2 ดอกพร้อมกัน

เบราว์เซอร์  ①  ล็อกเนื้อหาด้วยส่วนสาธารณะของ server   (ดึงจาก JWKS)
             ②  ประทับตราด้วยส่วนตัวของตัวเอง          (IndexedDB)
server       ③  ตรวจตราด้วยส่วนสาธารณะของผู้ใช้        (Redis)   ← ใครส่ง
             ④  ไขกล่องด้วยส่วนตัวของตัวเอง            (Key Vault) ← อ่านเนื้อหา

🔴 ลำดับ ③ ก่อน ④ ห้ามสลับ — ลายเซ็นเซ็นทับ “ก้อนที่ล็อกแล้ว” ตามที่วิ่งมาบนสาย ถ้าไขก่อนไบต์จะเปลี่ยน ตรวจลายเซ็นไม่ผ่านทันที และยังเปิดให้คนที่ยังไม่ผ่านการตรวจสั่งให้ server ไขกล่องได้ด้วย

ที่เก็บกุญแจมี 5 ที่ — และมันทับกันเป็นชั้น

ที่เก็บเก็บอะไรใครอ่านได้
Azure Key Vaultกุญแจและความลับฝั่ง server ทุกดอกservice ที่ตั้งค่าให้ต่อตู้
CSI ของ AKSตัวส่งค่าจากตู้เข้า pod เป็น environment variablepod นั้น
user-secretsของบนเครื่อง dev เท่านั้น ไม่เคยขึ้น environment จริงเครื่องคนเขียนโค้ด
IndexedDBส่วนตัวของผู้ใช้ดอกเดียว (#1)เบราว์เซอร์เครื่องนั้น
Redisส่วนสาธารณะของผู้ใช้ + token ของ sessionservice

เส้นทางค่าเข้าแอป:

  • บนเครื่อง dev — ใส่ URL ของตู้ไว้ใน appsettings → ตอน service start มันดูดค่าจากตู้เข้ามาเป็น config เอง
  • บน environment จริง — ไม่ใส่ URL ตู้ ปล่อยให้ CSI เอาค่าจากตู้เดียวกันมา mount เป็น environment variable แทน

⚠️ ลำดับทับซ้อนบนเครื่อง dev: user-secrets ทับ Key Vault ทับ environment variable ทับ appsettings คีย์ชื่อเดียวกันที่ยังค้างใน user-secrets จะ ชนะค่าที่อยู่ในตู้เสมอ — เป็นสาเหตุอันดับหนึ่งของอาการ “แก้ที่ตู้แล้วไม่มีผล”

การตั้งชื่อ: ชื่อในตู้ dev--user-service--Section--Key จะกลายเป็น config Section:Key ในแอป (--:) · ทาง CSI จะมาเป็น Section__Key (__:) — คนละหน้าตาแต่ปลายทางคือคีย์เดียวกัน

กุญแจผู้ใช้ (#1) — จุดที่คนเข้าใจผิดบ่อยที่สุด

❌ “login ใหม่ 1 ครั้ง = สร้างกุญแจคู่ใหม่ 1 ครั้ง” ✅ สร้างครั้งเดียวต่อเครื่อง — login รอบต่อไปใช้คู่เดิม

  • โค้ดฝั่งเบราว์เซอร์ทำ “มีอยู่แล้วใช้เลย ไม่มีค่อยสร้าง” — อ่านจาก IndexedDB ก่อนเสมอ
  • สร้างแบบ ดึงค่าออกมาไม่ได้ (extractable: false) ⇒ แม้แต่โค้ดของเราเองก็อ่านส่วนตัวไม่ได้ สั่งได้อย่างเดียวคือ “เซ็นให้หน่อย”
  • ผูกกับ ตัวตนของผู้ใช้ (oid) ไม่ใช่ session ⇒ login ใหม่ไม่ต้องผูกใหม่
  • ตอนฝากส่วนสาธารณะ: คนแรกชนะ — ส่งดอกเดิมซ้ำผ่านเงียบ ๆ แต่ส่งคนละดอกทั้งที่มีของเดิมอยู่จะถูกปฏิเสธด้วย KEY_CONFLICT
  • ฝั่ง server เก็บส่วนสาธารณะไว้ 7 วัน — ไม่ได้ใช้เกินนั้นมันหายไป แต่กุญแจในเบราว์เซอร์ยังอยู่ จึงแค่ฝากใหม่ได้เลย
  • การฝากเกิดขึ้นแบบ ตอนที่จำเป็นเท่านั้น: interceptor เซ็นคำขอ → ถ้า server ตอบว่ายังไม่รู้จักกุญแจนี้ ถึงค่อยฝากแล้วยิงซ้ำ (ไม่ใช่ฝากตอน login)

🔴 ช่องที่ยังไม่ปิด: 1 ผู้ใช้ = 1 กุญแจ ⇒ เปิดเครื่องที่สอง/เบราว์เซอร์ที่สองภายใน 7 วันจะชน KEY_CONFLICT และยังไม่มีวิธีถอนกุญแจเก่า — ใช้หลายเครื่องพร้อมกันยังไม่รองรับ

พื้นฐานว่ากุญแจคู่เซ็น/ตรวจยังไง: Body Signature 101

กุญแจ payload ของ server (#2) — เปลี่ยนดอกยังไงไม่ให้ระบบล่ม

  • ไม่ต้องแจ้งใครล่วงหน้า — ทุกดอกมีชื่อกำกับ (kid) เบราว์เซอร์ดึงรายการจาก JWKS ใหม่ทุกครั้งอยู่แล้ว
  • เปลี่ยนดอก = วางดอกใหม่ในตู้ด้วยชื่อใหม่ แล้วเอาดอกเก่าออกจากรายการ
  • ⚠️ ตัวหน้าบ้านหยิบ ดอกแรกในรายการ ⇒ ถ้าปล่อยให้มี 2 ดอกพร้อมกันมันอาจหยิบดอกเก่าเงียบ ๆ · ตอนเปลี่ยนต้องเหลือดอกเดียว
  • ⚠️ แก้ค่าในตู้แล้ว ต้อง restart service — ตัวดูดค่าตั้งรอบรีเฟรชไว้ 30 นาที ไม่ใช่ทันที
  • ⚠️ service อื่นที่ตั้งให้ต่อตู้เดียวกันจะโหลดความลับทั้งตู้เข้าหน่วยความจำของตัวเอง ไม่ได้กรองเฉพาะของตัวเอง — เรื่องนี้มีบันทึกแยกในเอกสารภายใน

เหตุผลว่าทำไมต้องเข้ารหัสทั้งที่มี HTTPS แล้ว: Encryption Field 101 · สเปคเต็มของชั้นนี้: สเปคชั้น 3

กุญแจข้อมูลใน DB (#3) — คนละเรื่องกับ #2 คนละความเสี่ยง

  • #2 ปกป้อง “ระหว่างทาง” · #3 ปกป้อง “ตอนนอนอยู่ใน DB” — คนละช่วงชีวิตของข้อมูล
  • เป็นกุญแจ ดอกเดียว ⇒ ใครได้ไปคือถอดได้ทั้งหมดย้อนหลัง — ความเสี่ยงหนักกว่า #2 มาก เพราะ #2 หลุดแค่ทำให้อ่านคำขอใหม่ได้ แต่ #3 หลุดคืออ่านข้อมูลเก่าทั้งฐาน
  • เปลี่ยนดอกต้อง เก็บดอกเก่าไว้ด้วย ไม่งั้นข้อมูลเดิมที่ล็อกด้วยดอกเก่าจะอ่านไม่ออก — ระบบจึงเก็บหลายดอกพร้อมกันแล้วชี้ว่า “ดอกไหนใช้ล็อกของใหม่”

รายละเอียดว่าเข้ารหัสกับปิดบัง (masking) ต่างกันยังไง: Data Encryption & Masking 101

คำถามที่ถามกันบ่อย

ส่วนสาธารณะหลุดเป็นไรไหม? ไม่ — มันถูกออกแบบมาให้แจก · ของ server ประกาศทางเน็ตอยู่แล้ว ใครโหลดก็ได้ · แต่ กุญแจดอกเดียว (#3-5) ไม่มีคำว่า “ส่วนสาธารณะ” หลุดคือจบ

ทำไมกุญแจของ server ต้องอยู่ Key Vault ไม่เก็บไว้ในโปรเจกต์? ของในโปรเจกต์ติดไปกับเครื่องคนเขียนโค้ดและอาจหลุดขึ้น git ได้ · ตู้เก็บให้ที่เดียว ควบคุมสิทธิ์ได้ เปลี่ยนได้โดยไม่ต้องแก้โค้ดหรือ deploy ใหม่ · และ environment จริงก็ดึงจากตู้เดียวกันผ่าน CSI อยู่แล้ว ⇒ ใช้กลไกเดียวกันทั้งเครื่อง dev และของจริง

ผู้ใช้ล้างข้อมูลเบราว์เซอร์แล้วเกิดอะไรขึ้น? กุญแจ #1 หายไปพร้อมกัน เบราว์เซอร์จะสร้างคู่ใหม่ให้เอง · ถ้าของเดิมฝั่ง server ยังไม่หมดอายุ 7 วัน จะชน KEY_CONFLICT — ดูช่องที่ยังไม่ปิดด้านบน

เข้ารหัส payload แล้วกัน XSS ได้ไหม? ไม่ได้ และห้ามอ้างแบบนั้น — สคริปต์ที่ฝังอยู่ในหน้าเว็บเห็นข้อมูลตั้งแต่ก่อนถูกเข้ารหัสอยู่แล้ว · XSS ต้องแก้ที่หน้าบ้าน: Frontend Hardening 101

กุญแจแต่ละดอกอายุเท่าไหร่? #1 ฝั่ง server เก็บ 7 วัน (ฝั่งเบราว์เซอร์อยู่จนกว่าจะล้าง) · #2-#5 ไม่มีวันหมดอายุอัตโนมัติ เปลี่ยนเมื่อเราสั่งเปลี่ยนเท่านั้น · ส่วนอายุ session เป็นคนละเรื่อง: Session Lifetime 101