Skip to content

บทที่ 4 — Cryptography for Devs

← บทที่ 3 | สารบัญ | บทที่ 5 →

📒 มีศัพท์ที่ไม่คุ้น? ดู ตารางคำศัพท์ + คำย่อ ในบทที่ 0 ข้อ 18 หรือ glossary หลัก

หลังจบบท คุณจะ:

  • เข้าใจ Hash, HMAC, Encryption (symmetric / asymmetric), Signature
  • เลือก algorithm + library ถูก
  • เข้าใจ TLS ทำงานยังไง
  • ใช้ key management (generate, rotate, store)
  • เลี่ยง pitfall ที่พบบ่อย

กฎทอง: "อย่าเขียน crypto เอง — ใช้ library ที่คนอื่นทดสอบมาแล้ว" (ฝรั่งเรียก "Don't roll your own crypto" — "roll" ที่นี่หมายถึง "ทำขึ้นเอง" ไม่ใช่ "กลิ้ง") — ใช้ library มาตรฐาน ไม่เขียนเอง

🗺️ บทนี้อ่านยังไง — ถ้าไม่เข้าใจ ข้ามได้

บทนี้เป็นบทที่ "ทฤษฎีหนักสุด" ในเล่ม มีคณิตศาสตร์และศัพท์เฉพาะเยอะ — ไม่ต้องเข้าใจทุกอย่างก็ได้

ถ้าจำได้แค่ประโยคเดียวจากบทนี้ ให้จำว่า: "ใช้ library มาตรฐาน อย่าเขียน crypto เอง" เท่านี้ก็ปลอดภัยกว่า 90% ของคนทั่วไปแล้ว

ระดับอ่านยังไง
มือใหม่อ่านเอา "ความรู้สึก" ว่าแต่ละอย่าง (hash, encrypt, sign) ใช้ทำอะไร + จำกฎทองข้างบน · ส่วนที่มี ⚠️ pitfall (ข้อ 18) อ่านให้รู้ว่า "อย่าทำอะไร" · รายละเอียด algorithm/โค้ด ข้ามได้
พอเขียนโค้ดเป็นทำตามตัวอย่าง AES-GCM (ข้อ 5) + HMAC (ข้อ 4) + SecureRandom (ข้อ 8) ได้ · ที่เหลืออ่านเอาหลักการ
ทำงานจริง/มี cloudลงลึก envelope encryption (ข้อ 15) + KMS + key rotation ได้เลย

👉 ตัวอย่างโค้ดในบทนี้เป็น Java เป็นหลัก — ใช้ภาษาอื่นให้อ่านเอาหลักการแล้วหา crypto library ของภาษาตัวเอง


1. ทำไม Cryptography ยาก

"นี่ algorithm ปลอดภัย" — แต่ implement ผิด → ไม่ปลอดภัย
"นี่ library ดี" — แต่ใช้ผิด mode → ไม่ปลอดภัย
"ทำงานได้" — แต่ side channel leak → ไม่ปลอดภัย

📒 ศัพท์ที่จะเจอในย่อหน้าถัดไป (อ่านก่อนเข้าตัวอย่าง):

  • ECB (อ่าน อี-ซี-บี) = Electronic Codebook — โหมดที่ encrypt แต่ละก้อนแยกกันโดยไม่ผูกกัน ทำให้ก้อนเหมือนกัน → ผลลัพธ์เหมือนกัน (เห็น pattern)
  • IV (อ่าน ไอ-วี) = Initialization Vector — ค่าสุ่มที่ใส่เข้าไปเพื่อให้ผลลัพธ์ของการ encrypt ข้อความเดียวกันต่างกันทุกครั้ง
  • padding = การเติมไบต์ท้ายข้อมูลให้ครบขนาด block (เช่น 16 bytes สำหรับ AES)
  • side channel = ช่องโหว่ที่ไม่ใช่ตัว algorithm — แต่รั่วผ่าน "ผลข้างเคียง" เช่น เวลา, พลังงาน, เสียงที่ใช้

ตัวอย่างจริง:

  • ECB mode ของ AES → encrypted แต่ยังเห็น pattern
  • ใช้ random IV ซ้ำ → ทำลายความปลอดภัย
  • Implement RSA เอง → padding error → key recovery

ใช้ library + standard mode. Cryptographer เก่ง ๆ ยังพลาด


2. ประเภทของ Cryptography

"cryptography" ไม่ได้มีแค่ "เข้ารหัส" — มันเป็นกลุ่มเครื่องมือหลายแบบที่ใช้คนละงาน: hash (ตรวจ integrity), HMAC, symmetric/asymmetric encryption, digital signature, key exchange และ secure random เข้าใจภาพรวมนี้ก่อนแล้วทั้งบทจะต่อกันติด:

1. Hash function           — text → digest (ลายเซ็นย่อ, one-way = ทำกลับไม่ได้)
2. HMAC                     — hash + key (verify integrity + authenticity)
3. Symmetric encryption    — 1 key, encrypt + decrypt
4. Asymmetric encryption    — public/private key pair
5. Digital signature         — sign with private, verify with public
6. Key exchange              — ตกลง shared key ผ่าน public channel
7. Random number generation  — secure randomness

3. Hash Function

input (any size) → hash function → digest (fixed size)
"hello"           → SHA-256        → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

คุณสมบัติของ Hash ที่ดี

  • One-way (ทำกลับไม่ได้) — reverse ไม่ได้
  • Deterministic (ผลลัพธ์คงเดิม) — input เดิม = output เดิมเสมอ
  • Avalanche (ผลกระเพื่อม) — เปลี่ยน input 1 bit → output เปลี่ยนเกือบหมด
  • Collision resistance (กันชน) — หา 2 input ที่ hash เท่ากันยาก

Algorithm ที่ใช้

MD5         128-bit  ❌ broken (collisions found)
SHA-1       160-bit  ⚠️ broken สำหรับ collision (SHAttered, Google 2017)
                     — ยังถือว่าปลอดภัยสำหรับ HMAC แต่ห้ามใช้กับ signature/cert ใหม่
SHA-256     256-bit  ✅ standard
SHA-512     512-bit  ✅ standard (faster on 64-bit CPU)
SHA-3       variable ✅ modern alternative (different design)
SHA-3 SHAKE-128/256  ✅ XOF (extendable-output) — variable length output, ใช้แทน HKDF บางกรณี
BLAKE2b     up to 512-bit ✅ modern, fast — ใช้ภายใน Argon2, WireGuard
BLAKE3      256-bit  ✅ modern, very fast (อ่าน "เบลค-ทรี")

ใช้สำหรับ

✅ File integrity (SHA-256 checksum)
✅ Detect change (Git uses SHA-1 — for non-security purpose)
✅ Index (hash table)

❌ Password — ใช้ slow hash แทน (BCrypt, Argon2)
❌ Symmetric encryption — hash ไม่ใช่ encryption

ตัวอย่าง

java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;

public class HashExample {
    public static String sha256Hex(String text) throws NoSuchAlgorithmException {
        byte[] data = text.getBytes(StandardCharsets.UTF_8);
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(data);
        return HexFormat.of().formatHex(hash);
        // "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    }
}

📌 Snippet ต่อๆ ไปในบทนี้จะ ไม่ใส่ import statement ซ้ำ เพื่อให้อ่านโค้ดสำคัญง่ายขึ้น แต่ทุกตัวอย่าง compile ได้ถ้ามี import ตามมาตรฐาน Java (java.security.*, javax.crypto.*, java.nio.charset.StandardCharsets, java.util.*)


4. HMAC — Hash-based Message Authentication

HMAC = hash(secret + message) → ผู้ที่มี secret เท่านั้นที่สร้างได้

ใช้สำหรับ

  • Webhook signature
  • JWT (HS256)
  • API request signing
  • Cookie integrity

ตัวอย่าง

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

String message = "amount=100&to=alice";
String secret = "shared-secret-key";

Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
byte[] sig = mac.doFinal(message.getBytes());
String sigHex = HexFormat.of().formatHex(sig);

Webhook ตัวอย่าง (Stripe-style)

Server ส่ง webhook:
  POST https://client.com/webhook
  X-Signature: t=1747562400,v1=abc123...
  body: {"event":"payment.success"}

Client verify:
  computed = HMAC-SHA256(secret, "1747562400." + body)
  ถ้า computed != provided signature → reject (อาจเป็น forge)

⚠️ Constant-Time Comparison

java
// ❌ Time-leaking comparison (early exit)
if (sig1.equals(sig2)) { ... }

// ✅ Constant time — `MessageDigest.isEqual` คือ API ที่ JDK แนะนำสำหรับเทียบ byte[] แบบ constant-time
if (MessageDigest.isEqual(sig1.getBytes(), sig2.getBytes())) { ... }

→ ป้องกัน timing attack — attacker วัดเวลา response เพื่อเดา sig ทีละ byte

📌 เวอร์ชันสำคัญ (timeline ที่จริง):

  • JDK 6 update 17 (2009) เริ่มเปลี่ยน MessageDigest.isEqual เป็น constant-time (CVE-2009-3875)
  • JDK 7+ การันตี constant-time implementation อย่างเป็นทางการ
  • ถ้ายัง maintain โค้ด Java เก่ามาก (ก่อน 6u17) → implement comparison เอง (loop ที่ XOR ทุก byte แล้วเช็คท้ายสุด)

⚠️ caveat สำคัญ: MessageDigest.isEqual เป็น constant-time เฉพาะเมื่อ length เท่ากัน — ถ้า length ต่างจะ return false ทันที (early-return) ซึ่ง leak length ได้ ถ้ากลัว length leak ให้ pad ให้ยาวเท่ากันหรือ check length เท่ากันก่อนเรียก แล้วเปรียบเทียบ length ผ่าน constant-time function

  • Python: hmac.compare_digest()
  • Node.js: crypto.timingSafeEqual()
  • Go: subtle.ConstantTimeCompare()

5. Symmetric Encryption

1 key — ใช้ทั้ง encrypt และ decrypt

text + key → encrypt → cipher
cipher + key → decrypt → text

Algorithm

DES                    ❌ broken (56-bit key, breakable)
3DES                   ❌ deprecated (slow + 64-bit block)
AES-128 (เอ-อี-เอส 128) ⚠️ acceptable สำหรับ short-lived data
                       — Grover's algorithm (quantum) ลด strength เหลือ 64-bit ~ marginal
AES-256                ✅ ⭐ recommended baseline (PQ-resistant via Grover)
ChaCha20 (อ่าน "ชา-ชา-ทเวนตี้") + Poly1305  ✅ modern AEAD,
                       เร็วบน ARM/mobile ที่ไม่มี AES hardware
XChaCha20-Poly1305     ✅ extended nonce (192-bit) — ทน nonce-misuse ดีกว่า

Mode of Operation — สำคัญมาก

📒 mental model:

  • block cipher = อัลกอริทึมที่ encrypt ทีละก้อน 128-bit (16 bytes) ก้อนเดียวเท่านั้น (เช่น AES คือ "ฟังก์ชัน" รับ key + 16 bytes → 16 bytes)
  • mode (โหมด) = วิธี "จัดเรียง" หลายก้อนต่อ ๆ ไปให้ดูเหมือนสุ่ม (เพราะถ้าใช้ block cipher ตรง ๆ ทุกก้อนจะ encrypt แยกกัน → ก้อนเหมือนกัน = cipher เหมือนกัน = leak pattern)

แปลคร่าว ๆ: block cipher = "เครื่องเข้ารหัสทีละกล่อง", mode = "วิธีลำเลียงกล่องต่อ ๆ ไป"

Modeความปลอดภัยใช้เมื่อ
ECB❌ ห้าม(insecure — เห็น pattern)
CBC❌ avoid for new codelegacy only — ต้อง encrypt-then-MAC + random IV + ป้องกัน padding oracle ถูก ถึงจะปลอดภัย
GCM✅ มาตรฐานสมัยใหม่ — AEAD มี authentication ในตัว
CTR⚠️ ดีstreaming — แต่ไม่มี authentication, ต้องคู่กับ MAC

⚠️ ECB Mode — ห้ามใช้

text
ECB encrypt block ตรงๆ → block เหมือนกัน → cipher เหมือนกัน
→ pattern ใน image ยังเห็นชัดเจน

🖼️ ภาพประกอบ classic — "Tux penguin ECB": ลองเข้าหน้านี้ของ Wikipedia เพื่อดูภาพเทียบ ECB vs other mode (public domain): https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_(ECB)

ภาพเพนกวินที่ encrypt ด้วย ECB ยังเห็นเค้าโครงเพนกวินครบ — ส่วน mode อื่น (CBC/CTR/GCM) จะกลายเป็น noise ไร้รูปร่าง นี่คือ "visual proof" ของทำไม ECB ห้ามใช้กับ data ที่มี pattern

AES-GCM ใน Java

ก่อนดูโค้ด — mental model ของ AEAD/AAD/tag/IV:

  • AEAD (อ่าน เอ-อี-เอ-ดี) = Authenticated Encryption with Associated Data — "เข้ารหัส + ตรวจความถูกต้อง ในก้อนเดียว" (ไม่ต้องเรียก HMAC แยก)
  • IV / nonce = ค่าสุ่ม 12 bytes ที่ใส่เข้าไปคู่กับ key — ทำให้ encrypt ข้อความเดิม 2 ครั้งได้ ciphertext ต่างกัน · ห้ามซ้ำต่อ key เดียวกัน เด็ดขาด
  • AAD = ข้อมูลแถม (associated data) ที่จะ "ตรวจ integrity" แต่ "ไม่ encrypt" — เช่น user_id, version, file_id (อยู่ใน metadata แบบ public แต่ห้ามถูกสลับ)
  • tag = ลายเซ็น 16 bytes ที่ติดท้าย ciphertext — ตอน decrypt ถ้า tag ผิดแสดงว่า ciphertext/AAD ถูกแก้
  • GCM (อ่าน จี-ซี-เอ็ม) = Galois/Counter Mode — โหมดที่ implement AEAD

ทีนี้ดูโค้ด:

java
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.SecureRandom;

// 1. Generate key (256-bit = 32 bytes)
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
SecretKey key = kg.generateKey();

// 2. Generate IV (12 bytes for GCM)
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);

// 3. Encrypt
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
// 128 = tag length (bits) = 16 bytes ต่อท้าย ciphertext; ห้ามใช้ < 96

// 3.1 ผูก context กับ ciphertext ผ่าน AAD
byte[] aad = ("user_id=42;version=1").getBytes(StandardCharsets.UTF_8);
cipher.updateAAD(aad);

byte[] cipherText = cipher.doFinal("secret message".getBytes(StandardCharsets.UTF_8));

// 4. Decrypt — ต้อง updateAAD ด้วย AAD ตัวเดียวกันก่อน doFinal
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
cipher.updateAAD(aad);
byte[] plainText = cipher.doFinal(cipherText);

ทำไม tag 128-bit: ถ้าใช้ tag สั้นกว่า 96-bit attacker สุ่มถูกได้ง่าย — NIST แนะนำ 128-bit สำหรับ general use

ทำไม IV ซ้ำ = หายนะ: ถ้า attacker เห็น ciphertext 2 ตัวที่ใช้ (key, IV) เดียวกัน → XOR กันแล้วได้ "plaintext1 XOR plaintext2" (key หายไป) + recover authentication key → forge ciphertext ใด ๆ ก็ได้ (ไม่ใช่แค่ decrypt ที่เคยเห็น) นี่คือเหตุผลที่ GCM IV reuse ถือว่า "catastrophic"

AAD จริง ๆ ใช้ยังไง: ถ้า attacker copy ciphertext ของ user A ไปแปะเป็นของ user B (โดยที่ user_id อยู่นอก ciphertext) — ตอน decrypt ฝั่ง B ใส่ AAD="user_id=B" → tag ไม่ตรง → fail ทันที กัน "ciphertext substitution attack"

⚠️ Cipher object ไม่ thread-safe: ต้อง init() ใหม่ทุกครั้งก่อนใช้ — ห้าม cache Cipher object แล้วเอาไปใช้ข้าม request/thread จะเจอ data race แบบ silent (ciphertext เพี้ยน) ถ้าต้องการ pool ให้ใช้ ThreadLocal<Cipher> หรือสร้างใหม่ทุกครั้ง (getInstance เร็วพอ)

⚠️ GCM IV birthday bound — สำคัญสำหรับ high-throughput (⚠️ ขั้นสูง — มือใหม่ข้ามได้):

"Birthday bound" (birthday paradox) = ปรากฏการณ์ทาง probability ที่ collision เกิดเร็วกว่าที่คนทั่วไปคิด · ตัวอย่างคลาสสิก: ในห้องที่มีคน 23 คน โอกาสที่จะมี 2 คนเกิดวันเดียวกันคือ ~50% (ไม่ใช่ 365/23) — เพราะเราเทียบทุกคู่ ไม่ใช่เทียบกับวันใดวันหนึ่ง · กับ random 96-bit IV หลังจาก ~2¹⁶ messages ก็เริ่มมีโอกาส collision แล้ว

NIST SP 800-38D กำหนด 2³² invocations per key เป็น hard limit (ไม่ใช่แค่ "ระวัง collision" — เป็น mandate) เพื่อรักษา collision probability < 2⁻³² · ต้อง rotate key เมื่อใกล้ถึง

Random 96-bit IV ปลอดภัยถึงประมาณ 2³² messages (~4 พันล้าน) ต่อ key

  • High-volume system (เช่น log encryption, message queue) → พิจารณา AES-GCM-SIV (RFC 8452) ที่ทน nonce-reuse ได้
  • หรือใช้ counter-based IV (deterministic, track ใน DB) — ไม่ใช่ random
  • ภาษาอื่น: XChaCha20-Poly1305 (อ่าน เอ็กซ์-ชา-ชา-ทเวนตี้ โพ-ลี-วัน-เธอเรอะ-โอ-ไฟว์) มี 192-bit nonce → random ปลอดภัยมากกว่ามาก

อันตรายของ GCM IV reuse: ไม่ใช่แค่ leak — ถ้า IV ซ้ำกับ key เดียวกัน attacker จะ recover authentication key และ forge ciphertext + decrypt ของเก่าทั้งหมดที่ใช้ key นั้น (catastrophic ไม่ใช่ degraded)

⚠️ IV (Initialization Vector) — กฎ

  • ต้องสุ่มใหม่ทุกครั้ง (สำหรับ GCM/CBC) — ถ้าซ้ำ → ความปลอดภัยพัง
  • เก็บคู่กับ cipher text (ไม่ลับ — แต่ห้ามซ้ำ)
  • GCM IV = 12 bytes (96-bit)
java
// Store format: IV (12 bytes) + cipher + tag
ByteBuffer buf = ByteBuffer.allocate(iv.length + cipherText.length);
buf.put(iv);
buf.put(cipherText);
byte[] combined = buf.array();

6. Asymmetric Encryption (Public-Key)

มี 2 key:
- Public key  — แจกให้ใครก็ได้ (encrypt + verify signature)
- Private key — เก็บเป็นความลับ (decrypt + sign)

Alice → ขอ public key ของ Bob
Alice → encrypt ข้อความ ด้วย Bob's public key
Bob   → decrypt ด้วย private key

Algorithm (ปี 2026 — เรียงตามที่ "ควรเลือกใช้ก่อน")

Ed25519 (อ่าน "อีดี-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์")
        ✅ ⭐ recommended สำหรับ signing ในระบบใหม่
        — key เล็ก (32 bytes), เร็ว, ไม่มี side-channel pitfall
X25519 / Curve25519 (อ่าน "เคิร์ฟ-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์")
        ✅ ⭐ recommended สำหรับ key agreement (TLS 1.3, Signal, WireGuard)
ECDSA P-256 / P-384
        ✅ interop (JWT ES256, smart card, FIPS)
RSA-2048+
        ⚠️ legacy interop only — slow, key ใหญ่, ยังจำเป็นเพราะ ecosystem (TLS cert ส่วนใหญ่, JWT RS256)
        — minimum 2048-bit สำหรับใช้งานปกติ, 4096-bit สำหรับ new high-security
        — encryption ใช้ RSA-OAEP, signature ใช้ RSA-PSS (ไม่ใช่ PKCS#1 v1.5)

Use Cases

  • Encrypt small data (e.g., AES key) — RSA-OAEP (legacy) หรือ ML-KEM (post-quantum, §20)
  • Digital signature — Ed25519 (ใหม่), ECDSA (interop), RSA-PSS (legacy)
  • TLS handshake — exchange symmetric key ผ่าน X25519 (TLS 1.3 default)

⚠️ RSA ช้า + จำกัด size

RSA encrypt ได้แค่ data ขนาดเล็ก — สูตรขีดจำกัด RSA-OAEP-SHA256:

Key sizeMax plaintext
2048-bit190 bytes
3072-bit318 bytes
4096-bit446 bytes

สูตร: max_bytes = (key_size / 8) - 2 * hash_len - 2 (4096-bit: 512 - 2*32 - 2 = 446)

📌 ที่มาของสูตร: มาจาก RSA-OAEP padding spec (PKCS#1 v2.2) — ต้องเหลือพื้นที่สำหรับ 2 hash + 2 byte overhead · ไม่ต้องจำสูตร ใช้ตารางพอ ถ้าจะ encrypt ของใหญ่กว่าให้ใช้ hybrid (ดูข้างล่าง)

อย่า encrypt data ใหญ่กว่านี้ตรง ๆ — จะ fail (IllegalBlockSizeException ใน Java) ✅ ใช้ hybrid encryption / envelope encryption (RSA encrypt AES key, AES encrypt data) — ดู §15

→ Pattern จริง: hybrid encryption

1. Generate random AES key
2. Encrypt message ด้วย AES (fast, no size limit)
3. Encrypt AES key ด้วย RSA public key
4. Send: { encrypted_aes_key, encrypted_message }

🔍 ภาพรวม hybrid encryption:

ทำไมต้อง 2 step: RSA encrypt data ใหญ่ไม่ได้ + ช้ามาก · AES encrypt data ใหญ่ได้เร็ว แต่ทั้ง 2 ฝ่ายต้อง share key ก่อน — ปัญหาคือจะ share ยังไงปลอดภัย · hybrid = ใช้ RSA แค่ share AES key (เล็ก) + AES ทำ heavy lifting

ตัวอย่าง

⚠️ อ่านก่อน — "RSA/ECB/..." ตรงนี้ "ไม่ใช่" ECB mode ที่ห้ามใช้:

RSA ไม่ได้ใช้ block cipher mode (ECB/CBC/GCM ที่เราพูดถึงใน §5) — RSA เข้ารหัสเป็น "1 block ใหญ่" (modular exponentiation บนเลขทั้งก้อน) ไม่มี chaining ระหว่าง block เลย

ที่ JCA API ใส่คำว่า "ECB" ใน transformation string RSA/ECB/... เป็น quirk ของ Java เท่านั้น — แปลว่า "single-block RSA operation" ไม่ได้แปลว่าใช้ ECB mode แบบ AES · padding ที่ใช้จริงคือ OAEP กับ SHA-256 + MGF1 ซึ่งปลอดภัย

สรุป: เห็น "RSA/ECB/OAEPWith..." ใน Java = OK · เห็น "AES/ECB/..." = ห้ามใช้

java
// Generate key pair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair pair = kpg.generateKeyPair();
PublicKey pub = pair.getPublic();
PrivateKey priv = pair.getPrivate();

// Encrypt with public (padding = OAEP-SHA256-MGF1 — ปลอดภัย)
Cipher c = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
c.init(Cipher.ENCRYPT_MODE, pub);
byte[] cipherText = c.doFinal("secret".getBytes());

// Decrypt with private
c.init(Cipher.DECRYPT_MODE, priv);
byte[] plainText = c.doFinal(cipherText);

7. Digital Signature

digital signature พิสูจน์ 2 อย่างพร้อมกัน: "ข้อความมาจากคนนี้จริง" (authenticity) และ "ไม่ถูกแก้ระหว่างทาง" (integrity) — โดยใช้ private key เซ็น และให้ใครก็ได้ verify ด้วย public key มันคือรากฐานของ JWT, code signing และ cryptocurrency:

Alice ต้องการพิสูจน์ว่า "ข้อความนี้มาจากฉัน + ไม่ถูกแก้":

1. Alice → hash ข้อความ
2. Alice → sign hash ด้วย private key
3. แนบ signature ไปกับข้อความ

Bob verify:
1. hash ข้อความ
2. ใช้ Alice's public key verify signature
3. ถ้าผ่าน → ของ Alice แน่นอน + ไม่ถูกแก้

ใช้สำหรับ

  • JWT (RS256, ES256)
  • Code signing (verify download)
  • Email signing (S/MIME, PGP)
  • Document signing (PDF)
  • Cryptocurrency (Bitcoin tx)

ตัวอย่าง

java
// Sign
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(message.getBytes());
byte[] signature = signer.sign();

// Verify
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey);
verifier.update(message.getBytes());
boolean valid = verifier.verify(signature);

8. SecureRandom — Random ที่ปลอดภัย

java
import java.security.SecureRandom;

SecureRandom rnd = new SecureRandom();
// ไม่ต้อง seed เอง — JVM seed อัตโนมัติจาก OS entropy
// (/dev/urandom บน Linux/macOS, CryptGenRandom บน Windows)
byte[] token = new byte[32];
rnd.nextBytes(token);

String tokenStr = Base64.getUrlEncoder().withoutPadding().encodeToString(token);
// "X1B5KqV4yzC..."

⚠️ ห้ามใช้ Math.random() หรือ new Random() สำหรับ security

java
// ❌ Predictable
Random r = new Random();
String token = String.valueOf(r.nextLong());

// ✅ Secure
SecureRandom sr = new SecureRandom();
byte[] tokenBytes = new byte[32];
sr.nextBytes(tokenBytes);

ทำไม? — Random ใช้ seed → predict ได้ถ้าเดา seed เจอ

Token Length

Session token:      128-bit (16 bytes)
API key:             128-bit
Reset token:         256-bit (32 bytes) — high security
Encryption key:      128 / 256-bit
UUID v4:             128-bit

9. Key Derivation

"ทำ key จาก password" — ห้ามใช้ password ตรง ๆ เป็น encryption key

Password → KDF (Key Derivation Function) → encryption key

            slow + memory-hard

📒 คำอ่าน: KDF = "เค-ดี-เอฟ", PBKDF2 = "พี-บี-เค-ดี-เอฟ-ทู", Argon2id = "อาร์-กอน-ทู-ไอ-ดี", bcrypt = "บี-คริปต์", scrypt = "เอส-คริปต์"

Algorithm (เรียงตามที่ "ควรเลือกใช้ก่อน" ปี 2026 — OWASP 2024)

  • Argon2id ⭐ — modern, memory-hard, OWASP 2024 primary recommendation สำหรับ password hashing
  • scrypt — memory-hard, acceptable alternative
  • bcrypt cost 12+ — ยังใช้ได้ (timing target 250-500ms บน CPU 2026, ไม่ใช่ 30-100ms แบบสมัยก่อน)
  • ⚠️ PBKDF2legacy / FIPS-mandated only สำหรับ system ใหม่ที่ไม่ผูกกับ FIPS ใช้ Argon2id แทน
java
// PBKDF2 — ใช้เฉพาะเมื่อต้อง compat กับระบบเก่า/FIPS
// ตัวอย่างนี้ไว้ดู API ของ JCA เป็น reference
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(
    password.toCharArray(),
    salt,
    600_000,    // iterations — OWASP Password Storage Cheat Sheet (2024)
    256         // key length (bits)
);
SecretKey key = factory.generateSecret(spec);

📒 ทำไม class เยอะ: Java JCA API design มาแบบ "factory + spec" — SecretKeyFactory คือ "โรงงานสร้าง key", KeySpec คือ "สูตรการสร้าง" (params), PBEKeySpec = "password-based encryption" spec · จำ pattern นี้: factory.generateSecret(spec) → SecretKey

⚠️ iteration count (last reviewed: 2026-06): OWASP Password Storage Cheat Sheet (2024) แนะนำ:

  • Argon2id — m=19MB, t=2, p=1 (default ของ libsodium ใหม่)
  • PBKDF2-HMAC-SHA256: ขั้นต่ำ 600,000 iterations
  • PBKDF2-HMAC-SHA512: ขั้นต่ำ 210,000 iterations
  • bcrypt: cost factor 12+ (timing target ~250-500ms บน 2026 server CPU — ไม่ใช่ 30-100ms ที่เคยใช้ปี 2010)
  • source: OWASP Password Storage Cheat Sheet — เช็คค่าล่าสุดก่อน deploy เพราะ recommended count เพิ่มขึ้นทุก ๆ 2-3 ปี

ใช้สำหรับ: password manager, encryption with password


Part 2: TLS — Transport Layer Security

10. TLS ทำงานยังไง (พื้นฐาน)

อธิบาย step-by-step ภาษามนุษย์:

  1. ClientHello — เบราว์เซอร์บอก server ว่า "ฉันรองรับ cipher/version พวกนี้ + ส่ง random number มาด้วย"
  2. ServerHello — server ตอบกลับ "งั้นเลือก cipher นี้ + ส่ง random number ของฉัน"
  3. Certificate — server ส่ง certificate (ใบรับรอง) ของตัวเองมา → client เช็คว่า CA ที่ออก cert เชื่อถือได้ไหม
  4. ServerKeyExchange — server ส่ง "กุญแจ ECDHE ชั่วคราว" (public key สำหรับ session นี้เท่านั้น) มา
  5. ClientKeyExchange — client ส่ง "กุญแจ ECDHE ชั่วคราว" ของตัวเองกลับไป
  6. ทั้งสองฝั่งใช้ "private ของตัวเอง + public ของคู่" คำนวณ shared secret ออกมา (Diffie-Hellman magic — ดักได้แต่ถอดไม่ได้)
  7. Finished — encrypted ด้วย shared secret → จากนี้ทุกอย่าง encrypted

📌 ใน TLS 1.3 steps จะรวบเหลือ 1 round-trip (0-RTT ในบางกรณี) เพราะ client ส่ง keyshare ตั้งแต่ ClientHello เลย

TLS Version

📅 ตารางนี้ last reviewed: 2026-06 — ตรวจ Mozilla SSL Config Generator ก่อน deploy production: https://ssl-config.mozilla.org/

Versionสถานะ (as of 2026-Q2)
TLS 1.3mandatory สำหรับระบบใหม่ — NIST SP 800-52 Rev 3 (Aug 2024) แนะนำ; HSTS preload list บังคับ
TLS 1.2⚠️ legacy compat only — formally deprecated สำหรับ deployment ใหม่ (RFC 8996, March 2021); IETF discussion ลด support ตั้งแต่ปี 2024 · เก็บไว้เฉพาะ backward compat กับ client เก่า (older browsers, IoT) · เฉพาะกับ AEAD cipher (AES-GCM, ChaCha20-Poly1305) ห้าม CBC mode
TLS 1.1 / 1.0❌ disable เด็ดขาด — browsers dropped 2020 (Chrome 84, Firefox 78), PCI-DSS deprecated since 2018
SSL 3.0 / SSL 2.0❌ broken (POODLE 2014, DROWN 2016) — ห้ามมีในระบบ

📌 ระวัง overstate: PCI-DSS 4.0.1 (มีผล 2024-06) บังคับ "strong cryptography" ไม่ได้บังคับ TLS 1.3 specifically — TLS 1.2 ยังผ่าน PCI ถ้า config cipher suite ถูก

AEAD = Authenticated Encryption with Associated Data — เข้ารหัส + ตรวจความถูกต้องในก้อนเดียว (เช่น AES-GCM, ChaCha20-Poly1305)

ตัวอย่าง nginx config (แนะนำเริ่มต้นจาก Mozilla SSL Config Generator — "intermediate" profile, reviewed 2026-06):

nginx
# nginx 1.19.4+ (Aug 2020) — รองรับ ssl_conf_command ที่จำเป็นสำหรับ TLS 1.3 cipher selection
ssl_protocols TLSv1.3 TLSv1.2;

# ssl_ciphers = ใช้สำหรับ TLS 1.2 เท่านั้น (TLS 1.3 ignore directive นี้)
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

# ssl_conf_command Ciphersuites = ใช้สำหรับ TLS 1.3 (OpenSSL 1.1.1+)
# default ของ OpenSSL ก็ปลอดภัยอยู่แล้ว — ระบุชัดเจนเพื่อ explicit
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;

ssl_prefer_server_ciphers off;   # TLS 1.3 ignore directive นี้อยู่แล้ว

⚠️ bug ที่พบบ่อย: ใส่ TLS_AES_256_GCM_SHA384 (cipher ของ TLS 1.3) ลง ssl_ciphers directly — nginx จะ warn/ignore เพราะ directive นั้นเป็นของ TLS 1.2 เท่านั้น สำหรับ TLS 1.3 ต้องใช้ ssl_conf_command Ciphersuites

Cipher Suite

ชื่อ cipher suite แบบ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ตอนแรกอ่านแล้วเหมือนรหัสรถถัง — จริง ๆ มันคือ "ส่วนผสม 5 ขั้น" เขียนต่อกัน:

TLS 1.2 format — 5 ส่วน:

  🏷️ TLS    | 🔑 ECDHE      | 🪪 RSA         | 🔒 AES_256_GCM    | #️⃣ SHA384
  prefix    | key exchange  | authentication | symmetric (AEAD)  | hash for MAC/PRF
            | (forward      | (cert sig algo)|                   |
            |  secrecy!)    |                |                   |

อ่านเป็นประโยค: "TLS ใช้ ECDHE ตกลง key, RSA เซ็น cert, AES-256 GCM เข้ารหัสข้อมูล, SHA384 ทำ MAC"

TLS 1.3 format — เหลือ 2 ส่วน (สั้นกว่ามาก):

  🏷️ TLS    | 🔒 AES_256_GCM    | #️⃣ SHA384
  prefix    | symmetric (AEAD)  | hash for PRF / HKDF

📌 ทำไม TLS 1.3 cipher suite สั้นกว่า:TLS 1.3 ตัด key exchange (ECDHE บังคับ) และ authentication (กำหนดผ่าน signature_algorithms extension แยก) ออกจาก cipher suite — เหลือแค่ AEAD + hash function เท่านั้น ทำให้ negotiate ง่ายและลด combination ลงเหลือไม่กี่ตัว

Forward Secrecy (ความลับย้อนหลังปลอดภัย)

Forward secrecy = แม้ private key ของ server หลุดในอนาคต ข้อมูลที่ดักเก็บไว้ก่อนหน้านี้ก็ยัง decrypt ไม่ได้ — เพราะแต่ละ session ใช้กุญแจชั่วคราวคนละตัว

ใช้ ECDHE (อ่าน "อี-ซี-ดี-เอช-อี" — Elliptic-Curve Diffie–Hellman Ephemeral — สร้าง key ชั่วคราวบน elliptic curve) หรือ DHE (อ่าน "ดี-เอช-อี" — Diffie–Hellman Ephemeral — เวอร์ชันไม่ใช้ EC; ช้ากว่าและต้องใช้ key ใหญ่กว่ามาก) → ถ้า private key ของ server ถูกขโมย → session เก่า ๆ ก็ยัง decrypt ไม่ได้

ห้ามใช้ static RSA (RSA แบบกุญแจตายตัว) — ไม่มี forward secrecy (กุญแจหลุดทีเดียวถอดได้หมด)

📖 คำย่อ + คำอ่าน:

  • DH = Diffie–Hellman (อ่าน "ดิฟ-ฟี่-เฮลล์-แมน" — ชื่อนักคณิตศาสตร์ 2 คน) วิธีตกลง shared key ผ่าน channel เปิด
  • E = Ephemeral (อ่าน "เอ-เฟ-เมอ-รัล" = ชั่วคราว — สร้างใหม่ทุก session)
  • EC = Elliptic Curve (ใช้คณิตศาสตร์ของ elliptic curve → key สั้นกว่าและเร็วกว่า)

11. Certificate

X.509 Format

Certificate:
  Subject:    CN=api.example.com
  Issuer:     CN=Let's Encrypt R3
  Valid:      2026-01-01 to 2026-04-01 (90 days — see note below)
  Public Key: <key>
  Signature:  <signed by issuer>
  
  SAN (Subject Alternative Names):
    DNS:api.example.com
    DNS:www.example.com

Chain of Trust

Root CA (trusted, in browser)
   ↓ signs
Intermediate CA
   ↓ signs
Your server certificate

Browser มี root CA list — verify chain ขึ้นไป

📌 Certificate validity ปี 2026 — อย่าจำ "90 วัน" เป็นค่าตายตัว:

  • Let's Encrypt: 90 วัน default + 6 วัน short-lived certs (beta 2025)
  • CA/B Forum SC-081 (passed 2025) บังคับลด max cert validity เหลือ 47 วัน ภายในมีนาคม 2029
  • Let's Encrypt ประกาศจะ phase out 90-day certs โดย ~Sep 2026 → ใช้ ACME v2 automation ตั้งแต่ตอนนี้

CT logs (Certificate Transparency): browser ทุกตัวบังคับแล้ว — cert ที่ issue ทุกใบต้องเข้า public log (helps detect rogue CA)

Get Certificate

bash
# certbot = client ของ Let's Encrypt — รันบรรทัดเดียวก็ได้ cert ฟรี + auto renew
# -w (webroot) = path ที่จะวาง challenge file ให้ Let's Encrypt มา verify domain ownership
# -d (domain) = domain ที่จะออก cert ให้
certbot certonly --webroot -w /var/www/html -d api.example.com

# ทางเลือกอื่น (ACME v2 ecosystem):
# - cert-manager (Kubernetes)
# - Smallstep CA (private CA + ACME ภายในองค์กร)
# - acme.sh (lightweight shell client)

# Internal CA สำหรับ internal service
openssl req ...

Pinning

App ตรวจว่า certificate มาจาก specific CA / public key:

Public key pinning (mobile app)
→ ป้องกัน rogue CA + MITM

⚠️ Risky — ถ้า pin หาย → app ทุกตัวที่ deploy แล้วใช้ไม่ได้


12. mTLS — Mutual TLS

TLS ปกติ client ตรวจ server ฝ่ายเดียว (เช่นเบราว์เซอร์ตรวจว่าเว็บคือของจริง) — mTLS เพิ่มให้ server ตรวจ client ด้วย ทั้งสองฝั่งจึงพิสูจน์ตัวตนกันและกัน เหมาะกับ service-to-service ภายใน, ระบบ high-security และ zero-trust network:

ปกติ TLS = client verify server
mTLS = ทั้ง client + server verify กัน

Server cert ← client verify
Client cert → server verify

ใช้กับ:

  • Service-to-service (internal microservices)
  • Banking / high security
  • Zero-trust network
yaml
# Spring Boot
server:
  ssl:
    key-store: server.p12
    key-store-password: ...
    trust-store: clients-ca.p12     # CA ที่ออก cert ให้ client
    trust-store-password: ...        # optional — truststore เก็บ public cert ของ CA เท่านั้น
                                     # ไม่จำเป็นต้องใส่ password (เว้นแต่ truststore เก็บ private key)
    client-auth: need                # บังคับ client cert (ทางเลือก: want = optional, none = ปิด)

Part 3: Key Management

13. Key Lifecycle

key ทาง crypto ไม่ใช่ของที่สร้างทิ้งไว้ตลอดกาล — มันมี "วงจรชีวิต" ตั้งแต่ generate → distribute → use → rotate → revoke → destroy การจัดการแต่ละขั้นให้ปลอดภัยสำคัญพอ ๆ กับ algorithm เพราะ key ที่หลุดทำให้ crypto ทั้งหมดไร้ความหมาย:

1. Generate
2. Distribute (secure)
3. Use
4. Rotate (periodically)
5. Backup (encrypted)
6. Revoke (when compromise)
7. Destroy (secure wipe)

14. Key Storage

❌ ห้าม

ใส่ใน code:
private static final String KEY = "abc123";

ใน config file ที่ commit:
key: abc123

ใน environment variable plain:
KEY=abc123  (ดีกว่า in-code แต่ยังเสี่ยง)

✅ Storage ที่ถูก

1. Cloud Secret Manager
   - AWS Secrets Manager
   - GCP Secret Manager
   - Azure Key Vault
   
2. HashiCorp Vault
   - Self-hosted
   - Most flexible
   
3. HSM (Hardware Security Module)
   - Physical hardware
   - High-security (banking, gov)
   - AWS CloudHSM, etc.
   
4. KMS (Key Management Service)
   - Key เก็บใน HSM ของ cloud
   - App ใช้ผ่าน API (sign/encrypt/decrypt)
   - Key ไม่เคยออกจาก KMS

ดูบทที่ 5 (Secret Management) สำหรับรายละเอียด


15. Envelope Encryption Pattern (เข้ารหัสแบบซองจดหมาย)

Envelope encryption = "เข้ารหัสซ้อนชั้นแบบซองจดหมาย": ใช้กุญแจตัวเล็ก (DEK) เข้ารหัสข้อมูลจริง แล้วเอากุญแจใหญ่ (Master Key ที่อยู่ใน KMS ออกมาไม่ได้) มาเข้ารหัสกุญแจเล็กอีกที — เหมือนใส่กุญแจไว้ในซองที่ล็อกด้วยกุญแจใหญ่ · KMS = Key Management Service (บริการจัดการกุญแจของ cloud) · DEK = Data Encryption Key (กุญแจที่ใช้เข้ารหัสข้อมูลจริง)

ขั้นตอน encrypt:

1. App ขอ DEK ใหม่จาก KMS
2. KMS return DEK + encrypted DEK
3. App ใช้ DEK encrypt data
4. App store: encrypted DEK + encrypted data
5. App ทิ้ง DEK ออกจาก memory

ขั้นตอน decrypt:

1. App ดึง encrypted DEK + data
2. ส่ง encrypted DEK ไป KMS
3. KMS return DEK
4. App ใช้ DEK decrypt data

ทำไม?

  • ✅ Master key อยู่ใน KMS เสมอ (ไม่หลุด)
  • ✅ Rotate master key ง่าย (re-encrypt แค่ DEK ทั้งหมด, ไม่ใช่ data)
  • ✅ Performance — KMS call แค่ DEK เล็ก ๆ ไม่ใช่ data ใหญ่
  • ✅ Each data has unique DEK

📎 ทำไมต้อง byte[] ไม่ใช่ String สำหรับ key/secret ใน Java:

  • String ใน Java immutable — เก็บใน String pool/heap, garbage collect เมื่อไหร่ก็ได้, ระหว่างนั้น memory dump อ่านได้
  • Arrays.fill(bytes, (byte) 0) zero ออกได้ทันที = secret หาย จาก memory หลังใช้เสร็จ
  • String.replace() ไม่ overwrite — สร้าง String ใหม่ ตัวเก่ายังลอยใน heap
  • กฎทอง: secret/password ใช้ char[] หรือ byte[] เสมอ + zeroize หลังใช้

⚠️ JVM modern caveat: GC (ZGC, G1, Shenandoah) อาจ copy bytes ระหว่าง region ระหว่าง compact — zeroize 1 จุดอาจไม่ลบ copy เก่าใน old region ทันที สำหรับ high-security (banking, HSM-class) ใช้:

  • ByteBuffer.allocateDirect() (off-heap, GC ไม่ย้าย) — แต่ต้อง zeroize ด้วยตัวเองและ free ผ่าน cleaner
  • MemorySegment ของ Foreign Memory API (Java 22+) แทน sun.misc.Unsafe
  • Hardware-backed key store (HSM, AWS KMS, Cloud HSM) ที่ key ไม่ลงมาใน JVM memory เลย

⚠️ SecretKeySpec ก็ COPY bytes: ตอนสร้าง new SecretKeySpec(plaintextDek, "AES") constructor จะ copy plaintextDek ลง field ภายใน · zeroize plaintextDek ตัวนอกไม่ลบ copy ภายในของ SecretKeySpec · ถ้าต้องการลบจริง ๆ ต้องเรียก .destroy() (ถ้า provider implement — ไม่ใช่ทุก provider implement)


16. Key Rotation

key ที่ใช้นาน ๆ เสี่ยงหลุดและถูกเจาะมากขึ้นเรื่อย ๆ จึงต้อง "หมุนเปลี่ยน" (rotate) ตามรอบ — แต่เปลี่ยนทื่อ ๆ จะทำให้ข้อมูลเก่าถอดไม่ออก เคล็ดลับคือรองรับหลาย key พร้อมกัน (เก่า verify ได้, ใหม่ใช้เขียน) แล้วค่อย ๆ re-encrypt ส่วนนี้สอน rotation แบบไม่มี downtime:

Schedule:
- KMS master key: 1 year
- Application encryption key: 90 days
- API key: 90 days
- JWT signing key: 30-90 days
- Database password: 90 days
- TLS certificate: Let's Encrypt = 90 วัน (auto renew)

Rotation Without Downtime

1. Generate new key (key_v2)
2. App รองรับ ทั้ง key_v1 + key_v2
3. Re-encrypt ข้อมูลด้วย v2 (background)
4. Switch new write → v2
5. หลังจาก verify ทั้งหมด → remove v1

JWT Rotation

java
// Multiple keys ที่ active
@Bean
public JWKSource<SecurityContext> jwkSource() {
    return (selector, context) -> 
        keyRepo.findActive().stream()
            .map(JWK::parse)
            .collect(Collectors.toList());
}

→ Token เก่า verify ได้ + Token ใหม่ใช้ key ใหม่


17. Encryption at Rest vs in Transit

ข้อมูลต้องถูกปกป้องในทุก "สถานะ" — ระหว่างส่งผ่าน network (in transit, ใช้ TLS), ตอนเก็บใน disk (at rest, ใช้ TDE/disk encryption) และล่าสุดแม้ตอน CPU ประมวลผล (in use, confidential computing) ส่วนนี้แยกแต่ละสถานะและเครื่องมือที่ใช้:

In Transit

ระหว่างส่งผ่าน network → ใช้ TLS
- HTTPS for API
- TLS for DB connection
- VPN for service-to-service

At Rest

เก็บใน disk → encrypted
- Database TDE (Transparent Data Encryption)
- Disk encryption (LUKS, BitLocker)
- S3 encryption (SSE-S3, SSE-KMS)
- Application-level encryption (very sensitive)

In Use (newer)

ตอน CPU process ก็ encrypted
- Intel SGX, AMD SEV
- Confidential Computing

18. ⚠️ Crypto Pitfalls

crypto เป็นเรื่องที่ "พลาดนิดเดียวพังหมด" — ความผิดพลาดที่เจอบ่อยคือเขียน algorithm เอง (อย่าทำ!), ใช้ ECB mode, reuse IV/nonce, หรือใช้ Math.random() แทน secure random ส่วนนี้รวมกับดักพร้อมโค้ดที่ถูก/ผิด ให้เลี่ยงตั้งแต่ต้น:

1. Roll Your Own Crypto

java
// ❌
public String encrypt(String text, String key) {
    char[] result = new char[text.length()];
    for (int i = 0; i < text.length(); i++) {
        result[i] = (char) (text.charAt(i) ^ key.charAt(i % key.length()));
    }
    return new String(result);
}

// ✅ ใช้ AES-GCM จาก JDK

2. ECB Mode

java
// ❌
Cipher.getInstance("AES/ECB/PKCS5Padding");

// ✅
Cipher.getInstance("AES/GCM/NoPadding");

3. IV ซ้ำ

java
// ❌
byte[] iv = "1234567890123456".getBytes();   // ทุก call ใช้ IV เดียวกัน

// ✅
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);

4. Math.random for Token

java
// ❌
String token = String.valueOf(Math.random()).substring(2);

// ✅
SecureRandom sr = new SecureRandom();
byte[] tokenBytes = new byte[32];
sr.nextBytes(tokenBytes);

5. Hardcoded Key

java
// ❌
private static final String SECRET = "my-secret-2026";

// ⚠️ ดีขึ้นนิดหน่อย แต่ยังไม่ ideal
private static final String SECRET = System.getenv("SECRET");
// env var ใน JVM ยัง leak ได้ผ่าน /proc/<pid>/environ, heap dump, error message
// ✅ ดีกว่า: inject ตอน runtime จาก Vault / AWS Secrets Manager / KMS
//    + cache ใน char[]/byte[] + zeroize หลังใช้

6. Timing Attack

java
// ❌
if (provided.equals(stored)) { ... }   // early exit by character

// ✅
if (MessageDigest.isEqual(provided.getBytes(), stored.getBytes())) { ... }

7. Padding Oracle (การโจมตีผ่าน error ของ padding)

Padding = ไบต์เติมท้ายข้อมูลให้ครบขนาด block (เช่น AES block = 16 bytes ถ้าข้อมูลเหลือ 13 bytes ต้องเติม 3 bytes ให้ครบ)

Padding oracle เปรียบเทียบเหมือน แม่กุญแจที่ส่งเสียง "คลิ๊ก" บอกว่ารหัสล็อกถูก/ผิด — ขโมยไม่ต้องรู้รหัสจริง แค่ลองหมุนทีละหลัก ฟังเสียง "คลิ๊ก" เดาทีละหลักได้

ในโลก crypto: ถ้าระบบตอบ attacker ได้ว่า "padding ถูกหรือผิด" (ผ่าน error message, HTTP status, response time) → attacker ส่ง ciphertext ที่แก้ทีละ byte แล้วฟัง "เสียงคลิ๊ก" → ค่อย ๆ เดารหัสได้ทีละ byte จนได้ plaintext ทั้งหมด — โดยไม่ต้องรู้ key เลย

ใช้ CBC mode ที่ leak (เผยให้รู้) padding error → attacker decrypt ได้ทีละ byte

→ ใช้ GCM (authenticated encryption — เข้ารหัสที่ตรวจความถูกต้องในตัว) แทน ปลอดภัยกว่า

🛠️ ถ้ายังต้อง support CBC (legacy / interop):

  • ใช้ encrypt-then-MAC pattern: คำนวณ HMAC(key_mac, IV || ciphertext) ครอบ ciphertext แล้ว verify MAC ก่อน decrypt
  • ถ้า MAC verify fail → reject ทันที ก่อน ทำ padding check (ไม่ leak padding error)
  • ใช้ constant-time padding verification + return error เดียวกันสำหรับ padding error และ MAC error
  • key สำหรับ encrypt และ MAC ต้องคนละตัว (derive ผ่าน HKDF)

8. Weak Random Seed

java
// ❌
Random r = new Random(System.currentTimeMillis());  // attacker เดา seed ได้

// ✅
SecureRandom r = new SecureRandom();   // seed จาก /dev/urandom

9. Key Reuse Across Purpose

❌ ใช้ key เดียวกัน encrypt + sign
✅ Generate key ต่างสำหรับแต่ละ purpose (encryption key, signing key, ...)

10. Long Key Lifetime

❌ JWT signing key อายุ 5 ปี
✅ Rotate 90 วัน

19. Modern Cryptography ที่ควรรู้

Argon2id — Password Hashing

ดูบทที่ 2

Ed25519 — Signing

(อ่าน "อีดี-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์" — "Edwards curve 25519")

  • เร็ว, ปลอดภัย, key เล็ก (32 bytes)
  • ใช้แทน RSA / ECDSA

X25519 / Curve25519 — Key Exchange

(อ่าน "เอ็กซ์-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์" / "เคิร์ฟ-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์")

  • ใช้ใน TLS 1.3, Signal, WireGuard

ChaCha20-Poly1305

(อ่าน "ชา-ชา-ทเวนตี้ โพ-ลี-วัน-เธอเรอะ-โอ-ไฟว์")

  • Symmetric encryption ทางเลือกของ AES-GCM
  • เร็วบน mobile/ARM (ไม่ต้องการ hardware AES instruction)

libsodium / NaCl

(อ่าน "ลิบ-โซ-เดียม" / "แนกเคิล" — ตั้งใจเล่นคำกับ NaCl = เกลือทางเคมี · อ่านเป็น "salt" ก็ได้)

  • High-level crypto library
  • Hard to misuse — แนะนำใช้ในภาษาที่มี wrapper
  • Java: lazysodium-java
java
// libsodium-style
byte[] encrypted = SecretBox.encrypt(message, key);
// → no choice of mode / IV — library handle

20. Post-Quantum Cryptography ⭐ (Update 2024-2026)

🟡 ขั้นสูง — section นี้สำหรับคนที่เข้าใจ RSA + ECDH แล้ว

Post-Quantum cryptography (PQC) = crypto ที่ทนต่อ quantum computer ในอนาคต basic ที่ยังไม่คุ้น RSA/ECDH: อ่านเอาความรู้สึก — NIST finalize standards แล้ว (FIPS 203/204/205, ส.ค. 2024) + Chrome/Cloudflare deploy hybrid TLS ตั้งแต่ 2024

สิ่งสำคัญที่ basic ต้องรู้: "เก็บกักไว้ก่อน-ถอดทีหลัง" (Harvest now, decrypt later) — attacker เก็บ data ที่ encrypt ไว้ตอนนี้ รอ quantum computer พัฒนาแล้ว decrypt ทีหลัง · data ที่ sensitive ระยะยาว (medical record 50 ปี, รหัสลับรัฐ, แผนธุรกิจ) เริ่ม migrate ตั้งแต่ตอนนี้

Threat: Quantum Computer Will Break Current Crypto

ปัญหาที่ใกล้:
- Quantum computer (ขนาดพอ — ~4,000 logical qubits) จะ break:
  - RSA (Shor's algorithm)
  - ECDSA, ECDH
  - Diffie-Hellman
- ยังไม่มี quantum computer ที่ใหญ่พอตอนนี้
- คาดการณ์ Q-Day: ~2030-2040

📒 qubit (อ่าน "คิว-บิต") = "quantum bit" = หน่วยข้อมูลของ quantum computer · ต่างจาก bit ทั่วไปที่เป็น 0 หรือ 1 อย่างเดียว — qubit อยู่ในสถานะ superposition (เป็น 0 และ 1 พร้อมกัน) จนกว่าจะวัด · "logical qubit" = qubit ที่ผ่าน error correction แล้ว ใช้ physical qubits จำนวนมาก (~1000:1) ทำให้ใช้งานได้จริง

NIST Standards — FIPS 203, 204, 205 (สิงหาคม 2024)

NIST ออก post-quantum standards อย่างเป็นทางการในเดือนสิงหาคม 2024:

FIPSAlgorithm (เคยชื่อ)จุดประสงค์
FIPS 203ML-KEM (เคยชื่อ Kyber — อ่าน "ไค-เบอร์")Key encapsulation (KEM) — แทน ECDH key exchange (RSA-OAEP encryption ก็ถูกทยอย phase out คนละทาง)
FIPS 204ML-DSA (เคยชื่อ Dilithium — อ่าน "ดิ-ลิ-เธียม")Digital signature — แทน RSA-PSS, ECDSA
FIPS 205SLH-DSA (เคยชื่อ SPHINCS+ — อ่าน "สฟิงซ์-พลัส")Stateless hash-based signature (slow + secure backup)

📒 คำอ่าน + ความหมายชื่อย่อ:

  • ML-KEM = "Module-Lattice-based Key Encapsulation Mechanism"
  • ML-DSA = "Module-Lattice-based Digital Signature Algorithm"
  • SLH-DSA = "Stateless Hash-based Digital Signature Algorithm"
  • KEM (Key Encapsulation Mechanism) ≠ encryption โดยตรง — KEM คือ "ห่อ shared key ส่ง" คล้าย Diffie-Hellman มากกว่าคล้าย RSA-OAEP

"เก็บกักไว้ก่อน-ถอดทีหลัง" (Harvest Now, Decrypt Later) Attack

สถานการณ์อันตราย:
1. ปี 2026 — attacker เก็บ TLS traffic ที่ encrypted ของคุณ
2. รอ ~10 ปี ให้ quantum computer พร้อม
3. ปี 2035 — decrypt traffic ที่เก็บไว้
4. → ข้อมูลที่ confidential ตอนนี้ → หลุดในอนาคต

→ ข้อมูลที่ต้อง confidential > 10 ปี ต้องเริ่มเปลี่ยนแล้ว!

Migration Strategy (ปี 2026)

1. ระบุข้อมูลที่ต้องเก็บลับ > 10 ปี:
   - Medical records
   - Government secrets
   - Long-term legal documents
   - Strategic business plans

2. Hybrid mode (interim):
   - ใช้ classic (ECDH/RSA) + PQ (ML-KEM) ผสมกัน
   - กัน both quantum AND classical attack
   - Cloudflare, Google Chrome ใช้แล้ว (X25519+Kyber768)

3. Library support:
   - OpenSSL 3.5+ — ML-KEM (ปี 2025)
   - BoringSSL — รองรับ
   - liboqs (Open Quantum Safe) — research lib
   - AWS/Cloudflare — already hybrid TLS

4. Code update:
   - ปกติ apps ใช้ TLS library — update lib = ได้ PQ ฟรี ๆ
   - ระบบที่ใช้ crypto โดยตรง — ต้องเปลี่ยน API

ปี 2026 — Status

✅ Big tech adopt:
   - Apple iMessage PQ3 (2024)
   - Signal — PQXDH key agreement
   - Cloudflare — hybrid PQ TLS by default
   - Google Chrome — X25519MLKEM768

⚠️ Library ยังกำลังพัฒนา:
   - JCE / BouncyCastle — เพิ่ม ML-KEM, ML-DSA แล้ว
   - libsodium — กำลังประเมิน

🔴 Avoid:
   - PQ algorithm ที่ "ไม่ใช่ NIST standard"
   - Premature deployment (ก่อน 2024 — ใช้ algo ที่ถูก ถอนออก)

Practical takeaway ปี 2026 — ต้องเริ่มแล้ว ไม่ใช่ "ยังไม่ต้องรีบ":

  • Cloudflare + Chrome > 50% ของ TLS handshake บนอินเทอร์เน็ตเป็น X25519MLKEM768 hybrid แล้ว (ตั้งแต่ปลาย 2024)
  • NIST CSRC: deprecate RSA-2048 / ECC-P256 สำหรับ federal use ภายในปี 2030 · disallow หลัง 2035
  • ของใหม่: เริ่มใช้ hybrid TLS (X25519+ML-KEM-768) ทันที + วางแผน migrate signature เป็น ML-DSA
  • ของเก่า: ระบุ data ที่ confidential > 10 ปี + ทยอย re-encrypt

📌 X25519MLKEM768 = hybrid TLS 1.3 key share ที่ Chrome/Cloudflare ใช้ default ปี 2024+ — ผสม classic X25519 + post-quantum ML-KEM-768 ผ่าน HKDF

ลองเล่นด้วย BouncyCastle ML-KEM (Kyber)

(BouncyCastle อ่าน "เบาน์-ซี่-คาส-เซิ่ล" — แปลตรงตัว "ปราสาทเด้งดึ๋ง")

⚠️ API ยังไม่ stable — bcprov-jdk18on 1.78+ ใส่ ML-KEM/ML-DSA แล้ว แต่ namespace และ class signature อาจเปลี่ยนใน minor release ตรวจ BC release note ก่อนใช้ production (last reviewed: 2026-06)

java
// ⚠️ ตัวอย่างนี้ optional — ต้อง Java 21+ เพราะ javax.crypto.KEM API ถูกเพิ่มใน JEP 452 (Java 21)
//    Java 17 (LTS เก่า) จะ fail compile · ต้อง upgrade JDK หรือใช้ low-level API ของ BC ตรง ๆ
//
// dependency: org.bouncycastle:bcprov-jdk18on:1.78+
// Security.addProvider(new BouncyCastleProvider());

import org.bouncycastle.jcajce.spec.MLKEMParameterSpec;
import javax.crypto.KeyGenerator;
import javax.crypto.KEM;
import javax.crypto.SecretKey;
import java.security.KeyPairGenerator;
import java.security.KeyPair;

// 1. Generate ML-KEM-768 key pair (security level ~AES-192)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM", "BC");
kpg.initialize(MLKEMParameterSpec.ml_kem_768);
KeyPair kp = kpg.generateKeyPair();

// 2. Sender: encapsulate → shared secret + ciphertext
KEM kemSender = KEM.getInstance("ML-KEM", "BC");
KEM.Encapsulator enc = kemSender.newEncapsulator(kp.getPublic());
KEM.Encapsulated encapsulated = enc.encapsulate();
SecretKey sharedSecretSender = encapsulated.key();
byte[] ciphertext = encapsulated.encapsulation(); // ส่งไปฝั่ง receiver

// 3. Receiver: decapsulate ด้วย private key → shared secret เดียวกัน
KEM kemReceiver = KEM.getInstance("ML-KEM", "BC");
KEM.Decapsulator dec = kemReceiver.newDecapsulator(kp.getPrivate());
SecretKey sharedSecretReceiver = dec.decapsulate(ciphertext);

// 4. ใช้ sharedSecret เป็น key สำหรับ AES-GCM symmetric encryption
//    (hybrid: ผสมกับ ECDH shared secret ผ่าน HKDF — อย่าใช้ ML-KEM เดี่ยวๆ)

📌 แนวทาง hybrid (recommended 2026): ผสม shared secret ของ ECDH (X25519) + ML-KEM-768 ผ่าน HKDF — ถ้า PQC algorithm ตัวใดตัวหนึ่งโดน break ในอนาคต อีกตัวยัง protect อยู่ Cloudflare/Apple/Signal ใช้ pattern นี้

ตัวอย่าง hybrid (pseudo-code — combine ใน practice):

java
// 1. X25519 classic KEX
byte[] classicSecret = x25519Agreement(myEcPriv, peerEcPub);   // 32 bytes

// 2. ML-KEM-768 PQ KEM (ตัวอย่างจากบล็อกข้างบน)
byte[] pqSecret = sharedSecretSender.getEncoded();              // 32 bytes

// 3. รวมผ่าน HKDF — ถ้าทั้ง 2 ตัวยังปลอดภัย → final key ปลอดภัย
//    ถ้าตัวใดตัวหนึ่งโดน break (เช่น X25519 พังจาก quantum) → อีกตัวยังกัน
byte[] combined = concat(classicSecret, pqSecret);
byte[] finalKey = HKDF.expand(HKDF.extract(salt, combined), info, 32);
// → finalKey ใช้กับ AES-GCM session

21. ตัวอย่างจริง — File Encryption Service

ปิดท้ายด้วยการรวมทุกแนวคิดเป็นโค้ดจริง — service เข้ารหัสไฟล์ที่ใช้ envelope encryption (ขอ DEK จาก KMS, เข้ารหัส data ด้วย DEK, แล้วเก็บ DEK ที่ถูกเข้ารหัสไว้กับไฟล์) ตัวอย่างนี้แสดง pattern ที่ production จริงใช้ ไม่ใช่ของเล่น:

🟡 prerequisite: ตัวอย่างนี้ใช้ AWS SDK for Java v2 (Builder pattern, SdkBytes) — ถ้าไม่เคยใช้ AWS ให้อ่านเอาหลักการ envelope encryption แทน · API ของ GCP KMS / Azure Key Vault โครงสร้างคล้ายกัน (request → response → asByteArray)

java
// ใช้ AWS SDK for Java v2 — software.amazon.awssdk:kms
// dependency: software.amazon.awssdk:kms:2.25.x+
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest;
import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse;
import software.amazon.awssdk.services.kms.model.DecryptRequest;
import software.amazon.awssdk.services.kms.model.DecryptResponse;
import software.amazon.awssdk.services.kms.model.DataKeySpec;
import software.amazon.awssdk.core.SdkBytes;

@Service
public class FileEncryptionService {

    private final KmsClient kms;
    private final String kmsKeyId; // "alias/my-app" หรือ ARN

    public FileEncryptionService(KmsClient kms, String kmsKeyId) {
        this.kms = kms;
        this.kmsKeyId = kmsKeyId;
    }

    public EncryptedFile encrypt(byte[] data, String contextId) throws Exception {
        // 1. ขอ DEK จาก KMS (AWS SDK v2 — Builder pattern)
        GenerateDataKeyResponse dataKey = kms.generateDataKey(
            GenerateDataKeyRequest.builder()
                .keyId(kmsKeyId)
                .keySpec(DataKeySpec.AES_256)
                .build()
        );

        byte[] plaintextDek = dataKey.plaintext().asByteArray();
        SecretKey dek = new SecretKeySpec(plaintextDek, "AES");

        try {
            // 2. Random IV
            byte[] iv = new byte[12];
            new SecureRandom().nextBytes(iv);

            // 3. Encrypt data ด้วย DEK — ผูก context ผ่าน AAD
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, dek, new GCMParameterSpec(128, iv));
            cipher.updateAAD(contextId.getBytes(StandardCharsets.UTF_8));
            byte[] cipherText = cipher.doFinal(data);

            // 4. Return: IV + encrypted DEK + cipher + context
            return new EncryptedFile(
                iv,
                dataKey.ciphertextBlob().asByteArray(), // encrypted DEK
                cipherText,
                contextId
            );
        } finally {
            // 5. ทิ้ง DEK plaintext ออกจาก memory ทุกกรณี (รวม exception path)
            Arrays.fill(plaintextDek, (byte) 0);
        }
    }

    public byte[] decrypt(EncryptedFile encrypted) throws Exception {
        // 1. Decrypt DEK ผ่าน KMS
        DecryptResponse decryptRes = kms.decrypt(
            DecryptRequest.builder()
                .ciphertextBlob(SdkBytes.fromByteArray(encrypted.encryptedDek()))
                .build()
        );
        byte[] dekBytes = decryptRes.plaintext().asByteArray();
        SecretKey dek = new SecretKeySpec(dekBytes, "AES");

        try {
            // 2. Decrypt data — ต้อง updateAAD ตัวเดียวกับตอน encrypt
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, dek, new GCMParameterSpec(128, encrypted.iv()));
            cipher.updateAAD(encrypted.contextId().getBytes(StandardCharsets.UTF_8));
            return cipher.doFinal(encrypted.cipherText());
        } finally {
            Arrays.fill(dekBytes, (byte) 0);
        }
    }
}

📌 API ที่ใช้จริงใน AWS SDK v2:

  • KmsClient.generateDataKey(GenerateDataKeyRequest)GenerateDataKeyResponse
  • .plaintext() คืน SdkBytes — เรียก .asByteArray() เพื่อ get byte[]
  • .ciphertextBlob() = encrypted DEK ที่ store คู่กับ data
  • อย่า log plaintextDek หรือ dekBytes — เป็นความลับ
  • SDK v1 (com.amazonaws:aws-java-sdk-kms) deprecated — ใช้ v2 เท่านั้นใน project ใหม่

22. Library ใน Java

java
// JCA (Java Cryptography Architecture)
java.security.*       // Hash, signature, KeyGenerator
javax.crypto.*        // Cipher, Mac, KeyAgreement

// Bouncy Castle — ทางเลือก (มี algorithm ใหม่ ๆ)
org.bouncycastle:bcprov-jdk18on

// Google Tink — high-level (Google design — hard to misuse)
com.google.crypto.tink:tink

// Lazysodium — libsodium wrapper
com.goterl:lazysodium-java

ลองใช้ Tink หรือ libsodium — interface ที่ design มาให้ใช้ผิดยาก


23. ⚠️ ข้อผิดพลาดที่พบบ่อยสรุป

Hash password ด้วย SHA256BCrypt/Argon2
Roll own cryptoใช้ JCA / Tink / libsodium
ECB modeGCM mode
IV ซ้ำrandom ใหม่ทุกครั้ง
Math.random for tokenSecureRandom
Hardcoded keySecrets Manager / Vault
.equals() compare signatureMessageDigest.isEqual
RSA encrypt big datahybrid (RSA + AES)
Long-lived keyrotate 90 วัน
Single key for all purposeskey ต่างสำหรับแต่ละ purpose

24. Checkpoint

🛠️ Checkpoint 4.1 — Hash + HMAC

  • SHA-256 hash ของ string
  • HMAC-SHA256 สำหรับ webhook signature
  • Verify ด้วย constant-time compare

🛠️ Checkpoint 4.2 — AES-GCM

  • Generate AES-256 key
  • Encrypt string ด้วย random IV
  • Store: IV + cipher
  • Decrypt กลับมา

🛠️ Checkpoint 4.3 — RSA Signature

  • Generate RSA 2048-bit key pair
  • Sign string ด้วย private
  • Verify ด้วย public
  • ลองแก้ string 1 ตัว → verify fail

🛠️ Checkpoint 4.4 — TLS Test

  • Run server ด้วย Let's Encrypt cert
  • ทดสอบที่ ssllabs.com — ตั้งเป้าให้ได้ A+
  • ดูว่า cipher suite ที่ใช้คืออะไร

🛠️ Checkpoint 4.5 — Envelope Encryption

  • ทำ encryption service ที่ใช้ AWS KMS / GCP KMS
  • Encrypt file ใหญ่ ๆ
  • Verify rotate master key ไม่กระทบ data ที่ encrypt แล้ว

25. สรุปบท

Don't roll your own crypto — ใช้ library มาตรฐาน (JCA, Tink, libsodium)
Hash ≠ encrypt — SHA-256 OK สำหรับ integrity, BCrypt/Argon2 สำหรับ password
HMAC สำหรับ message authentication — constant-time compare
AES-256-GCM = standard symmetric encryption — random IV ทุกครั้ง
RSA/ECDSA/Ed25519 = asymmetric — encrypt small / sign
Hybrid encryption สำหรับ data ใหญ่ (AES + RSA)
SecureRandom ไม่ใช่ Math.random
TLS 1.2+ + forward secrecy + Let's Encrypt
mTLS สำหรับ service-to-service / zero-trust
Envelope encryption + KMS = best practice ของ key management
Rotate keys + at-rest / in-transit / in-use encryption


← บทที่ 3 | บทที่ 5 → Secret Management


Glossary: ../glossary.md · Style guide: ../CONTRIBUTING.md last_verified: 2026-06-03 · review report: ../REVIEW-2026-06-03.md